#![deny(warnings)]
#![allow(dead_code)]
//! [TSM.ID].[11031972] -- Platform X Ecosystem
//! xcu-panopticon -- All-Seeing System Monitor
//! Cross-node metrics aggregation, dashboarding, real-time health

use std::collections::HashMap;
use std::sync::{Arc, Mutex};

#[derive(Debug)]
pub enum PanopticonError {
    NodeUnreachable(String),
    MetricNotFound(String),
    AggregationFailed(String),
}

impl std::fmt::Display for PanopticonError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::NodeUnreachable(e) => write!(f, "Node unreachable: {e}"),
            Self::MetricNotFound(e) => write!(f, "Metric not found: {e}"),
            Self::AggregationFailed(e) => write!(f, "Aggregation failed: {e}"),
        }
    }
}

impl std::error::Error for PanopticonError {}

#[derive(Debug, Clone)]
pub struct NodeHealth {
    pub node_id: String,
    pub is_alive: bool,
    pub cpu_percent: f64,
    pub memory_percent: f64,
    pub disk_percent: f64,
    pub active_connections: u32,
    pub request_per_sec: f64,
    pub error_rate: f64,
    pub latency_p50_ms: f64,
    pub latency_p99_ms: f64,
    pub last_heartbeat: u64,
}

#[derive(Debug, Clone)]
pub struct ClusterHealth {
    pub total_nodes: usize,
    pub alive_nodes: usize,
    pub avg_cpu: f64,
    pub avg_memory: f64,
    pub total_rps: f64,
    pub avg_latency_p50: f64,
    pub avg_latency_p99: f64,
    pub worst_node: Option<String>,
    pub overall_status: HealthStatus,
}

#[derive(Debug, Clone)]
pub enum HealthStatus { Healthy, Degraded, Critical, Down }

#[derive(Debug, Clone)]
pub struct MetricPoint {
    pub value: f64,
    pub timestamp: u64,
}

pub struct Panopticon {
    nodes: Arc<Mutex<HashMap<String, NodeHealth>>>,
    metrics_history: Arc<Mutex<HashMap<String, Vec<MetricPoint>>>>,
    max_history_per_metric: usize,
}

impl Panopticon {
    pub fn new(max_history: usize) -> Self {
        Self {
            nodes: Arc::new(Mutex::new(HashMap::new())),
            metrics_history: Arc::new(Mutex::new(HashMap::new())),
            max_history_per_metric: max_history,
        }
    }

    /// Register or update node health
    pub fn report_health(&self, health: NodeHealth) -> Result<(), PanopticonError> {
        let node_id = health.node_id.clone();
        // Store metric history
        if let Ok(mut hist) = self.metrics_history.lock() {
            let key = format!("{}.cpu", node_id);
            let entry = hist.entry(key).or_insert_with(Vec::new);
            entry.push(MetricPoint { value: health.cpu_percent, timestamp: health.last_heartbeat });
            if entry.len() > self.max_history_per_metric {
                entry.remove(0);
            }

            let key = format!("{}.rps", node_id);
            let entry = hist.entry(key).or_insert_with(Vec::new);
            entry.push(MetricPoint { value: health.request_per_sec, timestamp: health.last_heartbeat });
            if entry.len() > self.max_history_per_metric {
                entry.remove(0);
            }
        }

        if let Ok(mut nodes) = self.nodes.lock() {
            nodes.insert(node_id, health);
        }
        Ok(())
    }

    /// Calculate cluster-wide health
    pub fn cluster_health(&self) -> Result<ClusterHealth, PanopticonError> {
        let nodes = self.nodes.lock()
            .map_err(|_| PanopticonError::AggregationFailed("Lock poisoned".into()))?;

        if nodes.is_empty() {
            return Ok(ClusterHealth {
                total_nodes: 0, alive_nodes: 0, avg_cpu: 0.0, avg_memory: 0.0,
                total_rps: 0.0, avg_latency_p50: 0.0, avg_latency_p99: 0.0,
                worst_node: None, overall_status: HealthStatus::Down,
            });
        }

        let total = nodes.len();
        let alive: Vec<&NodeHealth> = nodes.values().filter(|n| n.is_alive).collect();
        let alive_count = alive.len();

        let (sum_cpu, sum_mem, sum_rps, sum_p50, sum_p99) = alive.iter().fold(
            (0.0, 0.0, 0.0, 0.0, 0.0),
            |(c, m, r, p5, p9), n| {
                (c + n.cpu_percent, m + n.memory_percent, r + n.request_per_sec,
                 p5 + n.latency_p50_ms, p9 + n.latency_p99_ms)
            },
        );

        let alive_f = if alive_count > 0 { alive_count as f64 } else { 1.0 };

        // Find worst node (highest CPU + error rate)
        let worst = alive.iter()
            .max_by(|a, b| {
                let score_a = a.cpu_percent + a.error_rate * 100.0;
                let score_b = b.cpu_percent + b.error_rate * 100.0;
                score_a.partial_cmp(&score_b).unwrap_or(std::cmp::Ordering::Equal)
            })
            .map(|n| n.node_id.clone());

        let status = if alive_count == 0 {
            HealthStatus::Down
        } else if alive_count < total {
            HealthStatus::Critical
        } else if sum_cpu / alive_f > 80.0 {
            HealthStatus::Degraded
        } else {
            HealthStatus::Healthy
        };

        Ok(ClusterHealth {
            total_nodes: total,
            alive_nodes: alive_count,
            avg_cpu: sum_cpu / alive_f,
            avg_memory: sum_mem / alive_f,
            total_rps: sum_rps,
            avg_latency_p50: sum_p50 / alive_f,
            avg_latency_p99: sum_p99 / alive_f,
            worst_node: worst,
            overall_status: status,
        })
    }

    /// Percentile calculation from metric history
    pub fn percentile(&self, metric_key: &str, pct: f64) -> Result<f64, PanopticonError> {
        let hist = self.metrics_history.lock()
            .map_err(|_| PanopticonError::AggregationFailed("Lock".into()))?;
        let points = hist.get(metric_key)
            .ok_or_else(|| PanopticonError::MetricNotFound(metric_key.into()))?;
        if points.is_empty() {
            return Ok(0.0);
        }
        let mut values: Vec<f64> = points.iter().map(|p| p.value).collect();
        values.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
        let idx = ((pct / 100.0) * (values.len() - 1) as f64) as usize;
        Ok(values[idx.min(values.len() - 1)])
    }

    pub fn node_count(&self) -> usize {
        self.nodes.lock().map(|n| n.len()).unwrap_or(0)
    }
}

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

    fn make_node(id: &str, cpu: f64, alive: bool) -> NodeHealth {
        NodeHealth {
            node_id: id.into(), is_alive: alive, cpu_percent: cpu,
            memory_percent: 50.0, disk_percent: 40.0, active_connections: 100,
            request_per_sec: 500.0, error_rate: 0.01, latency_p50_ms: 5.0,
            latency_p99_ms: 50.0, last_heartbeat: 1000,
        }
    }

    #[test]
    fn test_cluster_healthy() {
        let p = Panopticon::new(100);
        p.report_health(make_node("alpha", 30.0, true)).unwrap();
        p.report_health(make_node("beta", 40.0, true)).unwrap();
        p.report_health(make_node("gamma", 35.0, true)).unwrap();
        let health = p.cluster_health().unwrap();
        assert_eq!(health.alive_nodes, 3);
        assert!(matches!(health.overall_status, HealthStatus::Healthy));
    }

    #[test]
    fn test_node_down_critical() {
        let p = Panopticon::new(100);
        p.report_health(make_node("alpha", 30.0, true)).unwrap();
        p.report_health(make_node("beta", 40.0, false)).unwrap();
        let health = p.cluster_health().unwrap();
        assert!(matches!(health.overall_status, HealthStatus::Critical));
    }
}
