//! ML Model Performance Monitoring and Alerting System

use serde::{Deserialize, Serialize};
use std::collections::{HashMap, VecDeque};
use std::sync::Arc;
use std::time::{Duration, SystemTime};
use tokio::sync::{broadcast, RwLock};
use tracing::{debug, error, info, warn};

/// Performance metric sample
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModelPerformanceSample {
    /// Model identifier
    pub model_id: String,
    /// Sample timestamp
    pub timestamp: SystemTime,
    /// Prediction accuracy (0.0-1.0)
    pub accuracy: f64,
    /// Inference latency in microseconds
    pub latency_us: u64,
    /// Confidence score of prediction
    pub confidence: f64,
    /// Memory usage in MB
    pub memory_usage_mb: f64,
    /// CPU utilization percentage
    pub cpu_utilization: f64,
    /// Whether the prediction was correct (if known)
    pub prediction_correct: Option<bool>,
    /// Actual vs predicted outcome
    pub prediction_error: Option<f64>,
    /// Market regime during prediction
    pub market_regime: Option<String>,
}

/// Alert configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AlertConfig {
    /// Enable latency alerts
    pub enable_latency_alerts: bool,
    /// Latency threshold in microseconds
    pub latency_threshold_us: u64,
    /// Enable accuracy alerts
    pub enable_accuracy_alerts: bool,
    /// Minimum accuracy threshold
    pub accuracy_threshold: f64,
    /// Enable memory alerts
    pub enable_memory_alerts: bool,
    /// Memory threshold in MB
    pub memory_threshold_mb: f64,
    /// Alert cooldown period in seconds
    pub alert_cooldown_seconds: u64,
    /// Enable model drift detection
    pub enable_drift_detection: bool,
    /// Drift detection window size
    pub drift_window_size: usize,
    /// Drift threshold (percentage change)
    pub drift_threshold_percent: f64,
}

impl Default for AlertConfig {
    fn default() -> Self {
        Self {
            enable_latency_alerts: true,
            latency_threshold_us: 1000, // 1ms
            enable_accuracy_alerts: true,
            accuracy_threshold: 0.65,
            enable_memory_alerts: true,
            memory_threshold_mb: 512.0,
            alert_cooldown_seconds: 300, // 5 minutes
            enable_drift_detection: true,
            drift_window_size: 100,
            drift_threshold_percent: 10.0,
        }
    }
}

/// Performance alert
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PerformanceAlert {
    /// Alert ID
    pub alert_id: String,
    /// Alert timestamp
    pub timestamp: SystemTime,
    /// Alert severity
    pub severity: AlertSeverity,
    /// Alert type
    pub alert_type: AlertType,
    /// Model ID that triggered the alert
    pub model_id: String,
    /// Alert message
    pub message: String,
    /// Current value that triggered alert
    pub current_value: f64,
    /// Threshold that was exceeded
    pub threshold: f64,
    /// Suggested action
    pub suggested_action: String,
}

/// Alert severity levels
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub enum AlertSeverity {
    Info,
    Warning,
    Critical,
    Emergency,
}

/// Alert types
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum AlertType {
    HighLatency,
    LowAccuracy,
    HighMemoryUsage,
    ModelDrift,
    ModelFailure,
    PredictionAnomaly,
}

/// Performance statistics
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModelPerformanceStats {
    /// Model identifier
    pub model_id: String,
    /// Total samples
    pub total_samples: u64,
    /// Average accuracy
    pub avg_accuracy: f64,
    /// 95th percentile latency
    pub p95_latency_us: f64,
    /// 99th percentile latency
    pub p99_latency_us: f64,
    /// Maximum latency observed
    pub max_latency_us: u64,
    /// Average memory usage
    pub avg_memory_mb: f64,
    /// Peak memory usage
    pub peak_memory_mb: f64,
    /// Average CPU utilization
    pub avg_cpu_utilization: f64,
    /// Prediction error rate
    pub error_rate: f64,
    /// Recent trend (improving/degrading/stable)
    pub trend: PerformanceTrend,
    /// Last updated timestamp
    pub last_updated: SystemTime,
}

impl Default for ModelPerformanceStats {
    fn default() -> Self {
        Self {
            model_id: String::new(),
            total_samples: 0,
            avg_accuracy: 0.0,
            p95_latency_us: 0.0,
            p99_latency_us: 0.0,
            max_latency_us: 0,
            avg_memory_mb: 0.0,
            peak_memory_mb: 0.0,
            avg_cpu_utilization: 0.0,
            error_rate: 0.0,
            trend: PerformanceTrend::Unknown,
            last_updated: SystemTime::now(),
        }
    }
}

/// Performance trend indicators
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Default)]
pub enum PerformanceTrend {
    Improving,
    Stable,
    Degrading,
    #[default]
    Unknown,
}

/// Model performance monitor
#[derive(Debug)]
pub struct MLPerformanceMonitor {
    /// Alert configuration
    alert_config: Arc<RwLock<AlertConfig>>,
    /// Performance samples storage (per model)
    model_samples: Arc<RwLock<HashMap<String, VecDeque<ModelPerformanceSample>>>>,
    /// Performance statistics (per model)
    model_stats: Arc<RwLock<HashMap<String, ModelPerformanceStats>>>,
    /// Recent alerts
    alerts: Arc<RwLock<VecDeque<PerformanceAlert>>>,
    /// Last alert timestamps for cooldown
    last_alert_times: Arc<RwLock<HashMap<(String, AlertType), SystemTime>>>,
    /// Alert broadcaster
    alert_broadcaster: Arc<broadcast::Sender<PerformanceAlert>>,
    /// Drift detection windows
    drift_windows: Arc<RwLock<HashMap<String, VecDeque<f64>>>>,
}

impl MLPerformanceMonitor {
    /// Create new performance monitor
    pub fn new() -> Self {
        let (alert_sender, _) = broadcast::channel(1000);

        Self {
            alert_config: Arc::new(RwLock::new(AlertConfig::default())),
            model_samples: Arc::new(RwLock::new(HashMap::new())),
            model_stats: Arc::new(RwLock::new(HashMap::new())),
            alerts: Arc::new(RwLock::new(VecDeque::new())),
            last_alert_times: Arc::new(RwLock::new(HashMap::new())),
            alert_broadcaster: Arc::new(alert_sender),
            drift_windows: Arc::new(RwLock::new(HashMap::new())),
        }
    }

    /// Create monitor with custom alert configuration
    pub fn with_config(alert_config: AlertConfig) -> Self {
        let (alert_sender, _) = broadcast::channel(1000);

        Self {
            alert_config: Arc::new(RwLock::new(alert_config)),
            model_samples: Arc::new(RwLock::new(HashMap::new())),
            model_stats: Arc::new(RwLock::new(HashMap::new())),
            alerts: Arc::new(RwLock::new(VecDeque::new())),
            last_alert_times: Arc::new(RwLock::new(HashMap::new())),
            alert_broadcaster: Arc::new(alert_sender),
            drift_windows: Arc::new(RwLock::new(HashMap::new())),
        }
    }

    /// Record performance sample
    pub async fn record_sample(&self, sample: ModelPerformanceSample) {
        let model_id = sample.model_id.clone();

        // Store sample
        {
            let mut samples = self.model_samples.write().await;
            let model_samples = samples
                .entry(model_id.clone())
                .or_insert_with(VecDeque::new);
            model_samples.push_back(sample.clone());

            // Keep only recent samples (last 1000)
            if model_samples.len() > 1000 {
                model_samples.pop_front();
            }
        }

        // Update statistics
        self.update_model_statistics(&model_id).await;

        // Check for alerts
        self.check_alerts(&sample).await;

        // Update drift detection
        if let Some(accuracy) = sample
            .prediction_correct
            .map(|correct| if correct { 1.0 } else { 0.0 })
        {
            self.update_drift_detection(&model_id, accuracy).await;
        }
    }

    /// Update model statistics
    async fn update_model_statistics(&self, model_id: &str) {
        let samples = {
            let samples_map = self.model_samples.read().await;
            samples_map.get(model_id).cloned().unwrap_or_default()
        };

        if samples.is_empty() {
            return;
        }

        let mut stats = ModelPerformanceStats {
            model_id: model_id.to_string(),
            total_samples: samples.len() as u64,
            last_updated: SystemTime::now(),
            ..Default::default()
        };

        // Calculate accuracy
        let accuracy_samples: Vec<f64> = samples
            .iter()
            .filter_map(|s| {
                s.prediction_correct
                    .map(|correct| if correct { 1.0 } else { 0.0 })
            })
            .collect();

        if !accuracy_samples.is_empty() {
            stats.avg_accuracy =
                accuracy_samples.iter().sum::<f64>() / accuracy_samples.len() as f64;
        }

        // Calculate latency percentiles
        let mut latencies: Vec<u64> = samples.iter().map(|s| s.latency_us).collect();
        latencies.sort_unstable();

        if !latencies.is_empty() {
            let p95_idx = ((latencies.len() as f64) * 0.95) as usize;
            let p99_idx = ((latencies.len() as f64) * 0.99) as usize;

            stats.p95_latency_us = latencies
                .get(p95_idx.min(latencies.len() - 1))
                .copied()
                .unwrap_or(0) as f64;
            stats.p99_latency_us = latencies
                .get(p99_idx.min(latencies.len() - 1))
                .copied()
                .unwrap_or(0) as f64;
            stats.max_latency_us = latencies.iter().max().copied().unwrap_or(0);
        }

        // Calculate memory and CPU averages
        stats.avg_memory_mb =
            samples.iter().map(|s| s.memory_usage_mb).sum::<f64>() / samples.len() as f64;
        stats.peak_memory_mb = samples
            .iter()
            .map(|s| s.memory_usage_mb)
            .fold(0.0, f64::max);
        stats.avg_cpu_utilization =
            samples.iter().map(|s| s.cpu_utilization).sum::<f64>() / samples.len() as f64;

        // Calculate error rate
        let total_predictions = samples
            .iter()
            .filter(|s| s.prediction_correct.is_some())
            .count();
        let incorrect_predictions = samples
            .iter()
            .filter(|s| s.prediction_correct == Some(false))
            .count();

        stats.error_rate = if total_predictions > 0 {
            incorrect_predictions as f64 / total_predictions as f64
        } else {
            0.0
        };

        // Calculate trend
        stats.trend = self.calculate_performance_trend(&samples);

        // Store updated statistics
        {
            let mut stats_map = self.model_stats.write().await;
            stats_map.insert(model_id.to_string(), stats);
        }
    }

    /// Calculate performance trend
    fn calculate_performance_trend(
        &self,
        samples: &VecDeque<ModelPerformanceSample>,
    ) -> PerformanceTrend {
        if samples.len() < 20 {
            return PerformanceTrend::Unknown;
        }

        // Split samples into recent and older halves
        let split_point = samples.len() / 2;
        let older_samples = &samples.as_slices().0[..split_point];
        let recent_samples = &samples.as_slices().0[split_point..];

        // Calculate average accuracy for each half
        let older_accuracy: Vec<f64> = older_samples
            .iter()
            .filter_map(|s| {
                s.prediction_correct
                    .map(|correct| if correct { 1.0 } else { 0.0 })
            })
            .collect();

        let recent_accuracy: Vec<f64> = recent_samples
            .iter()
            .filter_map(|s| {
                s.prediction_correct
                    .map(|correct| if correct { 1.0 } else { 0.0 })
            })
            .collect();

        if older_accuracy.is_empty() || recent_accuracy.is_empty() {
            return PerformanceTrend::Unknown;
        }

        let older_avg = older_accuracy.iter().sum::<f64>() / older_accuracy.len() as f64;
        let recent_avg = recent_accuracy.iter().sum::<f64>() / recent_accuracy.len() as f64;

        let change_percent = ((recent_avg - older_avg) / older_avg) * 100.0;

        if change_percent > 5.0 {
            PerformanceTrend::Improving
        } else if change_percent < -5.0 {
            PerformanceTrend::Degrading
        } else {
            PerformanceTrend::Stable
        }
    }

    /// Update drift detection
    async fn update_drift_detection(&self, model_id: &str, accuracy: f64) {
        let config = self.alert_config.read().await;

        if !config.enable_drift_detection {
            return;
        }

        let mut drift_windows = self.drift_windows.write().await;
        let window = drift_windows
            .entry(model_id.to_string())
            .or_insert_with(VecDeque::new);

        window.push_back(accuracy);

        // Keep only the required window size
        if window.len() > config.drift_window_size {
            window.pop_front();
        }

        // Check for drift if we have enough samples
        if window.len() >= config.drift_window_size {
            let recent_avg = window
                .iter()
                .rev()
                .take(config.drift_window_size / 2)
                .sum::<f64>()
                / (config.drift_window_size / 2) as f64;
            let older_avg = window
                .iter()
                .take(config.drift_window_size / 2)
                .sum::<f64>()
                / (config.drift_window_size / 2) as f64;

            let drift_percent = ((recent_avg - older_avg) / older_avg).abs() * 100.0;

            if drift_percent > config.drift_threshold_percent {
                self.create_drift_alert(model_id, drift_percent, config.drift_threshold_percent)
                    .await;
            }
        }
    }

    /// Check for performance alerts
    async fn check_alerts(&self, sample: &ModelPerformanceSample) {
        let config = self.alert_config.read().await;

        // Check latency alert
        if config.enable_latency_alerts
            && sample.latency_us > config.latency_threshold_us
            && self
                .should_send_alert(&sample.model_id, AlertType::HighLatency)
                .await
        {
            self.create_alert(
                &sample.model_id,
                AlertType::HighLatency,
                AlertSeverity::Warning,
                format!(
                    "High inference latency: {}μs (threshold: {}μs)",
                    sample.latency_us, config.latency_threshold_us
                ),
                sample.latency_us as f64,
                config.latency_threshold_us as f64,
                "Consider model optimization or load balancing".to_string(),
            )
            .await;
        }

        // Check accuracy alert (if we have prediction result)
        if config.enable_accuracy_alerts {
            if let Some(correct) = sample.prediction_correct {
                let accuracy = if correct { 1.0 } else { 0.0 };
                if accuracy < config.accuracy_threshold
                    && self
                        .should_send_alert(&sample.model_id, AlertType::LowAccuracy)
                        .await
                {
                    self.create_alert(
                        &sample.model_id,
                        AlertType::LowAccuracy,
                        AlertSeverity::Critical,
                        format!(
                            "Low prediction accuracy: {:.2}% (threshold: {:.2}%)",
                            accuracy * 100.0,
                            config.accuracy_threshold * 100.0
                        ),
                        accuracy,
                        config.accuracy_threshold,
                        "Review model performance and consider retraining".to_string(),
                    )
                    .await;
                }
            }
        }

        // Check memory alert
        if config.enable_memory_alerts
            && sample.memory_usage_mb > config.memory_threshold_mb
            && self
                .should_send_alert(&sample.model_id, AlertType::HighMemoryUsage)
                .await
        {
            self.create_alert(
                &sample.model_id,
                AlertType::HighMemoryUsage,
                AlertSeverity::Warning,
                format!(
                    "High memory usage: {:.1}MB (threshold: {:.1}MB)",
                    sample.memory_usage_mb, config.memory_threshold_mb
                ),
                sample.memory_usage_mb,
                config.memory_threshold_mb,
                "Monitor for memory leaks or consider model compression".to_string(),
            )
            .await;
        }
    }

    /// Create drift alert
    async fn create_drift_alert(&self, model_id: &str, drift_percent: f64, threshold_percent: f64) {
        if self
            .should_send_alert(model_id, AlertType::ModelDrift)
            .await
        {
            self.create_alert(
                model_id,
                AlertType::ModelDrift,
                AlertSeverity::Critical,
                format!(
                    "Model drift detected: {:.2}% change (threshold: {:.2}%)",
                    drift_percent, threshold_percent
                ),
                drift_percent,
                threshold_percent,
                "Consider model retraining or updating training data".to_string(),
            )
            .await;
        }
    }

    /// Create and broadcast alert
    async fn create_alert(
        &self,
        model_id: &str,
        alert_type: AlertType,
        severity: AlertSeverity,
        message: String,
        current_value: f64,
        threshold: f64,
        suggested_action: String,
    ) {
        let alert = PerformanceAlert {
            alert_id: uuid::Uuid::new_v4().to_string(),
            timestamp: SystemTime::now(),
            severity,
            alert_type,
            model_id: model_id.to_string(),
            message,
            current_value,
            threshold,
            suggested_action,
        };

        // Store alert
        {
            let mut alerts = self.alerts.write().await;
            alerts.push_back(alert.clone());

            // Keep only recent alerts (last 100)
            if alerts.len() > 100 {
                alerts.pop_front();
            }
        }

        // Update last alert time
        {
            let mut last_alert_times = self.last_alert_times.write().await;
            last_alert_times.insert((model_id.to_string(), alert_type), SystemTime::now());
        }

        // Broadcast alert
        if let Err(e) = self.alert_broadcaster.send(alert.clone()) {
            debug!("Failed to broadcast alert: {}", e);
        }

        match severity {
            AlertSeverity::Emergency | AlertSeverity::Critical => {
                error!("ML Performance Alert: {}", alert.message)
            },
            AlertSeverity::Warning => warn!("ML Performance Alert: {}", alert.message),
            AlertSeverity::Info => info!("ML Performance Alert: {}", alert.message),
        }
    }

    /// Check if alert should be sent (considering cooldown)
    async fn should_send_alert(&self, model_id: &str, alert_type: AlertType) -> bool {
        let config = self.alert_config.read().await;
        let last_alert_times = self.last_alert_times.read().await;

        if let Some(&last_time) = last_alert_times.get(&(model_id.to_string(), alert_type)) {
            let cooldown = Duration::from_secs(config.alert_cooldown_seconds);
            SystemTime::now()
                .duration_since(last_time)
                .unwrap_or(cooldown)
                >= cooldown
        } else {
            true
        }
    }

    /// Get model statistics
    pub async fn get_model_stats(&self, model_id: &str) -> Option<ModelPerformanceStats> {
        let stats = self.model_stats.read().await;
        stats.get(model_id).cloned()
    }

    /// Get all model statistics
    pub async fn get_all_model_stats(&self) -> HashMap<String, ModelPerformanceStats> {
        self.model_stats.read().await.clone()
    }

    /// Get recent alerts
    pub async fn get_recent_alerts(&self, limit: usize) -> Vec<PerformanceAlert> {
        let alerts = self.alerts.read().await;
        alerts.iter().rev().take(limit).cloned().collect()
    }

    /// Subscribe to alerts
    pub fn subscribe_alerts(&self) -> broadcast::Receiver<PerformanceAlert> {
        self.alert_broadcaster.subscribe()
    }

    /// Update alert configuration
    pub async fn update_config(&self, new_config: AlertConfig) {
        let mut config = self.alert_config.write().await;
        *config = new_config;
        info!("Alert configuration updated");
    }

    /// Clear old data
    pub async fn cleanup(&self, max_age: Duration) {
        let cutoff_time = SystemTime::now() - max_age;

        // Cleanup samples
        {
            let mut samples = self.model_samples.write().await;
            for model_samples in samples.values_mut() {
                model_samples.retain(|sample| sample.timestamp >= cutoff_time);
            }
            samples.retain(|_, samples_vec| !samples_vec.is_empty());
        }

        // Cleanup alerts
        {
            let mut alerts = self.alerts.write().await;
            alerts.retain(|alert| alert.timestamp >= cutoff_time);
        }

        // Cleanup alert times
        {
            let mut last_alert_times = self.last_alert_times.write().await;
            last_alert_times.retain(|_, &mut timestamp| timestamp >= cutoff_time);
        }

        info!(
            "Cleaned up performance monitoring data older than {:?}",
            max_age
        );
    }

    /// Generate performance dashboard data
    pub async fn get_dashboard_data(&self) -> MLPerformanceDashboard {
        let all_stats = self.get_all_model_stats().await;
        let recent_alerts = self.get_recent_alerts(10).await;

        // Calculate summary metrics
        let total_models = all_stats.len();
        let healthy_models = all_stats
            .values()
            .filter(|stats| stats.avg_accuracy > 0.7 && stats.error_rate < 0.1)
            .count();

        let avg_accuracy = if !all_stats.is_empty() {
            all_stats.values().map(|s| s.avg_accuracy).sum::<f64>() / all_stats.len() as f64
        } else {
            0.0
        };

        let max_latency = all_stats
            .values()
            .map(|s| s.p99_latency_us)
            .fold(0.0, f64::max);

        MLPerformanceDashboard {
            total_models,
            healthy_models,
            avg_accuracy,
            max_latency,
            model_stats: all_stats,
            recent_alerts,
            last_updated: SystemTime::now(),
        }
    }
}

/// Performance dashboard data structure
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MLPerformanceDashboard {
    /// Total number of monitored models
    pub total_models: usize,
    /// Number of healthy models
    pub healthy_models: usize,
    /// Average accuracy across all models
    pub avg_accuracy: f64,
    /// Maximum latency observed
    pub max_latency: f64,
    /// Per-model statistics
    pub model_stats: HashMap<String, ModelPerformanceStats>,
    /// Recent alerts
    pub recent_alerts: Vec<PerformanceAlert>,
    /// Last dashboard update
    pub last_updated: SystemTime,
}

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

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

    #[tokio::test]
    async fn test_performance_monitor_creation() {
        let monitor = MLPerformanceMonitor::new();
        let stats = monitor.get_all_model_stats().await;
        assert!(stats.is_empty());
    }

    #[tokio::test]
    async fn test_sample_recording() {
        let monitor = MLPerformanceMonitor::new();

        let sample = ModelPerformanceSample {
            model_id: "test_model".to_string(),
            timestamp: SystemTime::now(),
            accuracy: 0.85,
            latency_us: 500,
            confidence: 0.9,
            memory_usage_mb: 100.0,
            cpu_utilization: 25.0,
            prediction_correct: Some(true),
            prediction_error: Some(0.1),
            market_regime: Some("trending".to_string()),
        };

        monitor.record_sample(sample).await;

        let stats = monitor.get_model_stats("test_model").await;
        assert!(stats.is_some());
        if let Some(s) = stats {
            assert_eq!(s.total_samples, 1);
        }
    }

    #[tokio::test]
    async fn test_alert_generation() {
        let config = AlertConfig {
            latency_threshold_us: 100, // Very low threshold for testing
            enable_latency_alerts: true, // Explicitly enable
            ..Default::default()
        };

        let monitor = MLPerformanceMonitor::with_config(config);

        let sample = ModelPerformanceSample {
            model_id: "slow_model".to_string(),
            timestamp: SystemTime::now(),
            accuracy: 0.85,
            latency_us: 1000, // Above threshold (1000 > 100)
            confidence: 0.9,
            memory_usage_mb: 100.0,
            cpu_utilization: 25.0,
            prediction_correct: Some(true),
            prediction_error: Some(0.1),
            market_regime: Some("trending".to_string()),
        };

        // Record sample - this should trigger alert creation
        monitor.record_sample(sample).await;

        let alerts = monitor.get_recent_alerts(10).await;
        assert!(
            !alerts.is_empty(),
            "Expected at least one alert to be generated"
        );
        if let Some(first_alert) = alerts.first() {
            assert_eq!(
                first_alert.alert_type,
                AlertType::HighLatency,
                "Expected first alert to be HighLatency, got {:?}",
                first_alert.alert_type
            );
        }
    }
}
