//! Monitoring dashboard widget for real-time system metrics
//!
//! NOTE: This module uses numeric casts from f64/usize to smaller types.
//! These casts are safe because the values are constrained to valid ranges
//! (percentages 0-100, UI dimensions, relevance scores 0-255).

#![allow(
    clippy::cast_precision_loss,  // usize → f64 for division is necessary
    clippy::cast_possible_truncation,  // f64 → u16/u8 is safe (values are 0-100)
    clippy::cast_sign_loss,  // Not applicable, but included for completeness
)]

use serde::{Deserialize, Serialize};
use std::time::{SystemTime, UNIX_EPOCH};

/// Performance metric snapshot
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Metric {
    /// Metric name (e.g., "memory", "cpu", "requests")
    pub name: String,
    /// Current value
    pub value: f64,
    /// Maximum threshold
    pub max: f64,
    /// Unit (e.g., "MB", "%", "req/s")
    pub unit: String,
    /// Timestamp of measurement
    pub timestamp: u64,
}

impl Metric {
    /// Create new metric
    #[must_use]
    pub fn new(name: String, value: f64, max: f64, unit: String) -> Self {
        Self {
            name,
            value,
            max,
            unit,
            timestamp: SystemTime::now()
                .duration_since(UNIX_EPOCH)
                .unwrap_or_default()
                .as_secs(),
        }
    }

    /// Get percentage of max (0-100)
    #[must_use]
    pub fn percentage(&self) -> u16 {
        // SAFETY: Result is always 0-100, safe to cast to u16
        ((self.value / self.max) * 100.0) as u16
    }

    /// Check if metric exceeds threshold
    #[must_use]
    pub fn is_warning(&self) -> bool {
        self.value > self.max * 0.75
    }

    /// Check if metric is critical
    #[must_use]
    pub fn is_critical(&self) -> bool {
        self.value > self.max * 0.9
    }
}

/// Monitoring dashboard state
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct MonitoringDashboard {
    /// List of metrics
    metrics: Vec<Metric>,
    /// Current selected metric index
    selected: usize,
    /// Whether to auto-refresh
    auto_refresh: bool,
    /// Refresh interval in seconds
    refresh_interval: u64,
}

impl MonitoringDashboard {
    /// Create new monitoring dashboard
    #[must_use]
    pub const fn new() -> Self {
        Self {
            metrics: Vec::new(),
            selected: 0,
            auto_refresh: true,
            refresh_interval: 1,
        }
    }

    /// Add metric to dashboard
    pub fn add_metric(&mut self, metric: Metric) {
        self.metrics.push(metric);
    }

    /// Update metric by name
    pub fn update_metric(&mut self, name: &str, value: f64) -> bool {
        if let Some(metric) = self.metrics.iter_mut().find(|m| m.name == name) {
            metric.value = value;
            metric.timestamp = SystemTime::now()
                .duration_since(UNIX_EPOCH)
                .unwrap_or_default()
                .as_secs();
            return true;
        }
        false
    }

    /// Get all metrics
    #[must_use]
    pub fn metrics(&self) -> &[Metric] {
        &self.metrics
    }

    /// Get selected metric
    #[must_use]
    pub fn selected_metric(&self) -> Option<&Metric> {
        self.metrics.get(self.selected)
    }

    /// Select next metric
    pub const fn select_next(&mut self) {
        if !self.metrics.is_empty() {
            self.selected = (self.selected + 1) % self.metrics.len();
        }
    }

    /// Select previous metric
    pub const fn select_previous(&mut self) {
        if !self.metrics.is_empty() {
            self.selected = if self.selected == 0 {
                self.metrics.len() - 1
            } else {
                self.selected - 1
            };
        }
    }

    /// Toggle auto-refresh
    pub const fn toggle_auto_refresh(&mut self) {
        self.auto_refresh = !self.auto_refresh;
    }

    /// Get overall system health (0-100)
    #[must_use]
    pub fn system_health(&self) -> u16 {
        if self.metrics.is_empty() {
            return 100;
        }

        let avg_percentage: f64 = self
            .metrics
            .iter()
            .map(|m| f64::from(m.percentage()))
            .sum::<f64>()
            // SAFETY: usize to f64 conversion needed for division
            / self.metrics.len() as f64;
        // SAFETY: Result of (100.0 - avg) is always 0-100, safe to cast to u16
        (100.0 - avg_percentage) as u16
    }

    /// Count critical metrics
    #[must_use]
    pub fn critical_count(&self) -> usize {
        self.metrics.iter().filter(|m| m.is_critical()).count()
    }

    /// Count warning metrics
    #[must_use]
    pub fn warning_count(&self) -> usize {
        self.metrics
            .iter()
            .filter(|m| m.is_warning() && !m.is_critical())
            .count()
    }
}

impl Default for MonitoringDashboard {
    fn default() -> Self {
        Self::new()
    }
}

#[cfg(test)]
mod tests {
    #![allow(clippy::float_cmp)] // Safe: comparing exact literal values in tests

    use super::*;

    #[test]
    fn test_metric_new() {
        let metric = Metric::new("memory".to_string(), 512.0, 1024.0, "MB".to_string());
        assert_eq!(metric.name, "memory");
        assert_eq!(metric.value, 512.0);
        assert_eq!(metric.max, 1024.0);
    }

    #[test]
    fn test_metric_percentage() {
        let metric = Metric::new("cpu".to_string(), 50.0, 100.0, "%".to_string());
        assert_eq!(metric.percentage(), 50);
    }

    #[test]
    fn test_metric_warning_threshold() {
        let metric = Metric::new("memory".to_string(), 800.0, 1000.0, "MB".to_string());
        assert!(metric.is_warning());
        assert!(!metric.is_critical());
    }

    #[test]
    fn test_metric_critical_threshold() {
        let metric = Metric::new("memory".to_string(), 950.0, 1000.0, "MB".to_string());
        assert!(metric.is_critical());
        assert!(metric.is_warning());
    }

    #[test]
    fn test_monitoring_dashboard_new() {
        let dashboard = MonitoringDashboard::new();
        assert!(dashboard.metrics.is_empty());
        assert!(dashboard.auto_refresh);
        assert_eq!(dashboard.refresh_interval, 1);
    }

    #[test]
    fn test_add_metric() {
        let mut dashboard = MonitoringDashboard::new();
        let metric = Metric::new("cpu".to_string(), 30.0, 100.0, "%".to_string());
        dashboard.add_metric(metric);

        assert_eq!(dashboard.metrics.len(), 1);
    }

    #[test]
    fn test_update_metric() {
        let mut dashboard = MonitoringDashboard::new();
        let metric = Metric::new("memory".to_string(), 512.0, 1024.0, "MB".to_string());
        dashboard.add_metric(metric);

        assert!(dashboard.update_metric("memory", 768.0));
        assert_eq!(dashboard.metrics[0].value, 768.0);
    }

    #[test]
    fn test_update_nonexistent_metric() {
        let mut dashboard = MonitoringDashboard::new();
        assert!(!dashboard.update_metric("nonexistent", 100.0));
    }

    #[test]
    fn test_select_next() {
        let mut dashboard = MonitoringDashboard::new();
        dashboard.add_metric(Metric::new("cpu".to_string(), 10.0, 100.0, "%".to_string()));
        dashboard.add_metric(Metric::new(
            "memory".to_string(),
            20.0,
            100.0,
            "%".to_string(),
        ));

        assert_eq!(dashboard.selected, 0);
        dashboard.select_next();
        assert_eq!(dashboard.selected, 1);
    }

    #[test]
    fn test_select_previous() {
        let mut dashboard = MonitoringDashboard::new();
        dashboard.add_metric(Metric::new("cpu".to_string(), 10.0, 100.0, "%".to_string()));
        dashboard.add_metric(Metric::new(
            "memory".to_string(),
            20.0,
            100.0,
            "%".to_string(),
        ));
        dashboard.selected = 1;

        dashboard.select_previous();
        assert_eq!(dashboard.selected, 0);
    }

    #[test]
    fn test_system_health() {
        let mut dashboard = MonitoringDashboard::new();
        dashboard.add_metric(Metric::new("cpu".to_string(), 50.0, 100.0, "%".to_string()));
        dashboard.add_metric(Metric::new(
            "memory".to_string(),
            50.0,
            100.0,
            "%".to_string(),
        ));

        assert_eq!(dashboard.system_health(), 50);
    }

    #[test]
    fn test_critical_count() {
        let mut dashboard = MonitoringDashboard::new();
        dashboard.add_metric(Metric::new("cpu".to_string(), 95.0, 100.0, "%".to_string()));
        dashboard.add_metric(Metric::new(
            "memory".to_string(),
            50.0,
            100.0,
            "%".to_string(),
        ));

        assert_eq!(dashboard.critical_count(), 1);
    }

    #[test]
    fn test_warning_count() {
        let mut dashboard = MonitoringDashboard::new();
        dashboard.add_metric(Metric::new("cpu".to_string(), 80.0, 100.0, "%".to_string()));
        dashboard.add_metric(Metric::new(
            "memory".to_string(),
            50.0,
            100.0,
            "%".to_string(),
        ));

        assert_eq!(dashboard.warning_count(), 1);
    }
}
