rmpca 0.2.0

Enterprise-grade unified CLI for rmp.ca operations - Rust port
//! Status command: Show health/status of rmpca jails and services
//!
//! This command checks the status of all rmpca jails and services,
//! optionally performing HTTP health checks.

use crate::config::Config;
use anyhow::{Context, Result};
use clap::Args as ClapArgs;
use serde::Serialize;
use std::collections::HashMap;

#[derive(Debug, ClapArgs)]
pub struct Args {
    /// Ping HTTP health/readiness endpoints
    #[arg(long)]
    health: bool,

    /// Filter to a specific jail
    #[arg(long)]
    jail: Option<String>,

    /// Output as JSON
    #[arg(long)]
    json: bool,

    /// Minimal output (for scripting)
    #[arg(short, long)]
    quiet: bool,
}

#[derive(Debug, Serialize)]
struct JailStatus {
    jail: String,
    service: String,
    jail_status: String,
    service_status: String,
    address: String,
    health: String,
}

/// Check if a jail is running by trying to reach it
async fn check_jail_status(client: &reqwest::Client, ip: &str, port: Option<u16>) -> String {
    // Try to connect to the jail's IP/port
    if let Some(p) = port {
        let addr = format!("{}:{}", ip, p);
        // Try a TCP-like check via HTTP
        let url = format!("http://{}", addr);
        match client.get(&url).timeout(std::time::Duration::from_secs(3)).send().await {
            Ok(_) => "up".to_string(),
            Err(e) => {
                if e.is_connect() || e.is_timeout() {
                    "down".to_string()
                } else {
                    "unknown".to_string()
                }
            }
        }
    } else {
        // No port specified, try ping-like check
        "unknown".to_string()
    }
}

/// Check if a service is running by hitting its health endpoint
async fn check_service_status(
    client: &reqwest::Client,
    ip: &str,
    port: u16,
    health_path: &str,
) -> (String, String) {
    let url = format!("http://{}:{}{}", ip, port, health_path);
    match client.get(&url).timeout(std::time::Duration::from_secs(5)).send().await {
        Ok(resp) => {
            let status = if resp.status().is_success() {
                "running".to_string()
            } else {
                format!("error ({})", resp.status())
            };
            let health = if resp.status().is_success() {
                format!("ok ({})", resp.status())
            } else {
                format!("fail ({})", resp.status())
            };
            (status, health)
        }
        Err(e) => {
            let status = if e.is_connect() {
                "stopped".to_string()
            } else if e.is_timeout() {
                "timeout".to_string()
            } else {
                "unknown".to_string()
            };
            let health = if e.is_connect() {
                "fail (connection refused)".to_string()
            } else if e.is_timeout() {
                "fail (timeout)".to_string()
            } else {
                format!("fail ({})", e)
            };
            (status, health)
        }
    }
}

/// Show health/status of all rmpca jails and services
pub async fn run(args: Args) -> Result<()> {
    let config = Config::load().unwrap_or_default();
    config.init_logging();

    tracing::info!("Checking jail status");

    let client = reqwest::Client::builder()
        .timeout(std::time::Duration::from_secs(10))
        .build()?;

    // Define jails and services
    let jails = vec![
        ("rmpca-extract", "extract", "10.10.0.2", Some(4000), Some("/")),
        ("rmpca-backend", "backend", "10.10.0.3", Some(3000), Some("/health")),
        ("rmpca-optimizer", "optimizer", "10.10.0.5", Some(8000), Some("/health")),
        ("rmpca-nginx-opt", "nginx", "10.10.0.7", Some(80), Some("/health")),
    ];

    let mut results = Vec::new();

    for (jail, service, ip, port, health_path) in jails {
        // Apply filter
        if let Some(ref filter) = args.jail {
            if jail != filter && service != filter {
                continue;
            }
        }

        let address = match port {
            Some(p) => format!("{}:{}", ip, p),
            None => ip.to_string(),
        };

        // Check jail status by trying to connect
        let jail_status = check_jail_status(&client, ip, port).await;

        // Health check (HTTP)
        let (service_status, health_status) = if args.health && port.is_some() && health_path.is_some() {
            check_service_status(&client, ip, port.unwrap(), health_path.unwrap()).await
        } else if port.is_some() {
            // Without --health, just check if port is reachable
            match check_jail_status(&client, ip, port).await.as_str() {
                "up" => ("running".to_string(), "".to_string()),
                "down" => ("stopped".to_string(), "".to_string()),
                _ => ("unknown".to_string(), "".to_string()),
            }
        } else {
            ("unknown".to_string(), "".to_string())
        };

        results.push(JailStatus {
            jail: jail.to_string(),
            service: service.to_string(),
            jail_status,
            service_status,
            address,
            health: health_status,
        });
    }

    // Output
    if args.json {
        println!("{}", serde_json::to_string_pretty(&results)?);
    } else if args.quiet {
        for r in &results {
            println!("{} {} {}", r.jail, r.jail_status, r.service_status);
        }
    } else {
        println!("{:<24} {:<10} {:<10} {:<14} {}",
            "JAIL", "JAIL", "SERVICE", "IP:PORT", "HEALTH");
        println!("{}", "-".repeat(78));
        for r in &results {
            println!("{:<24} {:<10} {:<10} {:<14} {}",
                r.jail, r.jail_status, r.service_status, r.address, r.health);
        }
    }

    Ok(())
}

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

    #[test]
    fn test_status_args() {
        let args = Args {
            health: true,
            jail: Some("rmpca-backend".to_string()),
            json: false,
            quiet: true,
        };
        assert!(args.health);
        assert_eq!(args.jail, Some("rmpca-backend".to_string()));
        assert!(args.quiet);
    }
}