//! Trend analysis for benchmark reports

use super::builder::BenchmarkReport;
use super::comparison::ComparisonResult;
use std::collections::HashMap;

/// Trend direction indicator
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum TrendDirection {
    Improving,
    Degrading,
    Stable,
    Volatile,
    Insufficient, // Not enough data points
}

/// Statistical analysis of benchmark trends
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct TrendAnalysis {
    pub metric: String,
    pub direction: TrendDirection,
    pub mean: f64,
    pub stddev: f64,
    pub min: f64,
    pub max: f64,
    pub slope: f64, // Linear regression slope (positive = improving)
    pub data_points: Vec<(String, f64)>, // (timestamp, value)
    pub sample_count: usize,
}

/// Detected performance regression
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct Regression {
    pub metric: String,
    pub timestamp: String,
    pub value: f64,
    pub baseline_mean: f64,
    pub delta_percent: f64,
    pub severity: RegressionSeverity,
}

/// Severity of performance regression
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum RegressionSeverity {
    Minor,    // 5-10% degradation
    Moderate, // 10-20% degradation
    Severe,   // 20-50% degradation
    Critical, // >50% degradation
}

/// Statistical summary of multiple reports
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct Statistics {
    pub cpu_mean: Option<f64>,
    pub cpu_stddev: Option<f64>,
    pub memory_mean: Option<f64>,
    pub memory_stddev: Option<f64>,
    pub disk_mean: Option<f64>,
    pub disk_stddev: Option<f64>,
    pub network_mean: Option<f64>,
    pub network_stddev: Option<f64>,
    pub sample_count: usize,
}

/// Analyze trend for a specific metric across multiple reports
pub fn analyze_trend(reports: &[BenchmarkReport], metric: &str) -> TrendAnalysis {
    let mut data_points = Vec::new();

    // Extract metric values from reports
    for report in reports {
        if let Some(value) = extract_metric_value(report, metric) {
            data_points.push((report.timestamp.clone(), value));
        }
    }

    if data_points.is_empty() {
        return TrendAnalysis {
            metric: metric.to_string(),
            direction: TrendDirection::Insufficient,
            mean: 0.0,
            stddev: 0.0,
            min: 0.0,
            max: 0.0,
            slope: 0.0,
            data_points: Vec::new(),
            sample_count: 0,
        };
    }

    let values: Vec<f64> = data_points.iter().map(|(_, v)| *v).collect();
    let mean = calculate_mean(&values);
    let stddev = calculate_stddev(&values, mean);
    let min = values.iter().copied().fold(f64::INFINITY, f64::min);
    let max = values.iter().copied().fold(f64::NEG_INFINITY, f64::max);
    let slope = calculate_slope(&values);

    let direction = determine_trend_direction(slope, stddev, mean);

    TrendAnalysis {
        metric: metric.to_string(),
        direction,
        mean,
        stddev,
        min,
        max,
        slope,
        data_points,
        sample_count: values.len(),
    }
}

/// Detect regressions across multiple reports
pub fn detect_regressions(reports: &[BenchmarkReport], threshold: f64) -> Vec<Regression> {
    if reports.len() < 2 {
        return Vec::new();
    }

    let mut regressions = Vec::new();
    let metrics = ["cpu", "memory", "disk", "network"];

    for metric in &metrics {
        // Calculate baseline (mean of first N reports)
        let baseline_count = (reports.len() / 2).max(1);
        let baseline_reports = &reports[..baseline_count];
        let baseline_values: Vec<f64> = baseline_reports
            .iter()
            .filter_map(|r| extract_metric_value(r, metric))
            .collect();

        if baseline_values.is_empty() {
            continue;
        }

        let baseline_mean = calculate_mean(&baseline_values);

        // Check recent reports for regressions
        let recent_reports = &reports[baseline_count..];
        for report in recent_reports {
            if let Some(value) = extract_metric_value(report, metric) {
                let delta_percent = ((value - baseline_mean) / baseline_mean) * 100.0;

                // Negative delta means degradation (lower is worse for performance metrics)
                if delta_percent < -threshold {
                    let severity = classify_regression_severity(delta_percent.abs());
                    regressions.push(Regression {
                        metric: metric.to_string(),
                        timestamp: report.timestamp.clone(),
                        value,
                        baseline_mean,
                        delta_percent,
                        severity,
                    });
                }
            }
        }
    }

    regressions
}

/// Calculate statistical summary across multiple reports
pub fn calculate_statistics(reports: &[BenchmarkReport]) -> Statistics {
    let cpu_values: Vec<f64> = reports
        .iter()
        .filter_map(|r| extract_metric_value(r, "cpu"))
        .collect();
    let memory_values: Vec<f64> = reports
        .iter()
        .filter_map(|r| extract_metric_value(r, "memory"))
        .collect();
    let disk_values: Vec<f64> = reports
        .iter()
        .filter_map(|r| extract_metric_value(r, "disk"))
        .collect();
    let network_values: Vec<f64> = reports
        .iter()
        .filter_map(|r| extract_metric_value(r, "network"))
        .collect();

    Statistics {
        cpu_mean: if !cpu_values.is_empty() {
            Some(calculate_mean(&cpu_values))
        } else {
            None
        },
        cpu_stddev: if !cpu_values.is_empty() {
            let mean = calculate_mean(&cpu_values);
            Some(calculate_stddev(&cpu_values, mean))
        } else {
            None
        },
        memory_mean: if !memory_values.is_empty() {
            Some(calculate_mean(&memory_values))
        } else {
            None
        },
        memory_stddev: if !memory_values.is_empty() {
            let mean = calculate_mean(&memory_values);
            Some(calculate_stddev(&memory_values, mean))
        } else {
            None
        },
        disk_mean: if !disk_values.is_empty() {
            Some(calculate_mean(&disk_values))
        } else {
            None
        },
        disk_stddev: if !disk_values.is_empty() {
            let mean = calculate_mean(&disk_values);
            Some(calculate_stddev(&disk_values, mean))
        } else {
            None
        },
        network_mean: if !network_values.is_empty() {
            Some(calculate_mean(&network_values))
        } else {
            None
        },
        network_stddev: if !network_values.is_empty() {
            let mean = calculate_mean(&network_values);
            Some(calculate_stddev(&network_values, mean))
        } else {
            None
        },
        sample_count: reports.len(),
    }
}

/// Compare current report against multiple baselines
pub fn compare_multi_baseline(
    current: &BenchmarkReport,
    baselines: HashMap<String, &BenchmarkReport>,
) -> HashMap<String, ComparisonResult> {
    let mut results = HashMap::new();

    for (name, baseline) in baselines {
        let comparison = super::comparison::compare_baselines(current, baseline);
        results.insert(name, comparison);
    }

    results
}

// Helper functions

fn extract_metric_value(report: &BenchmarkReport, metric: &str) -> Option<f64> {
    match metric {
        "cpu" => report
            .cpu_results
            .first()
            .map(|r| r.single_thread.combined_score),
        "memory" => report.memory_results.first().map(|r| r.read_bandwidth_gbps),
        "disk" => report
            .disk_results
            .first()
            .map(|r| r.sequential_read.throughput_mbps),
        "network" => report.network_results.first().map(|r| r.bandwidth_bps),
        _ => None,
    }
}

fn calculate_mean(values: &[f64]) -> f64 {
    if values.is_empty() {
        return 0.0;
    }
    values.iter().sum::<f64>() / values.len() as f64
}

fn calculate_stddev(values: &[f64], mean: f64) -> f64 {
    if values.len() < 2 {
        return 0.0;
    }
    let variance =
        values.iter().map(|v| (v - mean).powi(2)).sum::<f64>() / (values.len() - 1) as f64;
    variance.sqrt()
}

fn calculate_slope(values: &[f64]) -> f64 {
    if values.len() < 2 {
        return 0.0;
    }

    let n = values.len() as f64;
    let x_mean = (n - 1.0) / 2.0; // Indices: 0, 1, 2, ... n-1
    let y_mean = calculate_mean(values);

    let mut numerator = 0.0;
    let mut denominator = 0.0;

    for (i, &y) in values.iter().enumerate() {
        let x = i as f64;
        numerator += (x - x_mean) * (y - y_mean);
        denominator += (x - x_mean).powi(2);
    }

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

fn determine_trend_direction(slope: f64, stddev: f64, mean: f64) -> TrendDirection {
    if mean == 0.0 {
        return TrendDirection::Insufficient;
    }

    let coefficient_of_variation = stddev / mean;

    // High volatility
    if coefficient_of_variation > 0.3 {
        return TrendDirection::Volatile;
    }

    // Normalize slope by mean to get percentage change per sample
    let normalized_slope = (slope / mean) * 100.0;

    if normalized_slope > 2.0 {
        TrendDirection::Improving
    } else if normalized_slope < -2.0 {
        TrendDirection::Degrading
    } else {
        TrendDirection::Stable
    }
}

fn classify_regression_severity(delta_percent: f64) -> RegressionSeverity {
    if delta_percent >= 50.0 {
        RegressionSeverity::Critical
    } else if delta_percent >= 20.0 {
        RegressionSeverity::Severe
    } else if delta_percent >= 10.0 {
        RegressionSeverity::Moderate
    } else {
        RegressionSeverity::Minor
    }
}

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

    #[test]
    fn test_calculate_mean() {
        assert_eq!(calculate_mean(&[1.0, 2.0, 3.0, 4.0, 5.0]), 3.0);
        assert_eq!(calculate_mean(&[]), 0.0);
    }

    #[test]
    fn test_calculate_stddev() {
        let values = vec![2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0];
        let mean = calculate_mean(&values);
        let stddev = calculate_stddev(&values, mean);
        // Expected stddev is approximately 2.138
        assert!((stddev - 2.138).abs() < 0.01);
    }

    #[test]
    fn test_calculate_slope() {
        // Increasing trend
        let values = vec![1.0, 2.0, 3.0, 4.0, 5.0];
        assert!((calculate_slope(&values) - 1.0).abs() < 0.01);

        // Decreasing trend
        let values = vec![5.0, 4.0, 3.0, 2.0, 1.0];
        assert!((calculate_slope(&values) + 1.0).abs() < 0.01);

        // Stable
        let values = vec![3.0, 3.0, 3.0, 3.0, 3.0];
        assert!((calculate_slope(&values)).abs() < 0.01);
    }

    #[test]
    fn test_regression_severity() {
        assert_eq!(classify_regression_severity(5.0), RegressionSeverity::Minor);
        assert_eq!(
            classify_regression_severity(15.0),
            RegressionSeverity::Moderate
        );
        assert_eq!(
            classify_regression_severity(30.0),
            RegressionSeverity::Severe
        );
        assert_eq!(
            classify_regression_severity(60.0),
            RegressionSeverity::Critical
        );
    }
}
