//! Remote-host strategy adapters (ARCHITECTURE §5, L9).
//!
//! A [`RemoteHost`] is the **factory** that decides *where* a project's I/O
//! happens — it hands out the location-appropriate [`FileSystem`],
//! [`ProcessSpawner`] and [`PtyPort`]. Routing every use case through the
//! project's host is what makes local and remote execution transparent (Liskov,
//! ARCHITECTURE §1.2).
//!
//! This module ships [`LocalHost`] (the local strategy, fully wired and tested)
//! and the [`remote_host`] selector. The SSH and WSL strategies (`SshHost` via
//! russh/SFTP, `WslHost` via `wsl.exe`) are the remaining L9 work; their
//! integration tests are environment-gated (no SSH server / no WSL here), so they
//! are not yet wired in to avoid shipping unverified adapters. Until then the
//! selector reports them as unsupported rather than silently failing later.

use std::sync::Arc;

use async_trait::async_trait;

use domain::ports::{FileSystem, ProcessSpawner, PtyPort, RemoteError, RemoteHost};
use domain::remote::{RemoteKind, RemoteRef};

use crate::{LocalFileSystem, LocalProcessSpawner, PortablePtyAdapter};

/// The local execution strategy: the project lives on this machine, so the host
/// simply hands out the local adapters.
#[derive(Clone)]
pub struct LocalHost {
    fs: Arc<dyn FileSystem>,
    spawner: Arc<dyn ProcessSpawner>,
    pty: Arc<dyn PtyPort>,
}

impl LocalHost {
    /// Builds a local host wired to the local FS / process / PTY adapters.
    #[must_use]
    pub fn new() -> Self {
        Self {
            fs: Arc::new(LocalFileSystem::new()),
            spawner: Arc::new(LocalProcessSpawner::new()),
            pty: Arc::new(
                PortablePtyAdapter::new().with_sandbox_enforcer(crate::sandbox::default_enforcer()),
            ),
        }
    }
}

impl Default for LocalHost {
    fn default() -> Self {
        Self::new()
    }
}

#[async_trait]
impl RemoteHost for LocalHost {
    fn kind(&self) -> RemoteKind {
        RemoteKind::Local
    }

    async fn connect(&self) -> Result<(), RemoteError> {
        // Nothing to establish for the local machine.
        Ok(())
    }

    fn file_system(&self) -> Arc<dyn FileSystem> {
        Arc::clone(&self.fs)
    }

    fn process_spawner(&self) -> Arc<dyn ProcessSpawner> {
        Arc::clone(&self.spawner)
    }

    fn pty(&self) -> Arc<dyn PtyPort> {
        Arc::clone(&self.pty)
    }
}

/// Selects and builds the [`RemoteHost`] for a [`RemoteRef`].
///
/// `Local` yields a [`LocalHost`]. `Ssh`/`Wsl` are not yet wired (their adapters
/// are the remaining, environment-gated L9 work) and return a clear
/// [`RemoteError::Connection`] so callers fail fast with an actionable message
/// instead of a confusing downstream error.
///
/// # Errors
/// [`RemoteError::Connection`] for SSH/WSL remotes until their adapters land.
pub fn remote_host(remote: &RemoteRef) -> Result<Arc<dyn RemoteHost>, RemoteError> {
    match remote {
        RemoteRef::Local => Ok(Arc::new(LocalHost::new())),
        RemoteRef::Ssh { host, .. } => Err(RemoteError::Connection(format!(
            "SSH remote ({host}) is not yet supported"
        ))),
        RemoteRef::Wsl { distro } => Err(RemoteError::Connection(format!(
            "WSL remote ({distro}) is not yet supported"
        ))),
    }
}
