koi-net 0.4.0

Local network toolkit: service discovery, DNS, health monitoring, TLS proxy, and certificate mesh
//! Unified status command handler.
//!
//! Shows the status of all capabilities - connecting to a running daemon
//! if available, otherwise reporting offline status.

use koi_common::capability::CapabilityStatus;

use crate::cli::{Cli, Config};
use crate::client::KoiClient;
use crate::format;

pub fn status(cli: &Cli, config: &Config) -> anyhow::Result<()> {
    if let Some(status_json) = try_daemon_status(cli) {
        if cli.json {
            println!("{}", serde_json::to_string_pretty(&status_json)?);
        } else {
            print!("{}", format::unified_status(&status_json));
        }
        return Ok(());
    }

    // No daemon - report offline status
    let capabilities = offline_capabilities(config);

    let status = LocalStatus {
        version: env!("CARGO_PKG_VERSION").to_string(),
        platform: std::env::consts::OS.to_string(),
        daemon: false,
        capabilities,
    };

    if cli.json {
        println!("{}", serde_json::to_string_pretty(&status)?);
    } else {
        println!("Koi v{}", status.version);
        println!("  Platform:  {}", status.platform);
        println!("  Daemon:    not running");
        for cap in &status.capabilities {
            let marker = if cap.healthy { "+" } else { "-" };
            println!("  [{}] {}:  {}", marker, cap.name, cap.summary);
        }
    }

    Ok(())
}

#[derive(serde::Serialize)]
struct LocalStatus {
    version: String,
    platform: String,
    daemon: bool,
    capabilities: Vec<CapabilityStatus>,
}

/// Probe for a running daemon and return unified status JSON if reachable.
pub fn try_daemon_status(cli: &Cli) -> Option<serde_json::Value> {
    if cli.standalone {
        return None;
    }

    let endpoint = cli
        .endpoint
        .clone()
        .or_else(koi_config::breadcrumb::read_breadcrumb_endpoint)?;

    let client = KoiClient::new(&endpoint);
    if client.health().is_err() {
        return None;
    }

    match client.unified_status() {
        Ok(status_json) => Some(status_json),
        Err(e) => {
            tracing::debug!(error = %e, "Could not fetch unified status");
            None
        }
    }
}

fn offline_capabilities(config: &Config) -> Vec<CapabilityStatus> {
    let mut caps = Vec::new();

    if config.no_mdns {
        caps.push(CapabilityStatus {
            name: "mdns".to_string(),
            summary: "disabled".to_string(),
            healthy: false,
        });
    } else {
        caps.push(CapabilityStatus {
            name: "mdns".to_string(),
            summary: "not running".to_string(),
            healthy: false,
        });
    }

    if config.no_certmesh {
        caps.push(CapabilityStatus {
            name: "certmesh".to_string(),
            summary: "disabled".to_string(),
            healthy: false,
        });
    } else {
        let certmesh_summary = if koi_certmesh::CertmeshPaths::default().is_ca_initialized() {
            "CA initialized (daemon not running)".to_string()
        } else {
            "CA not initialized".to_string()
        };
        caps.push(CapabilityStatus {
            name: "certmesh".to_string(),
            summary: certmesh_summary,
            healthy: false,
        });
    }

    if config.no_dns {
        caps.push(CapabilityStatus {
            name: "dns".to_string(),
            summary: "disabled".to_string(),
            healthy: false,
        });
    } else {
        caps.push(CapabilityStatus {
            name: "dns".to_string(),
            summary: "not running".to_string(),
            healthy: false,
        });
    }

    if config.no_health {
        caps.push(CapabilityStatus {
            name: "health".to_string(),
            summary: "disabled".to_string(),
            healthy: false,
        });
    } else {
        caps.push(CapabilityStatus {
            name: "health".to_string(),
            summary: "not running".to_string(),
            healthy: false,
        });
    }

    if config.no_proxy {
        caps.push(CapabilityStatus {
            name: "proxy".to_string(),
            summary: "disabled".to_string(),
            healthy: false,
        });
    } else {
        caps.push(CapabilityStatus {
            name: "proxy".to_string(),
            summary: "not running".to_string(),
            healthy: false,
        });
    }

    if config.no_udp {
        caps.push(CapabilityStatus {
            name: "udp".to_string(),
            summary: "disabled".to_string(),
            healthy: false,
        });
    } else {
        caps.push(CapabilityStatus {
            name: "udp".to_string(),
            summary: "not running".to_string(),
            healthy: false,
        });
    }

    if config.no_runtime {
        caps.push(CapabilityStatus {
            name: "runtime".to_string(),
            summary: "disabled".to_string(),
            healthy: false,
        });
    } else {
        caps.push(CapabilityStatus {
            name: "runtime".to_string(),
            summary: "not running".to_string(),
            healthy: false,
        });
    }

    caps
}

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

    #[test]
    fn offline_all_enabled_shows_not_running() {
        let config = Config::default();
        let caps = offline_capabilities(&config);
        assert_eq!(caps[0].name, "mdns");
        assert!(!caps[0].healthy);
        assert!(
            caps[0].summary.contains("not running"),
            "mdns summary: {}",
            caps[0].summary
        );
        assert_eq!(caps[1].name, "certmesh");
        assert!(!caps[1].healthy);
        assert_eq!(caps[2].name, "dns");
        assert!(!caps[2].healthy);
        assert_eq!(caps[3].name, "health");
        assert!(!caps[3].healthy);
        assert_eq!(caps[4].name, "proxy");
        assert!(!caps[4].healthy);
        assert_eq!(caps[5].name, "udp");
        assert!(!caps[5].healthy);
    }

    #[test]
    fn offline_mdns_disabled() {
        let config = Config {
            no_mdns: true,
            ..Config::default()
        };
        let caps = offline_capabilities(&config);
        assert_eq!(caps[0].name, "mdns");
        assert_eq!(caps[0].summary, "disabled");
    }

    #[test]
    fn offline_certmesh_disabled() {
        let config = Config {
            no_certmesh: true,
            ..Config::default()
        };
        let caps = offline_capabilities(&config);
        assert_eq!(caps[1].name, "certmesh");
        assert_eq!(caps[1].summary, "disabled");
    }

    #[test]
    fn offline_dns_disabled() {
        let config = Config {
            no_dns: true,
            ..Config::default()
        };
        let caps = offline_capabilities(&config);
        assert_eq!(caps[2].name, "dns");
        assert_eq!(caps[2].summary, "disabled");
    }

    #[test]
    fn offline_health_disabled() {
        let config = Config {
            no_health: true,
            ..Config::default()
        };
        let caps = offline_capabilities(&config);
        assert_eq!(caps[3].name, "health");
        assert_eq!(caps[3].summary, "disabled");
    }

    #[test]
    fn offline_proxy_disabled() {
        let config = Config {
            no_proxy: true,
            ..Config::default()
        };
        let caps = offline_capabilities(&config);
        assert_eq!(caps[4].name, "proxy");
        assert_eq!(caps[4].summary, "disabled");
    }

    #[test]
    fn offline_udp_disabled() {
        let config = Config {
            no_udp: true,
            ..Config::default()
        };
        let caps = offline_capabilities(&config);
        assert_eq!(caps[5].name, "udp");
        assert_eq!(caps[5].summary, "disabled");
    }

    #[test]
    fn offline_both_disabled() {
        let config = Config {
            no_mdns: true,
            no_certmesh: true,
            ..Config::default()
        };
        let caps = offline_capabilities(&config);
        assert_eq!(caps[0].summary, "disabled");
        assert_eq!(caps[1].summary, "disabled");
    }

    #[test]
    fn offline_returns_seven_capabilities() {
        let config = Config::default();
        let caps = offline_capabilities(&config);
        assert_eq!(caps.len(), 7);
    }

    #[test]
    fn offline_runtime_disabled() {
        let config = Config {
            no_runtime: true,
            ..Config::default()
        };
        let caps = offline_capabilities(&config);
        assert_eq!(caps[6].name, "runtime");
        assert_eq!(caps[6].summary, "disabled");
    }
}