//! `ToolError`-mapped wrapper over `platform_clients::machines::MachinesClient`.
//!
//! Re-exports the request/response types tools need so they don't have to
//! depend on `service-clients` directly.

use crate::protocol::ToolError;
use platform_clients::machines::{
    ExecuteCommandRequest, ExecuteCommandResponse, MachineInfo, MachinePoolStatus, MachinesClient,
};
use std::sync::Arc;

pub use platform_clients::machines::{
    ExecuteCommandRequest as SshRequest, ExecuteCommandResponse as SshResponse,
    MachineInfo as SshMachineInfo, RetryPolicy as SshRetryPolicy,
};

fn map_err(e: platform_clients::error::ServiceError) -> ToolError {
    if e.is_not_found() {
        return ToolError::not_found(e.to_string());
    }
    ToolError::invocation(e.to_string())
}

/// Shareable SSH source backed by `MachinesClient`.
#[derive(Debug, Clone)]
pub struct SshSource {
    inner: Arc<MachinesClient>,
}

impl SshSource {
    /// `base_url` — e.g. `"http://localhost:9013"` (machines service HTTP API).
    pub fn new(base_url: impl Into<String>) -> Result<Self, ToolError> {
        let client = MachinesClient::new(base_url)
            .map_err(|e| ToolError::invocation(format!("ssh source init: {e}")))?;
        Ok(Self { inner: Arc::new(client) })
    }

    /// Execute `command` on `host` with no retry.
    pub async fn execute(
        &self,
        host: impl Into<String>,
        command: impl Into<String>,
    ) -> Result<ExecuteCommandResponse, ToolError> {
        self.inner.execute_command(host, command).await.map_err(map_err)
    }

    /// Execute with explicit retry policy.
    pub async fn execute_with_retry(
        &self,
        request: ExecuteCommandRequest,
    ) -> Result<ExecuteCommandResponse, ToolError> {
        self.inner.execute_command_with_retry(request).await.map_err(map_err)
    }

    /// List all registered machines.
    pub async fn list_machines(&self) -> Result<Vec<MachineInfo>, ToolError> {
        self.inner.list_machines().await.map_err(map_err)
    }

    /// Get a single machine by name.
    pub async fn get_machine(&self, name: &str) -> Result<MachineInfo, ToolError> {
        self.inner.get_machine(name).await.map_err(map_err)
    }

    /// Machine pool status summary.
    pub async fn pool_status(&self) -> Result<MachinePoolStatus, ToolError> {
        self.inner.pool_status().await.map_err(map_err)
    }

    /// Returns `Ok(true)` when the machines service is reachable.
    pub async fn health(&self) -> Result<bool, ToolError> {
        self.inner.health_check().await.map_err(map_err)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[tokio::test]
    async fn unreachable_service_returns_invocation_error() {
        let src = SshSource::new("http://127.0.0.1:1").unwrap();
        let err = src.health().await.unwrap_err();
        assert!(!matches!(err, ToolError::NotFound(_)));
    }
}
