//! Per-component health model.
//!
//! Each component exposes `health() -> ComponentHealth`. `autopilot::health_server`
//! aggregates these into the `/health` JSON shape documented in
//! `_docs/02_document/deployment/containerization.md §7`.

use serde::Serialize;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum HealthLevel {
    Green,
    Yellow,
    Red,
    Disabled,
}

#[derive(Debug, Clone, Serialize)]
pub struct ComponentHealth {
    pub level: HealthLevel,
    pub component: &'static str,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub detail: Option<String>,
}

impl ComponentHealth {
    pub fn green(component: &'static str) -> Self {
        Self {
            level: HealthLevel::Green,
            component,
            detail: None,
        }
    }

    pub fn yellow(component: &'static str, detail: impl Into<String>) -> Self {
        Self {
            level: HealthLevel::Yellow,
            component,
            detail: Some(detail.into()),
        }
    }

    pub fn red(component: &'static str, detail: impl Into<String>) -> Self {
        Self {
            level: HealthLevel::Red,
            component,
            detail: Some(detail.into()),
        }
    }

    pub fn disabled(component: &'static str) -> Self {
        Self {
            level: HealthLevel::Disabled,
            component,
            detail: None,
        }
    }
}

#[derive(Debug, Clone, Serialize)]
pub struct AggregatedHealth {
    pub status: HealthLevel,
    pub components: Vec<ComponentHealth>,
    pub last_state_change: chrono::DateTime<chrono::Utc>,
}

impl AggregatedHealth {
    /// Aggregate per-component readings into a single status.
    ///
    /// A component in `Disabled` does not affect aggregation. Otherwise:
    /// any `Red` → `Red`; else any `Yellow` → `Yellow`; else `Green`.
    pub fn aggregate(components: Vec<ComponentHealth>) -> Self {
        let mut status = HealthLevel::Green;
        for c in &components {
            match c.level {
                HealthLevel::Red => {
                    status = HealthLevel::Red;
                    break;
                }
                HealthLevel::Yellow if status != HealthLevel::Red => {
                    status = HealthLevel::Yellow;
                }
                _ => {}
            }
        }
        Self {
            status,
            components,
            last_state_change: chrono::Utc::now(),
        }
    }
}

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

    #[test]
    fn aggregate_red_dominates() {
        // Arrange
        let inputs = vec![
            ComponentHealth::green("a"),
            ComponentHealth::yellow("b", "lagging"),
            ComponentHealth::red("c", "down"),
        ];

        // Act
        let agg = AggregatedHealth::aggregate(inputs);

        // Assert
        assert_eq!(agg.status, HealthLevel::Red);
    }

    #[test]
    fn aggregate_yellow_when_no_red() {
        // Arrange
        let inputs = vec![
            ComponentHealth::green("a"),
            ComponentHealth::yellow("b", "lagging"),
        ];

        // Act
        let agg = AggregatedHealth::aggregate(inputs);

        // Assert
        assert_eq!(agg.status, HealthLevel::Yellow);
    }

    #[test]
    fn aggregate_green_when_all_green_or_disabled() {
        // Arrange
        let inputs = vec![
            ComponentHealth::green("a"),
            ComponentHealth::disabled("vlm"),
        ];

        // Act
        let agg = AggregatedHealth::aggregate(inputs);

        // Assert
        assert_eq!(agg.status, HealthLevel::Green);
    }
}
