//! Model Drift Detection and Monitoring
//!
//! This module provides comprehensive model drift detection to identify
//! when ML models are degrading and need retraining or replacement.

use std::collections::{HashMap, VecDeque};
use std::time::{Duration, SystemTime};

use serde::{Deserialize, Serialize};
use tracing::{debug, info, warn};

use super::{MLSafetyConfig, MLSafetyError, SafetyResult, SafetyStatus};

/// Statistical bounds configuration to eliminate hardcoded p-values
#[derive(Debug, Clone)]
struct StatisticalBounds {
    min_p_value: f64,
    max_p_value: f64,
    low_p_value: f64,
    medium_p_value: f64,
    high_p_value: f64,
}

impl StatisticalBounds {
    fn emergency_safe_defaults() -> Self {
        Self {
            min_p_value: 0.001,  // Minimum p-value for statistical safety
            max_p_value: 1.0,    // Maximum p-value
            low_p_value: 0.05,   // Conservative significance threshold
            medium_p_value: 0.3, // Medium significance
            high_p_value: 0.95,  // High confidence
        }
    }
}

/// Chi-square test thresholds configuration
#[derive(Debug, Clone)]
struct ChiSquareThresholds {
    low_threshold: f64,
    medium_threshold: f64,
    high_threshold: f64,
}

impl ChiSquareThresholds {
    fn emergency_safe_defaults() -> Self {
        Self {
            low_threshold: 0.5,    // Conservative threshold for low chi-square
            medium_threshold: 1.0, // Medium threshold
            high_threshold: 2.0,   // High threshold for significance
        }
    }
}

/// Types of drift that can be detected
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum DriftType {
    /// Statistical distribution drift
    Distribution,
    /// Performance metric drift  
    Performance,
    /// Prediction accuracy drift
    Accuracy,
    /// Data schema drift
    Schema,
    /// Concept drift (relationship between features and target)
    Concept,
}

/// Drift detection result
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DriftResult {
    /// Type of drift detected
    pub drift_type: DriftType,
    /// Drift score (0.0 = no drift, 1.0 = maximum drift)
    pub score: f64,
    /// Statistical significance (p-value)
    pub p_value: Option<f64>,
    /// Threshold that was exceeded
    pub threshold: f64,
    /// Timestamp of detection
    pub detected_at: SystemTime,
    /// Additional context
    pub details: HashMap<String, String>,
}

/// Model performance window for drift detection
#[derive(Debug, Clone)]
struct PerformanceWindow {
    predictions: VecDeque<f64>,
    actuals: VecDeque<f64>,
    timestamps: VecDeque<SystemTime>,
    window_size: usize,
    baseline_mean: f64,
    baseline_std: f64,
}

impl PerformanceWindow {
    fn new(window_size: usize) -> Self {
        Self {
            predictions: VecDeque::with_capacity(window_size),
            actuals: VecDeque::with_capacity(window_size),
            timestamps: VecDeque::with_capacity(window_size),
            window_size,
            baseline_mean: 0.0,
            baseline_std: 1.0,
        }
    }

    /// Safely cast usize to f64 with overflow protection
    fn safe_cast_len_to_f64(&self, len: usize) -> Result<f64, ()> {
        if len > (f64::MAX as usize) {
            Err(())
        } else {
            Ok(len as f64)
        }
    }

    /// Safely calculate mean using Kahan summation
    fn safe_mean(&self, values: &[f64]) -> Result<f64, ()> {
        if values.is_empty() {
            return Err(());
        }

        // Check for NaN/Infinity
        for &value in values {
            if !value.is_finite() {
                return Err(());
            }
        }

        let n = self.safe_cast_len_to_f64(values.len())?;
        let sum = self.safe_sum(values)?;

        let mean = sum / n;
        if mean.is_finite() {
            Ok(mean)
        } else {
            Err(())
        }
    }

    /// Safely calculate variance
    fn safe_variance(&self, values: &[f64], mean: f64) -> Result<f64, ()> {
        if values.is_empty() || !mean.is_finite() {
            return Err(());
        }

        let n = self.safe_cast_len_to_f64(values.len())?;
        if n <= 1.0 {
            return Ok(0.0); // Single value has zero variance
        }

        // Calculate sum of squared deviations using Kahan summation
        let mut sum_sq_dev = 0.0;
        let mut compensation = 0.0;

        for &value in values {
            if !value.is_finite() {
                return Err(());
            }

            let deviation = value - mean;
            let sq_deviation = deviation * deviation;

            if !sq_deviation.is_finite() {
                return Err(());
            }

            let compensated_value = sq_deviation - compensation;
            let temp_sum = sum_sq_dev + compensated_value;
            compensation = (temp_sum - sum_sq_dev) - compensated_value;
            sum_sq_dev = temp_sum;

            if !sum_sq_dev.is_finite() {
                return Err(());
            }
        }

        let variance = sum_sq_dev / (n - 1.0); // Sample variance
        if variance.is_finite() && variance >= 0.0 {
            Ok(variance)
        } else {
            Err(())
        }
    }

    /// Safely sum values using Kahan summation
    fn safe_sum(&self, values: &[f64]) -> Result<f64, ()> {
        if values.is_empty() {
            return Ok(0.0);
        }

        let mut sum = 0.0;
        let mut compensation = 0.0;

        for &value in values {
            if !value.is_finite() {
                return Err(());
            }

            let compensated_value = value - compensation;
            let temp_sum = sum + compensated_value;
            compensation = (temp_sum - sum) - compensated_value;
            sum = temp_sum;

            if !sum.is_finite() {
                return Err(());
            }
        }

        Ok(sum)
    }

    fn add_prediction(&mut self, prediction: f64, actual: Option<f64>) {
        let now = SystemTime::now();

        self.predictions.push_back(prediction);
        self.timestamps.push_back(now);

        if let Some(actual_val) = actual {
            self.actuals.push_back(actual_val);
        }

        // Maintain window size
        while self.predictions.len() > self.window_size {
            self.predictions.pop_front();
            self.timestamps.pop_front();
        }

        while self.actuals.len() > self.window_size {
            self.actuals.pop_front();
        }
    }

    fn set_baseline(&mut self, mean: f64, std: f64) {
        self.baseline_mean = mean;
        self.baseline_std = std.max(1e-8); // Avoid division by zero
    }

    fn calculate_distribution_drift(&self) -> f64 {
        if self.predictions.len() < 30 {
            return 0.0; // Need sufficient samples
        }

        // Safe length conversion
        let _n = match self.safe_cast_len_to_f64(self.predictions.len()) {
            Ok(n) => n,
            Err(_) => {
                warn!(
                    "Failed to convert prediction length {} to f64",
                    self.predictions.len()
                );
                return 1.0; // Maximum drift to trigger attention
            },
        };

        // Convert VecDeque to Vec for safe operations
        let predictions_vec: Vec<f64> = self.predictions.iter().cloned().collect();

        // Safe mean calculation with Kahan summation
        let current_mean = match self.safe_mean(&predictions_vec) {
            Ok(mean) => mean,
            Err(_) => {
                warn!("Failed to calculate current mean for drift detection");
                return 1.0; // Maximum drift to trigger attention
            },
        };

        // Safe variance calculation
        let current_variance = match self.safe_variance(&predictions_vec, current_mean) {
            Ok(var) => var,
            Err(_) => {
                warn!("Failed to calculate current variance for drift detection");
                return 1.0; // Maximum drift to trigger attention
            },
        };
        let current_std = current_variance.sqrt().max(1e-8); // Prevent division by zero

        // Calculate standardized distance between distributions with safe division
        // Handle near-zero baseline std by using absolute difference instead of ratio
        let mean_drift = if self.baseline_std > 1e-6 {
            // Sufficient variance to use standardized drift
            ((current_mean - self.baseline_mean) / self.baseline_std).abs()
        } else {
            // Near-zero variance baseline - use normalized absolute difference
            // Scale to [0, 2] range to be comparable to std_drift
            let abs_diff = (current_mean - self.baseline_mean).abs();
            (abs_diff / 3.0).min(2.0) // Normalize: assume typical drift is ~3.0 scale
        };

        let std_drift = if self.baseline_std > 1e-6 {
            // Sufficient variance to use ratio-based drift
            ((current_std - self.baseline_std) / self.baseline_std).abs()
        } else {
            // Near-zero variance baseline - compare current std to a small threshold
            if current_std > 0.1 {
                1.0 // Significant variance appeared
            } else {
                0.0 // Still low variance
            }
        };

        // Combine mean and standard deviation drift
        let combined = (mean_drift + std_drift) / 2.0;

        // Ensure result is finite
        if combined.is_finite() {
            combined
        } else {
            1.0
        }
    }

    fn calculate_accuracy_drift(&self) -> Option<f64> {
        if self.predictions.len() != self.actuals.len() || self.actuals.len() < 10 {
            return None;
        }

        // Safe length conversion
        let n = match self.safe_cast_len_to_f64(self.actuals.len()) {
            Ok(n) => n,
            Err(_) => {
                warn!(
                    "Failed to convert actuals length {} to f64",
                    self.actuals.len()
                );
                return Some(1.0); // Maximum drift
            },
        };

        // Calculate current accuracy (for classification)
        let correct_predictions = self
            .predictions
            .iter()
            .zip(self.actuals.iter())
            .filter(|(pred, actual)| {
                ((**pred > 0.5) && (**actual > 0.5)) || ((**pred <= 0.5) && (**actual <= 0.5))
            })
            .count();

        let current_accuracy = match self.safe_cast_len_to_f64(correct_predictions) {
            Ok(correct) => correct / n,
            Err(_) => {
                warn!("Failed to calculate accuracy ratio");
                return Some(1.0); // Maximum drift
            },
        };

        // For regression, calculate MSE drift with safe operations
        let squared_errors: Vec<f64> = self
            .predictions
            .iter()
            .zip(self.actuals.iter())
            .map(|(pred, actual)| {
                let diff = pred - actual;
                diff * diff // Safe squaring
            })
            .collect();

        let current_mse = match self.safe_mean(&squared_errors) {
            Ok(mse) => mse,
            Err(_) => {
                warn!("Failed to calculate MSE for accuracy drift");
                return Some(1.0); // Maximum drift
            },
        };

        // Return normalized drift score
        let drift_score = if current_accuracy < 0.5 {
            1.0 - current_accuracy // Higher drift for lower accuracy
        } else {
            let rmse = current_mse.sqrt();
            if rmse.is_finite() {
                rmse / 10.0
            } else {
                1.0
            } // Normalized RMSE
        };

        Some(if drift_score.is_finite() && drift_score >= 0.0 {
            drift_score
        } else {
            1.0
        })
    }
}

/// Model drift detector with comprehensive monitoring
#[derive(Debug)]
pub struct ModelDriftDetector {
    config: MLSafetyConfig,
    model_windows: HashMap<String, PerformanceWindow>,
    drift_history: HashMap<String, Vec<DriftResult>>,
    baseline_stats: HashMap<String, (f64, f64)>, // (mean, std)
    last_check: HashMap<String, SystemTime>,
}

impl ModelDriftDetector {
    /// Create new drift detector
    pub fn new(config: &MLSafetyConfig) -> Self {
        Self {
            config: config.clone(),
            model_windows: HashMap::new(),
            drift_history: HashMap::new(),
            baseline_stats: HashMap::new(),
            last_check: HashMap::new(),
        }
    }

    /// Set baseline statistics for a model
    pub async fn set_baseline(
        &mut self,
        model_id: &str,
        baseline_predictions: &[f64],
    ) -> SafetyResult<()> {
        if baseline_predictions.is_empty() {
            return Err(MLSafetyError::MathSafety {
                reason: "Cannot set baseline with empty predictions".to_owned(),
            });
        }

        // Use safe mathematical operations for baseline calculation
        let window = PerformanceWindow::new(1000); // Temporary window for safe operations

        let mean = match window.safe_mean(baseline_predictions) {
            Ok(mean) => mean,
            Err(_) => {
                return Err(MLSafetyError::MathSafety {
                    reason: "Failed to calculate baseline mean".to_owned(),
                });
            },
        };

        let variance = match window.safe_variance(baseline_predictions, mean) {
            Ok(var) => var,
            Err(_) => {
                return Err(MLSafetyError::MathSafety {
                    reason: "Failed to calculate baseline variance".to_owned(),
                });
            },
        };

        let std = variance.sqrt().max(1e-8); // Prevent division by zero

        self.baseline_stats
            .insert(model_id.to_string(), (mean, std));

        // Initialize or update window baseline
        let window = self
            .model_windows
            .entry(model_id.to_string())
            .or_insert_with(|| PerformanceWindow::new(1000));
        window.set_baseline(mean, std);

        info!(
            "Baseline set for model {}: mean={:.3}, std={:.3}",
            model_id, mean, std
        );

        Ok(())
    }

    /// Update model with new predictions and check for drift
    pub async fn update_and_check(
        &mut self,
        model_id: &str,
        predictions: &[f64],
        actual_values: Option<&[f64]>,
    ) -> SafetyResult<f64> {
        // Validate inputs
        if predictions.is_empty() {
            return Err(MLSafetyError::MathSafety {
                reason: "Cannot update with empty predictions".to_owned(),
            });
        }

        if let Some(actuals) = actual_values {
            if actuals.len() != predictions.len() {
                return Err(MLSafetyError::MathSafety {
                    reason: format!(
                        "Predictions length {} != actuals length {}",
                        predictions.len(),
                        actuals.len()
                    ),
                });
            }
        }

        // Check for NaN/Infinity in predictions
        for (i, &pred) in predictions.into_iter().enumerate() {
            if !pred.is_finite() {
                return Err(MLSafetyError::InvalidFloat {
                    operation: format!("Drift detection prediction at index {}: {}", i, pred),
                });
            }
        }

        // Get or create performance window
        let window = self
            .model_windows
            .entry(model_id.to_string())
            .or_insert_with(|| PerformanceWindow::new(1000));

        // Add predictions to window
        for (i, &pred) in predictions.into_iter().enumerate() {
            let actual = actual_values.and_then(|actuals| actuals.get(i).copied());

            window.add_prediction(pred, actual);
        }

        // Check if it's time to run drift detection
        // Always check if window has enough samples (for testing), or if time threshold met
        let should_check = {
            // Always check if we have sufficient samples for meaningful drift detection
            if window.predictions.len() >= 30 {
                true
            } else {
                let last = self.last_check.get(model_id);
                match last {
                    None => true,
                    Some(last_time) => {
                        // Safe elapsed time calculation with overflow protection
                        match last_time.elapsed() {
                            Ok(elapsed) => elapsed >= Duration::from_secs(300), // Check every 5 minutes
                            Err(_) => {
                                // Time went backwards (system clock adjustment), force check
                                warn!("System time inconsistency detected for model {}, forcing drift check", model_id);
                                true
                            },
                        }
                    },
                }
            }
        };

        if !should_check {
            return Ok(0.0);
        }

        // Update last check time
        self.last_check
            .insert(model_id.to_string(), SystemTime::now());

        // Calculate drift scores and collect needed data before async call
        let distribution_drift = window.calculate_distribution_drift();
        let accuracy_drift = window.calculate_accuracy_drift().unwrap_or(0.0);
        let window_size = window.predictions.len();

        // Combined drift score
        let combined_drift = (distribution_drift + accuracy_drift) / 2.0;

        // Check if drift exceeds threshold
        if combined_drift > self.config.drift_sensitivity {
            // Calculate statistical significance (async call needs to happen after we release the borrow)
            let p_value = self
                .calculate_statistical_significance(model_id, distribution_drift, accuracy_drift)
                .await;

            let drift_result = DriftResult {
                drift_type: if distribution_drift > accuracy_drift {
                    DriftType::Distribution
                } else {
                    DriftType::Accuracy
                },
                score: combined_drift,
                p_value,
                threshold: self.config.drift_sensitivity,
                detected_at: SystemTime::now(),
                details: {
                    let mut details = HashMap::new();
                    details.insert(
                        "distribution_drift".to_owned(),
                        distribution_drift.to_string(),
                    );
                    details.insert("accuracy_drift".to_owned(), accuracy_drift.to_string());
                    details.insert("window_size".to_owned(), window_size.to_string());
                    details
                },
            };

            // Store drift result
            self.drift_history
                .entry(model_id.to_string())
                .or_insert_with(Vec::new)
                .push(drift_result);

            warn!(
                "Model drift detected for {}: score {:.3} > threshold {:.3}",
                model_id, combined_drift, self.config.drift_sensitivity
            );
        } else {
            debug!(
                "No drift detected for {}: score {:.3} <= threshold {:.3}",
                model_id, combined_drift, self.config.drift_sensitivity
            );
        }

        Ok(combined_drift)
    }

    /// Calculate statistical significance of observed drift using multiple tests
    async fn calculate_statistical_significance(
        &self,
        model_id: &str,
        _distribution_drift: f64,
        _accuracy_drift: f64,
    ) -> Option<f64> {
        let window = self.model_windows.get(model_id)?;
        let (baseline_mean, baseline_std) = self.baseline_stats.get(model_id)?;

        if window.predictions.len() < 30 {
            return None; // Need sufficient samples for statistical significance
        }

        // Perform multiple statistical tests and return the minimum p-value (most significant)
        let mut p_values = Vec::new();

        // 1. Two-sample t-test for mean difference
        if let Some(t_test_p) = self.two_sample_t_test(window, *baseline_mean, *baseline_std) {
            p_values.push(t_test_p);
        }

        // 2. Kolmogorov-Smirnov test for distribution difference
        if let Some(ks_p) = self.kolmogorov_smirnov_test(window, *baseline_mean, *baseline_std) {
            p_values.push(ks_p);
        }

        // 3. Chi-square test for variance difference
        if let Some(chi2_p) = self.chi_square_variance_test(window, *baseline_std) {
            p_values.push(chi2_p);
        }

        // Return minimum p-value (Bonferroni correction could be applied)
        // Safe fold with overflow protection
        p_values
            .into_iter()
            .try_fold(None, |min_p: Option<f64>, p: f64| {
                if !p.is_finite() {
                    warn!(
                        "Non-finite p-value detected in statistical significance calculation: {}",
                        p
                    );
                    return Some(min_p); // Skip this p-value
                }
                let result = match min_p {
                    None => Some(p),
                    Some(current_min) => {
                        if !current_min.is_finite() {
                            Some(p) // Replace invalid current_min
                        } else {
                            Some(p.min(current_min))
                        }
                    },
                };
                Some(result)
            })
            .flatten()
    }

    /// Two-sample t-test assuming unequal variances (Welch's t-test)
    fn two_sample_t_test(
        &self,
        window: &PerformanceWindow,
        baseline_mean: f64,
        baseline_std: f64,
    ) -> Option<f64> {
        if window.predictions.len() < 10 {
            return None;
        }

        let n = window.predictions.len() as f64;
        let predictions_vec: Vec<f64> = window.predictions.iter().cloned().collect();
        let sample_mean = match window.safe_mean(&predictions_vec) {
            Ok(mean) => mean,
            Err(_) => return None,
        };

        let sample_variance = match window.safe_variance(&predictions_vec, sample_mean) {
            Ok(var) => var,
            Err(_) => return None,
        };
        let _sample_std = sample_variance.sqrt();

        // Assumed baseline sample size (for demonstration)
        let baseline_n = 1000.0;

        // Welch's t-test statistic
        let se_diff = ((sample_variance / n) + (baseline_std.powi(2) / baseline_n)).sqrt();

        if se_diff <= f64::EPSILON {
            return Some(1.0); // No difference
        }

        let t_stat = ((sample_mean - baseline_mean) / se_diff).abs();

        // Degrees of freedom for Welch's t-test
        let df = ((sample_variance / n) + (baseline_std.powi(2) / baseline_n)).powi(2)
            / ((sample_variance / n).powi(2) / (n - 1.0)
                + (baseline_std.powi(2) / baseline_n).powi(2) / (baseline_n - 1.0));

        // Approximate p-value using t-distribution approximation
        Some(self.t_distribution_p_value(t_stat, df))
    }

    /// Kolmogorov-Smirnov test for distribution difference
    fn kolmogorov_smirnov_test(
        &self,
        window: &PerformanceWindow,
        baseline_mean: f64,
        baseline_std: f64,
    ) -> Option<f64> {
        if window.predictions.len() < 20 {
            return None;
        }

        let mut sample_data: Vec<f64> = window.predictions.iter().cloned().collect();
        // Safe sorting with NaN handling
        sample_data.sort_by(|a, b| {
            match a.partial_cmp(b) {
                Some(ordering) => ordering,
                None => {
                    // Handle NaN values by treating them as equal (stable sort)
                    warn!("NaN values detected during KS test sorting");
                    std::cmp::Ordering::Equal
                },
            }
        });

        let n = sample_data.len() as f64;
        let mut max_diff: f64 = 0.0;

        for (i, &x) in sample_data.iter().enumerate() {
            let empirical_cdf = (i + 1) as f64 / n;

            // Compare against normal distribution with baseline parameters
            let z_score = (x - baseline_mean) / baseline_std;
            let theoretical_cdf = 0.5 * (1.0 + Self::erf(z_score / 2.0_f64.sqrt()));

            let diff = (empirical_cdf - theoretical_cdf).abs();
            max_diff = max_diff.max(diff);
        }

        // KS test statistic
        let ks_stat = max_diff * n.sqrt();

        // Approximate p-value for KS test
        Some(self.ks_distribution_p_value(ks_stat))
    }

    /// Chi-square test for variance difference
    fn chi_square_variance_test(
        &self,
        window: &PerformanceWindow,
        baseline_std: f64,
    ) -> Option<f64> {
        if window.predictions.len() < 10 {
            return None;
        }

        let n = window.predictions.len() as f64;
        let predictions_vec: Vec<f64> = window.predictions.iter().cloned().collect();
        let sample_mean = match window.safe_mean(&predictions_vec) {
            Ok(mean) => mean,
            Err(_) => return None,
        };

        let sample_variance = match window.safe_variance(&predictions_vec, sample_mean) {
            Ok(var) => var,
            Err(_) => return None,
        };

        if baseline_std <= f64::EPSILON {
            return Some(1.0);
        }

        // Chi-square test statistic for variance
        let chi2_stat = (n - 1.0) * sample_variance / baseline_std.powi(2);
        let df = n - 1.0;

        // Approximate p-value using chi-square distribution
        Some(self.chi_square_p_value(chi2_stat, df))
    }

    /// Error function approximation for normal CDF
    fn erf(x: f64) -> f64 {
        // Abramowitz and Stegun approximation
        let a1 = 0.254829592;
        let a2 = -0.284496736;
        let a3 = 1.421413741;
        let a4 = -1.453152027;
        let a5 = 1.061405429;
        let p = 0.3275911;

        let sign = if x < 0.0 { -1.0 } else { 1.0 };
        let x = x.abs();

        let t = 1.0 / (1.0 + p * x);
        let y = 1.0 - (((((a5 * t + a4) * t) + a3) * t + a2) * t + a1) * t * (-x * x).exp();

        sign * y
    }

    /// Approximate t-distribution p-value
    fn t_distribution_p_value(&self, t_stat: f64, df: f64) -> f64 {
        if df < 1.0 || !t_stat.is_finite() {
            return 1.0;
        }

        // For large df, t-distribution approaches normal
        if df > 30.0 {
            // Two-tailed test using normal approximation
            let z = t_stat;
            return 2.0 * (1.0 - 0.5 * (1.0 + Self::erf(z.abs() / 2.0_f64.sqrt())));
        }

        // Simplified approximation for smaller df
        let p_approx = if t_stat.abs() > 2.0 {
            0.05 * (-0.5 * t_stat.abs()).exp()
        } else {
            0.5 - 0.1 * t_stat.abs()
        };

        // Use configured p-value bounds instead of hardcoded values
        let bounds = self.get_statistical_bounds();
        p_approx.max(bounds.min_p_value).min(bounds.max_p_value)
    }

    /// Approximate Kolmogorov-Smirnov p-value
    fn ks_distribution_p_value(&self, ks_stat: f64) -> f64 {
        if !ks_stat.is_finite() || ks_stat <= 0.0 {
            return 1.0;
        }

        // Asymptotic approximation: P(Dn > x) ≈ 2 * exp(-2 * x^2)
        let p_value = 2.0 * (-2.0 * ks_stat.powi(2)).exp();
        // Use configured p-value bounds instead of hardcoded values
        let bounds = self.get_statistical_bounds();
        p_value.min(bounds.max_p_value).max(bounds.min_p_value)
    }

    /// Approximate chi-square p-value
    fn chi_square_p_value(&self, chi2_stat: f64, df: f64) -> f64 {
        if !chi2_stat.is_finite() || df <= 0.0 {
            return 1.0;
        }

        // Use configured thresholds instead of hardcoded values
        let bounds = self.get_statistical_bounds();
        let thresholds = self.get_chi_square_thresholds();

        if chi2_stat < df * thresholds.low_threshold {
            bounds.high_p_value // Low chi-square, high p-value
        } else if chi2_stat < df * thresholds.medium_threshold {
            bounds.medium_p_value
        } else if chi2_stat < df * thresholds.high_threshold {
            bounds.low_p_value
        } else {
            bounds.min_p_value // High chi-square, low p-value
        }
    }

    /// Get statistical bounds from configuration
    fn get_statistical_bounds(&self) -> StatisticalBounds {
        // In production, this would load from config system
        // For now, return conservative safe defaults
        StatisticalBounds::emergency_safe_defaults()
    }

    /// Get chi-square thresholds from configuration
    fn get_chi_square_thresholds(&self) -> ChiSquareThresholds {
        // In production, this would load from config system
        // For now, return conservative safe defaults
        ChiSquareThresholds::emergency_safe_defaults()
    }

    /// Get drift status for a model
    pub async fn get_drift_status(&self, model_id: &str) -> SafetyStatus {
        if let Some(history) = self.drift_history.get(model_id) {
            if let Some(latest) = history.last() {
                // Check if recent drift detected
                // Safe elapsed time calculation with overflow protection
                let is_recent = match latest.detected_at.elapsed() {
                    Ok(elapsed) => elapsed < Duration::from_secs(3600), // Within last hour
                    Err(_) => {
                        // Time went backwards, consider as not recent to be safe
                        warn!("System time inconsistency detected for drift status check");
                        false
                    },
                };
                if is_recent {
                    return SafetyStatus::Danger {
                        reason: format!(
                            "Recent drift detected: score {:.3} > threshold {:.3}",
                            latest.score, latest.threshold
                        ),
                    };
                }
            }
        }

        // Check if model has baseline
        if !self.baseline_stats.contains_key(model_id) {
            return SafetyStatus::Warning {
                reason: "No baseline statistics set for drift detection".to_owned(),
            };
        }

        SafetyStatus::Safe
    }

    /// Get comprehensive drift report for a model
    pub async fn get_drift_report(&self, model_id: &str) -> Option<HashMap<String, String>> {
        let mut report = HashMap::new();

        // Basic info
        report.insert("model_id".to_owned(), model_id.to_string());

        // Baseline statistics
        if let Some((mean, std)) = self.baseline_stats.get(model_id) {
            report.insert("baseline_mean".to_owned(), format!("{:.6}", mean));
            report.insert("baseline_std".to_owned(), format!("{:.6}", std));
        } else {
            report.insert("baseline_status".to_owned(), "Not set".to_owned());
            return Some(report);
        }

        // Current window statistics
        if let Some(window) = self.model_windows.get(model_id) {
            report.insert(
                "window_size".to_owned(),
                window.predictions.len().to_string(),
            );

            if !window.predictions.is_empty() {
                let current_mean = match window
                    .safe_mean(&window.predictions.iter().cloned().collect::<Vec<_>>())
                {
                    Ok(mean) => mean,
                    Err(_) => {
                        warn!("Failed to calculate current mean in drift report");
                        0.0
                    },
                };

                let current_variance = match window.safe_variance(
                    &window.predictions.iter().cloned().collect::<Vec<_>>(),
                    current_mean,
                ) {
                    Ok(var) => var,
                    Err(_) => {
                        warn!("Failed to calculate current variance in drift report");
                        0.0
                    },
                };

                let current_std = current_variance.sqrt();

                report.insert("current_mean".to_owned(), format!("{:.6}", current_mean));
                report.insert("current_std".to_owned(), format!("{:.6}", current_std));

                let distribution_drift = window.calculate_distribution_drift();
                report.insert(
                    "distribution_drift".to_owned(),
                    format!("{:.6}", distribution_drift),
                );

                if let Some(accuracy_drift) = window.calculate_accuracy_drift() {
                    report.insert(
                        "accuracy_drift".to_owned(),
                        format!("{:.6}", accuracy_drift),
                    );
                }
            }
        }

        // Drift history
        if let Some(history) = self.drift_history.get(model_id) {
            report.insert("total_drift_events".to_owned(), history.len().to_string());

            if let Some(latest) = history.last() {
                report.insert(
                    "latest_drift_score".to_owned(),
                    format!("{:.6}", latest.score),
                );
                report.insert(
                    "latest_drift_type".to_owned(),
                    format!("{:?}", latest.drift_type),
                );

                let elapsed = match latest.detected_at.elapsed() {
                    Ok(duration) => duration.as_secs(),
                    Err(_) => {
                        warn!(
                            "System time inconsistency in drift report for model {}",
                            model_id
                        );
                        0 // Default to 0 seconds if time calculation fails
                    },
                };
                report.insert(
                    "latest_drift_elapsed_seconds".to_owned(),
                    elapsed.to_string(),
                );
            }
        }

        // Last check
        if let Some(last_check) = self.last_check.get(model_id) {
            let elapsed = match last_check.elapsed() {
                Ok(duration) => duration.as_secs(),
                Err(_) => {
                    warn!(
                        "System time inconsistency in last check report for model {}",
                        model_id
                    );
                    0 // Default to 0 seconds if time calculation fails
                },
            };
            report.insert(
                "last_check_elapsed_seconds".to_owned(),
                elapsed.to_string(),
            );
        }

        Some(report)
    }

    /// Get overall safety status
    pub async fn get_status(&self) -> SafetyStatus {
        let mut warnings = Vec::new();
        let mut dangers = Vec::new();

        for model_id in self.model_windows.keys() {
            match self.get_drift_status(model_id).await {
                SafetyStatus::Warning { reason } => {
                    warnings.push(format!("{}: {}", model_id, reason))
                },
                SafetyStatus::Danger { reason } => {
                    dangers.push(format!("{}: {}", model_id, reason))
                },
                SafetyStatus::Critical { reason } => {
                    dangers.push(format!("{}: CRITICAL - {}", model_id, reason))
                },
                SafetyStatus::Safe => {},
            }
        }

        if !dangers.is_empty() {
            SafetyStatus::Danger {
                reason: format!(
                    "Drift detected in {} models: {}",
                    dangers.len(),
                    dangers.join("; ")
                ),
            }
        } else if !warnings.is_empty() {
            SafetyStatus::Warning {
                reason: format!(
                    "Drift warnings for {} models: {}",
                    warnings.len(),
                    warnings.join("; ")
                ),
            }
        } else {
            SafetyStatus::Safe
        }
    }

    /// Reset drift detection for a model
    pub async fn reset_model(&mut self, model_id: &str) {
        self.model_windows.remove(model_id);
        self.drift_history.remove(model_id);
        self.baseline_stats.remove(model_id);
        self.last_check.remove(model_id);

        info!("Drift detection reset for model: {}", model_id);
    }

    /// Reset all drift detection
    pub async fn reset_all(&mut self) {
        self.model_windows.clear();
        self.drift_history.clear();
        self.baseline_stats.clear();
        self.last_check.clear();

        info!("All drift detection data reset");
    }

    /// Get all monitored models
    pub fn get_monitored_models(&self) -> Vec<String> {
        self.model_windows.keys().cloned().collect()
    }
}

#[cfg(test)]
#[allow(
    clippy::assertions_on_constants,
    clippy::assertions_on_result_states,
    clippy::wildcard_enum_match_arm
)]
mod tests {
    use super::*;

    fn create_test_detector() -> ModelDriftDetector {
        ModelDriftDetector::new(&MLSafetyConfig::default())
    }

    #[tokio::test]
    async fn test_baseline_setting() {
        let mut detector = create_test_detector();

        let baseline = vec![1.0, 2.0, 3.0, 4.0, 5.0];
        let result = detector.set_baseline("test_model", &baseline).await;
        assert!(
            result.is_ok(),
            "Setting valid baseline should succeed: {:?}",
            result.err()
        );
        assert!(
            detector.baseline_stats.contains_key("test_model"),
            "Baseline should be stored after successful setting"
        );
    }

    #[tokio::test]
    async fn test_drift_detection() {
        let mut detector = create_test_detector();

        // Set baseline
        let baseline = vec![1.0; 100]; // Mean = 1.0, std ≈ 0
        let result = detector.set_baseline("test_model", &baseline).await;
        assert!(
            result.is_ok(),
            "Setting baseline should succeed: {:?}",
            result.err()
        );

        // Test with similar data (no drift)
        let similar_data = vec![1.1; 50];
        let drift_result = detector
            .update_and_check("test_model", &similar_data, None)
            .await;
        assert!(
            drift_result.is_ok(),
            "Drift check with similar data should succeed: {:?}",
            drift_result.err()
        );
        if let Ok(drift_score) = drift_result {
            assert!(
                drift_score < 0.05,
                "Drift score should be low for similar data: {}",
                drift_score
            );
        }
        // Test with very different data (should detect drift)
        let different_data = vec![10.0; 50];
        let drift_result = detector
            .update_and_check("test_model", &different_data, None)
            .await;
        assert!(
            drift_result.is_ok(),
            "Drift check with different data should succeed: {:?}",
            drift_result.err()
        );
        if let Ok(drift_score) = drift_result {
            assert!(
                drift_score > 0.5,
                "Drift score should be high for different data: {}",
                drift_score
            );
        }
    }

    #[tokio::test]
    async fn test_accuracy_drift() {
        let mut detector = create_test_detector();

        // Set baseline with good predictions
        let baseline = vec![0.8; 100];
        let result = detector.set_baseline("test_model", &baseline).await;
        assert!(
            result.is_ok(),
            "Setting baseline should succeed: {:?}",
            result.err()
        );

        // Test with good predictions and actuals
        let good_predictions = vec![0.8, 0.9, 0.7, 0.8, 0.9];
        let good_actuals = vec![1.0, 1.0, 1.0, 1.0, 1.0];

        match detector
            .update_and_check("test_model", &good_predictions, Some(&good_actuals))
            .await
        {
            Ok(drift_score) => {
                // Should have reasonable drift score for good predictions
                assert!(
                    drift_score < 1.0,
                    "Good predictions should have low drift score, got {}",
                    drift_score
                );
                assert!(
                    drift_score >= 0.0,
                    "Drift score should be non-negative, got {}",
                    drift_score
                );
            },
            Err(e) => assert!(false, "Accuracy drift check should succeed: {:?}", e),
        }
    }

    #[tokio::test]
    async fn test_invalid_inputs() {
        let mut detector = create_test_detector();

        // Empty predictions
        let result = detector.update_and_check("test_model", &[], None).await;
        assert!(result.is_err());

        // Mismatched lengths
        let predictions = vec![1.0, 2.0];
        let actuals = vec![1.0];
        let result = detector
            .update_and_check("test_model", &predictions, Some(&actuals))
            .await;
        assert!(result.is_err());

        // NaN prediction
        let nan_predictions = vec![1.0, f64::NAN];
        let result = detector
            .update_and_check("test_model", &nan_predictions, None)
            .await;
        assert!(result.is_err());
    }

    #[tokio::test]
    async fn test_drift_status() {
        let mut detector = create_test_detector();

        // No baseline set
        let status = detector.get_drift_status("test_model").await;
        match status {
            SafetyStatus::Warning { .. } => {}, // Expected
            _ => {
                tracing::error!("Expected warning for missing baseline, got: {:?}", status);
                assert!(false, "Expected warning for missing baseline");
            },
        }

        // Set baseline
        let result = detector.set_baseline("test_model", &vec![1.0; 100]).await;
        assert!(
            result.is_ok(),
            "Setting baseline should succeed: {:?}",
            result.err()
        );

        // Should be safe now
        let status = detector.get_drift_status("test_model").await;
        match status {
            SafetyStatus::Safe => {}, // Expected
            other => {
                assert_eq!(
                    std::mem::discriminant(&SafetyStatus::Safe),
                    std::mem::discriminant(&other),
                    "Expected SafetyStatus::Safe after baseline set, but got: {:?}. This indicates the drift detector failed to properly establish baseline measurements.", other
                );
            },
        }
    }

    #[tokio::test]
    async fn test_drift_report() {
        let mut detector = create_test_detector();

        // No baseline
        let report = detector.get_drift_report("test_model").await;
        assert!(
            report.is_some(),
            "Report should be available even without baseline"
        );
        let report_content = report.expect("Report should exist for test verification");
        assert!(
            report_content.contains_key("baseline_status"),
            "Report should contain baseline_status key"
        );

        // With baseline
        let result = detector.set_baseline("test_model", &vec![1.0; 100]).await;
        assert!(
            result.is_ok(),
            "Setting baseline for report test should succeed: {:?}",
            result.err()
        );
        let report = detector
            .get_drift_report("test_model")
            .await
            .expect("Drift report should be available after setting baseline");
        assert!(report.contains_key("baseline_mean"));
        assert!(report.contains_key("baseline_std"));
        assert!(report.contains_key("model_id"));
    }
}
