// {{ display_name }} MCP server — generated by mcpify. Do not hand-edit.

use std::collections::HashMap;
use std::time::SystemTime;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ComponentStatus {
    Healthy,
    Degraded,
    Unhealthy,
}

#[derive(Debug, Clone)]
pub struct ComponentState {
    pub name: String,
    pub critical: bool,
    pub status: ComponentStatus,
    pub last_checked_at: Option<SystemTime>,
    pub last_error: Option<String>,
}

/// Tracks the health of every registered component, distinguishing critical
/// components (a failure here makes the whole server unhealthy) from
/// optional ones (a failure only degrades it) — REQ-2.3.4.
#[derive(Debug, Default)]
pub struct ComponentRegistry {
    components: HashMap<String, ComponentState>,
}

impl ComponentRegistry {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn register(&mut self, name: &str, critical: bool) {
        self.components.insert(
            name.to_string(),
            ComponentState {
                name: name.to_string(),
                critical,
                status: ComponentStatus::Healthy,
                last_checked_at: None,
                last_error: None,
            },
        );
    }

    pub fn report(&mut self, name: &str, status: ComponentStatus, error: Option<String>) {
        let critical = self
            .components
            .get(name)
            .map(|existing| existing.critical)
            .unwrap_or(false);
        self.components.insert(
            name.to_string(),
            ComponentState {
                name: name.to_string(),
                critical,
                status,
                last_checked_at: Some(SystemTime::now()),
                last_error: error,
            },
        );
    }

    pub fn overall_status(&self) -> ComponentStatus {
        let states: Vec<&ComponentState> = self.components.values().collect();
        if states
            .iter()
            .any(|c| c.critical && c.status == ComponentStatus::Unhealthy)
        {
            return ComponentStatus::Unhealthy;
        }
        if states.iter().any(|c| c.status != ComponentStatus::Healthy) {
            return ComponentStatus::Degraded;
        }
        ComponentStatus::Healthy
    }

    pub fn snapshot(&self) -> Vec<ComponentState> {
        self.components.values().cloned().collect()
    }
}

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

    #[test]
    fn newly_registered_components_start_healthy() {
        let mut registry = ComponentRegistry::new();
        registry.register("db", true);
        assert_eq!(registry.overall_status(), ComponentStatus::Healthy);
    }

    #[test]
    fn a_critical_component_failure_makes_the_whole_registry_unhealthy() {
        let mut registry = ComponentRegistry::new();
        registry.register("db", true);
        registry.report(
            "db",
            ComponentStatus::Unhealthy,
            Some("timeout".to_string()),
        );
        assert_eq!(registry.overall_status(), ComponentStatus::Unhealthy);
    }

    #[test]
    fn a_non_critical_component_failure_only_degrades_the_registry() {
        let mut registry = ComponentRegistry::new();
        registry.register("cache", false);
        registry.report("cache", ComponentStatus::Unhealthy, None);
        assert_eq!(registry.overall_status(), ComponentStatus::Degraded);
    }
}
