//! Live probes for workspace component health.
//!
//! Shared by the preconditions gate (abortive) and the `health` CLI operation (read-only).
//! The gate calls `check_component` and aborts on `Failed` status.
//! The `health` operation calls `check_component` and displays the `ProbeReport`.

pub mod registry;

use std::time::{Duration, Instant};

use chrono::Utc;
use serde::{Deserialize, Serialize};
use tracing::debug;

use crate::ssh_exec::{run_ssh_command, SshOutput};

/// Liveness status returned by a probe.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum LiveStatus {
    Healthy,
    Degraded,
    Failed,
}

impl std::fmt::Display for LiveStatus {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            LiveStatus::Healthy => write!(f, "healthy"),
            LiveStatus::Degraded => write!(f, "degraded"),
            LiveStatus::Failed => write!(f, "FAILED"),
        }
    }
}

/// Full result of a live probe — read-only, suitable for display in `health` output.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProbeReport {
    pub component: String,
    pub mode: String,
    pub target: String,
    pub live: LiveStatus,
    pub detail: String,
    pub latency_ms: u64,
    pub probed_at: chrono::DateTime<chrono::Utc>,
}

/// Configuration passed to a probe.
pub struct ProbeConfig {
    /// SSH user for the target host.
    pub ssh_user: String,
    /// Path to the SSH identity file (optional).
    pub ssh_key_path: Option<String>,
    /// Per-probe timeout.
    pub timeout: Duration,
    /// Kubernetes namespace (only used for cluster-mode probes).
    pub namespace: Option<String>,
}

impl Default for ProbeConfig {
    fn default() -> Self {
        Self {
            ssh_user: "root".to_string(),
            ssh_key_path: None,
            timeout: Duration::from_secs(10),
            namespace: None,
        }
    }
}

/// Run a live probe against a component and return a `ProbeReport`.
///
/// The probe command depends on the component's mode:
/// - `taskserv`: `systemctl is-active <service>` (from `registry::taskserv_probe_command`)
/// - `cluster`:  `k0s kubectl get deploy` (from `registry::cluster_probe_command`)
/// - `container`: `docker inspect` (from `registry::container_probe_command`)
pub async fn check_component(
    component: &str,
    mode: &str,
    target: &str,
    config: &ProbeConfig,
) -> ProbeReport {
    let start = Instant::now();
    let key_ref = config.ssh_key_path.as_deref();

    let probe_cmd = match mode {
        "taskserv" => registry::taskserv_probe_command(component)
            .unwrap_or_else(|| format!("systemctl is-active {}", component.replace('_', "-"))),
        "cluster" => {
            let ns = config.namespace.as_deref().unwrap_or("default");
            registry::cluster_probe_command(component, ns)
        }
        "container" => registry::container_probe_command(component),
        other => {
            return ProbeReport {
                component: component.to_string(),
                mode: other.to_string(),
                target: target.to_string(),
                live: LiveStatus::Degraded,
                detail: format!("unknown mode '{other}' — cannot probe"),
                latency_ms: 0,
                probed_at: Utc::now(),
            };
        }
    };

    debug!("probing {}@{} (mode={}): {}", component, target, mode, probe_cmd);

    let result: Result<SshOutput, _> = run_ssh_command(
        target,
        &config.ssh_user,
        key_ref,
        &probe_cmd,
        config.timeout,
    )
    .await;

    let latency_ms = start.elapsed().as_millis() as u64;

    match result {
        Err(e) => ProbeReport {
            component: component.to_string(),
            mode: mode.to_string(),
            target: target.to_string(),
            live: LiveStatus::Failed,
            detail: format!("SSH error: {e}"),
            latency_ms,
            probed_at: Utc::now(),
        },
        Ok(out) => {
            let (live, detail) = if out.succeeded() {
                (LiveStatus::Healthy, out.stdout.trim().to_string())
            } else {
                (
                    LiveStatus::Failed,
                    format!(
                        "exit={} stdout={} stderr={}",
                        out.exit_code,
                        out.stdout.trim(),
                        out.stderr.trim()
                    ),
                )
            };
            ProbeReport {
                component: component.to_string(),
                mode: mode.to_string(),
                target: target.to_string(),
                live,
                detail,
                latency_ms,
                probed_at: Utc::now(),
            }
        }
    }
}
