//! Thin SSH command executor for the preconditions gate.
//!
//! Shells out to the system `ssh` binary via `tokio::process::Command`.
//! Uses BatchMode=yes (no interactive prompts) and a configurable timeout.

use std::time::Duration;

use anyhow::{Context, Result};
use tokio::process::Command;

/// Output from a single SSH command.
#[derive(Debug, Clone)]
pub struct SshOutput {
    pub exit_code: i32,
    pub stdout: String,
    pub stderr: String,
}

impl SshOutput {
    pub fn succeeded(&self) -> bool {
        self.exit_code == 0
    }
}

/// Execute a single command on a remote host via the system `ssh` binary.
///
/// Connects with `BatchMode=yes` (no password prompts) and `StrictHostKeyChecking=no`
/// (suitable for known internal infrastructure). The optional `key_path` is passed as `-i`.
pub async fn run_ssh_command(
    host: &str,
    user: &str,
    key_path: Option<&str>,
    command: &str,
    timeout: Duration,
) -> Result<SshOutput> {
    let mut cmd = Command::new("ssh");
    cmd.arg("-o")
        .arg("BatchMode=yes")
        .arg("-o")
        .arg("StrictHostKeyChecking=no")
        .arg("-o")
        .arg("UserKnownHostsFile=/dev/null")
        .arg("-o")
        .arg(format!("ConnectTimeout={}", timeout.as_secs()));

    if let Some(key) = key_path {
        cmd.arg("-i").arg(key);
    }

    cmd.arg(format!("{user}@{host}")).arg(command);

    let output = tokio::time::timeout(timeout + Duration::from_secs(2), cmd.output())
        .await
        .context("SSH command timed out")?
        .context("Failed to spawn ssh process")?;

    Ok(SshOutput {
        exit_code: output.status.code().unwrap_or(-1),
        stdout: String::from_utf8_lossy(&output.stdout).into_owned(),
        stderr: String::from_utf8_lossy(&output.stderr).into_owned(),
    })
}
