//! Remote-connection use case (ARCHITECTURE §6, L9).
//!
//! [`ConnectRemote`] establishes a project's [`RemoteHost`] and validates that
//! its root is reachable on the host's filesystem. It speaks only to the
//! [`RemoteHost`] port, so it behaves identically for a local, SSH or WSL host
//! (Liskov) and is fully testable with a mock host.

use std::sync::Arc;

use domain::ports::{EventBus, RemoteHost, RemotePath};
use domain::{DomainEvent, ProjectId, RemoteKind};

use crate::error::AppError;

/// Input for [`ConnectRemote::execute`].
#[derive(Clone)]
pub struct ConnectRemoteInput {
    /// The host strategy to connect through (built from the project's `RemoteRef`).
    pub host: Arc<dyn RemoteHost>,
    /// The project being connected (for the emitted event).
    pub project_id: ProjectId,
    /// Absolute root path to validate on the host.
    pub root: String,
}

/// Output of [`ConnectRemote::execute`].
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ConnectRemoteOutput {
    /// The kind of host that was connected.
    pub kind: RemoteKind,
}

/// Connects a project's remote host and checks its root is reachable.
pub struct ConnectRemote {
    events: Arc<dyn EventBus>,
}

impl ConnectRemote {
    /// Builds the use case from the [`EventBus`].
    #[must_use]
    pub fn new(events: Arc<dyn EventBus>) -> Self {
        Self { events }
    }

    /// Connects the host and validates the root exists, announcing
    /// [`DomainEvent::RemoteConnected`] on success.
    ///
    /// # Errors
    /// - [`AppError::Remote`] if the connection fails,
    /// - [`AppError::NotFound`] if the root does not exist on the host,
    /// - [`AppError::FileSystem`] on an I/O failure while probing the root.
    pub async fn execute(
        &self,
        input: ConnectRemoteInput,
    ) -> Result<ConnectRemoteOutput, AppError> {
        input.host.connect().await?;

        let fs = input.host.file_system();
        let exists = fs.exists(&RemotePath::new(input.root.clone())).await?;
        if !exists {
            return Err(AppError::NotFound(format!(
                "remote root {} is not reachable",
                input.root
            )));
        }

        self.events.publish(DomainEvent::RemoteConnected {
            project_id: input.project_id,
        });

        Ok(ConnectRemoteOutput {
            kind: input.host.kind(),
        })
    }
}
