//! Statistical Analysis for Anomaly Detection
//!
//! Implements various statistical methods for detecting anomalies in policy
//! evaluation data.

use chrono::Duration;

use super::{
    BaselineStatistics, DetectionConfig, ObservedStatistics, StatisticalEvidence, StatisticalMethod,
};

/// Statistical detector for anomalies
pub struct StatisticalDetector {
    config: DetectionConfig,
}

/// Statistical anomaly evidence
pub struct AnomalyEvidence {
    pub confidence: f64,
    pub method: StatisticalMethod,
    pub evidence: StatisticalEvidence,
}

impl StatisticalDetector {
    /// Create new statistical detector
    pub fn new(config: &DetectionConfig) -> Self {
        Self {
            config: config.clone(),
        }
    }

    /// Calculate baseline statistics from historical data
    pub fn calculate_baseline_stats(&self, values: &[f64]) -> BaselineStatistics {
        if values.is_empty() {
            return BaselineStatistics {
                mean: 0.0,
                median: 0.0,
                std_deviation: 0.0,
                percentile_95: 0.0,
                percentile_99: 0.0,
                min: 0.0,
                max: 0.0,
                count: 0,
            };
        }

        let mut sorted_values = values.to_vec();
        sorted_values.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));

        let mean = values.iter().sum::<f64>() / values.len() as f64;
        let median = self.calculate_percentile(&sorted_values, 50.0);
        let std_deviation = self.calculate_std_deviation(values, mean);
        let percentile_95 = self.calculate_percentile(&sorted_values, 95.0);
        let percentile_99 = self.calculate_percentile(&sorted_values, 99.0);
        let min = sorted_values[0];
        let max = sorted_values[sorted_values.len() - 1];

        BaselineStatistics {
            mean,
            median,
            std_deviation,
            percentile_95,
            percentile_99,
            min,
            max,
            count: values.len(),
        }
    }

    /// Detect anomaly using specified statistical method
    pub fn detect_anomaly(
        &self,
        observed_value: f64,
        baseline_stats: &BaselineStatistics,
        method: &StatisticalMethod,
    ) -> Option<StatisticalEvidence> {
        let threshold = self.config.thresholds.get(method).copied().unwrap_or(3.0);

        match method {
            StatisticalMethod::ZScore => {
                self.detect_zscore_anomaly(observed_value, baseline_stats, threshold)
            }
            StatisticalMethod::InterquartileRange => {
                self.detect_iqr_anomaly(observed_value, baseline_stats, threshold)
            }
            StatisticalMethod::IsolationForest => {
                self.detect_isolation_anomaly(observed_value, baseline_stats, threshold)
            }
            StatisticalMethod::MovingAverage => {
                self.detect_moving_average_anomaly(observed_value, baseline_stats, threshold)
            }
            _ => None, // Other methods not implemented yet
        }
    }

    /// Detect anomaly using Z-Score method
    fn detect_zscore_anomaly(
        &self,
        observed_value: f64,
        baseline_stats: &BaselineStatistics,
        threshold: f64,
    ) -> Option<StatisticalEvidence> {
        if baseline_stats.std_deviation == 0.0 {
            return None;
        }

        let z_score = (observed_value - baseline_stats.mean) / baseline_stats.std_deviation;
        let abs_z_score = z_score.abs();

        if abs_z_score > threshold {
            let confidence = (abs_z_score / (threshold * 2.0)).min(1.0);

            Some(StatisticalEvidence {
                method: StatisticalMethod::ZScore,
                score: abs_z_score,
                threshold,
                p_value: Some(self.calculate_p_value(z_score)),
                z_score: Some(z_score),
                iqr_factor: None,
                isolation_score: None,
                baseline_stats: baseline_stats.clone(),
                observed_stats: ObservedStatistics {
                    value: observed_value,
                    count: 1,
                    time_window: Duration::minutes(60),
                    percentile_rank: self.calculate_percentile_rank(observed_value, baseline_stats),
                },
            })
        } else {
            None
        }
    }

    /// Detect anomaly using Interquartile Range (IQR) method
    fn detect_iqr_anomaly(
        &self,
        observed_value: f64,
        baseline_stats: &BaselineStatistics,
        threshold: f64,
    ) -> Option<StatisticalEvidence> {
        // Approximate Q1 and Q3 from available statistics
        let q1 = baseline_stats.mean - (0.675 * baseline_stats.std_deviation); // Approximate Q1
        let q3 = baseline_stats.mean + (0.675 * baseline_stats.std_deviation); // Approximate Q3
        let iqr = q3 - q1;

        if iqr == 0.0 {
            return None;
        }

        let lower_bound = q1 - (threshold * iqr);
        let upper_bound = q3 + (threshold * iqr);

        if observed_value < lower_bound || observed_value > upper_bound {
            let distance_from_bounds = if observed_value < lower_bound {
                lower_bound - observed_value
            } else {
                observed_value - upper_bound
            };

            let iqr_factor = distance_from_bounds / iqr;
            let confidence = (iqr_factor / (threshold * 2.0)).min(1.0);

            Some(StatisticalEvidence {
                method: StatisticalMethod::InterquartileRange,
                score: iqr_factor,
                threshold,
                p_value: None,
                z_score: None,
                iqr_factor: Some(iqr_factor),
                isolation_score: None,
                baseline_stats: baseline_stats.clone(),
                observed_stats: ObservedStatistics {
                    value: observed_value,
                    count: 1,
                    time_window: Duration::minutes(60),
                    percentile_rank: self.calculate_percentile_rank(observed_value, baseline_stats),
                },
            })
        } else {
            None
        }
    }

    /// Detect anomaly using simplified Isolation Forest approach
    fn detect_isolation_anomaly(
        &self,
        observed_value: f64,
        baseline_stats: &BaselineStatistics,
        threshold: f64,
    ) -> Option<StatisticalEvidence> {
        // Simplified isolation score based on distance from mean relative to
        // distribution
        let normalized_distance = if baseline_stats.std_deviation > 0.0 {
            ((observed_value - baseline_stats.mean) / baseline_stats.std_deviation).abs()
        } else {
            0.0
        };

        // Simple isolation score: higher values are more isolated
        let isolation_score = 1.0 - (-normalized_distance / 2.0).exp();

        if isolation_score > threshold {
            let confidence = isolation_score;

            Some(StatisticalEvidence {
                method: StatisticalMethod::IsolationForest,
                score: isolation_score,
                threshold,
                p_value: None,
                z_score: None,
                iqr_factor: None,
                isolation_score: Some(isolation_score),
                baseline_stats: baseline_stats.clone(),
                observed_stats: ObservedStatistics {
                    value: observed_value,
                    count: 1,
                    time_window: Duration::minutes(60),
                    percentile_rank: self.calculate_percentile_rank(observed_value, baseline_stats),
                },
            })
        } else {
            None
        }
    }

    /// Detect anomaly using moving average method
    fn detect_moving_average_anomaly(
        &self,
        observed_value: f64,
        baseline_stats: &BaselineStatistics,
        threshold: f64,
    ) -> Option<StatisticalEvidence> {
        let deviation_ratio = if baseline_stats.mean > 0.0 {
            (observed_value / baseline_stats.mean - 1.0).abs()
        } else if observed_value > 0.0 {
            1.0 // Complete deviation from zero baseline
        } else {
            0.0 // Both are zero
        };

        if deviation_ratio > threshold {
            let confidence = (deviation_ratio / (threshold * 2.0)).min(1.0);

            Some(StatisticalEvidence {
                method: StatisticalMethod::MovingAverage,
                score: deviation_ratio,
                threshold,
                p_value: None,
                z_score: None,
                iqr_factor: None,
                isolation_score: None,
                baseline_stats: baseline_stats.clone(),
                observed_stats: ObservedStatistics {
                    value: observed_value,
                    count: 1,
                    time_window: Duration::minutes(60),
                    percentile_rank: self.calculate_percentile_rank(observed_value, baseline_stats),
                },
            })
        } else {
            None
        }
    }

    /// Calculate percentile from sorted values
    fn calculate_percentile(&self, sorted_values: &[f64], percentile: f64) -> f64 {
        if sorted_values.is_empty() {
            return 0.0;
        }

        let index = (percentile / 100.0) * (sorted_values.len() - 1) as f64;
        let lower_index = index.floor() as usize;
        let upper_index = index.ceil() as usize;

        if lower_index == upper_index {
            sorted_values[lower_index]
        } else {
            let weight = index - lower_index as f64;
            sorted_values[lower_index] * (1.0 - weight) + sorted_values[upper_index] * weight
        }
    }

    /// Calculate standard deviation
    fn calculate_std_deviation(&self, values: &[f64], mean: f64) -> f64 {
        if values.len() <= 1 {
            return 0.0;
        }

        let variance = values
            .iter()
            .map(|value| (value - mean).powi(2))
            .sum::<f64>()
            / (values.len() - 1) as f64;

        variance.sqrt()
    }

    /// Calculate p-value from z-score (simplified)
    fn calculate_p_value(&self, z_score: f64) -> f64 {
        // Simplified p-value calculation using approximation
        // This is a rough approximation - in production, use a proper statistical
        // library
        let abs_z = z_score.abs();

        if abs_z > 6.0 {
            0.0
        } else if abs_z > 3.0 {
            0.001
        } else if abs_z > 2.5 {
            0.01
        } else if abs_z > 1.96 {
            0.05
        } else if abs_z > 1.64 {
            0.1
        } else {
            0.2
        }
    }

    /// Calculate percentile rank of observed value
    fn calculate_percentile_rank(
        &self,
        observed_value: f64,
        baseline_stats: &BaselineStatistics,
    ) -> f64 {
        if baseline_stats.std_deviation == 0.0 {
            return if observed_value == baseline_stats.mean {
                50.0
            } else {
                100.0
            };
        }

        let z_score = (observed_value - baseline_stats.mean) / baseline_stats.std_deviation;

        // Approximate percentile rank from z-score
        match z_score {
            z if z > 3.0 => 99.9,
            z if z > 2.5 => 99.4,
            z if z > 2.0 => 97.7,
            z if z > 1.5 => 93.3,
            z if z > 1.0 => 84.1,
            z if z > 0.5 => 69.1,
            z if z > 0.0 => 50.0 + (z * 19.1),
            z if z > -0.5 => 50.0 + (z * 19.1),
            z if z > -1.0 => 30.9 + ((z + 0.5) * 38.2),
            z if z > -1.5 => 15.9 + ((z + 1.0) * 30.8),
            z if z > -2.0 => 6.7 + ((z + 1.5) * 18.4),
            z if z > -2.5 => 2.3 + ((z + 2.0) * 8.8),
            z if z > -3.0 => 0.6 + ((z + 2.5) * 3.4),
            _ => 0.1,
        }
    }

    /// Run ensemble detection using multiple methods
    pub fn ensemble_detect(
        &self,
        observed_value: f64,
        baseline_stats: &BaselineStatistics,
    ) -> Option<StatisticalEvidence> {
        let methods = vec![
            StatisticalMethod::ZScore,
            StatisticalMethod::InterquartileRange,
            StatisticalMethod::IsolationForest,
        ];

        let mut detections = Vec::new();
        let mut total_confidence = 0.0;
        let mut detection_count = 0;

        for method in &methods {
            if let Some(evidence) = self.detect_anomaly(observed_value, baseline_stats, method) {
                let confidence = match method {
                    StatisticalMethod::ZScore => (evidence.score / evidence.threshold).min(1.0),
                    StatisticalMethod::InterquartileRange => {
                        evidence.iqr_factor.unwrap_or(0.0) / evidence.threshold
                    }
                    StatisticalMethod::IsolationForest => evidence.isolation_score.unwrap_or(0.0),
                    _ => 0.0,
                };

                detections.push((method.clone(), evidence, confidence));
                total_confidence += confidence;
                detection_count += 1;
            }
        }

        if detection_count > 0 {
            let avg_confidence = total_confidence / detection_count as f64;

            // Use the strongest detection as the primary evidence
            let best_detection = detections
                .into_iter()
                .max_by(|a, b| a.2.partial_cmp(&b.2).unwrap_or(std::cmp::Ordering::Equal));

            if let Some((_, mut evidence, confidence)) = best_detection {
                evidence.method = StatisticalMethod::Ensemble;
                evidence.score = avg_confidence;
                evidence.threshold = self.config.confidence_threshold;

                if confidence >= self.config.confidence_threshold {
                    Some(evidence)
                } else {
                    None
                }
            } else {
                None
            }
        } else {
            None
        }
    }

    /// Calculate correlation between two time series
    pub fn calculate_correlation(&self, series1: &[f64], series2: &[f64]) -> f64 {
        if series1.len() != series2.len() || series1.is_empty() {
            return 0.0;
        }

        let mean1 = series1.iter().sum::<f64>() / series1.len() as f64;
        let mean2 = series2.iter().sum::<f64>() / series2.len() as f64;

        let numerator: f64 = series1
            .iter()
            .zip(series2.iter())
            .map(|(x1, x2)| (x1 - mean1) * (x2 - mean2))
            .sum();

        let sum_sq1: f64 = series1.iter().map(|x| (x - mean1).powi(2)).sum();
        let sum_sq2: f64 = series2.iter().map(|x| (x - mean2).powi(2)).sum();

        let denominator = (sum_sq1 * sum_sq2).sqrt();

        if denominator == 0.0 {
            0.0
        } else {
            numerator / denominator
        }
    }

    /// Perform trend analysis
    pub fn analyze_trend(&self, values: &[f64]) -> TrendAnalysis {
        if values.len() < 3 {
            return TrendAnalysis {
                direction: TrendDirection::Unknown,
                strength: 0.0,
                slope: 0.0,
                r_squared: 0.0,
            };
        }

        // Simple linear regression for trend analysis
        let n = values.len() as f64;
        let x_values: Vec<f64> = (0..values.len()).map(|i| i as f64).collect();

        let sum_x = x_values.iter().sum::<f64>();
        let sum_y = values.iter().sum::<f64>();
        let sum_xy = x_values
            .iter()
            .zip(values.iter())
            .map(|(x, y)| x * y)
            .sum::<f64>();
        let sum_x_sq = x_values.iter().map(|x| x * x).sum::<f64>();

        let slope = (n * sum_xy - sum_x * sum_y) / (n * sum_x_sq - sum_x * sum_x);
        let intercept = (sum_y - slope * sum_x) / n;

        // Calculate R-squared
        let y_mean = sum_y / n;
        let ss_tot = values.iter().map(|y| (y - y_mean).powi(2)).sum::<f64>();
        let ss_res = x_values
            .iter()
            .zip(values.iter())
            .map(|(x, y)| (y - (slope * x + intercept)).powi(2))
            .sum::<f64>();

        let r_squared = if ss_tot > 0.0 {
            1.0 - (ss_res / ss_tot)
        } else {
            0.0
        };

        let direction = if slope > 0.01 {
            TrendDirection::Increasing
        } else if slope < -0.01 {
            TrendDirection::Decreasing
        } else {
            TrendDirection::Stable
        };

        TrendAnalysis {
            direction,
            strength: r_squared,
            slope,
            r_squared,
        }
    }
}

/// Trend analysis result
#[derive(Debug, Clone)]
pub struct TrendAnalysis {
    pub direction: TrendDirection,
    pub strength: f64,
    pub slope: f64,
    pub r_squared: f64,
}

/// Trend direction
#[derive(Debug, Clone)]
pub enum TrendDirection {
    Increasing,
    Decreasing,
    Stable,
    Unknown,
}
