//! Statistical accuracy validation for all Hurst estimators.
//!
//! Uses fBm ground truth with 500 samples per (H, estimator) combination.
//! Reports bias, RMSE, and standard deviation with 95% CI.
//!
//! Run: cargo nextest run -p opendeviationbar-hurst -- accuracy

use opendeviationbar_hurst::fbm::generate_fbm;
use opendeviationbar_hurst::{dfa, ghe_q1, ghe_q2, hurst_higuchi, hurst_wavelet, rssimple};

const N_SAMPLES: usize = 500;
const WINDOW_SIZE: usize = 256;

struct EstimatorResult {
    name: &'static str,
    bias: f64,
    rmse: f64,
    std_dev: f64,
}

fn evaluate_estimator(
    name: &'static str,
    func: fn(&[f64]) -> f64,
    h_true: f64,
    n: usize,
    n_samples: usize,
) -> EstimatorResult {
    let mut estimates = Vec::with_capacity(n_samples);

    for seed in 0..n_samples {
        let fbm = generate_fbm(n, h_true, seed as u64 + 100_000);
        let h_est = func(&fbm);
        estimates.push(h_est);
    }

    let mean: f64 = estimates.iter().sum::<f64>() / n_samples as f64;
    let bias = mean - h_true;
    let variance: f64 = estimates
        .iter()
        .map(|&h| (h - mean) * (h - mean))
        .sum::<f64>()
        / n_samples as f64;
    let std_dev = variance.sqrt();
    let mse: f64 = estimates
        .iter()
        .map(|&h| (h - h_true) * (h - h_true))
        .sum::<f64>()
        / n_samples as f64;
    let rmse = mse.sqrt();

    EstimatorResult {
        name,
        bias,
        rmse,
        std_dev,
    }
}

#[test]
fn accuracy_comprehensive_report() {
    let h_values = [0.2, 0.3, 0.5, 0.7, 0.8];

    let estimators: Vec<(&str, fn(&[f64]) -> f64)> = vec![
        ("rs_simple", rssimple as fn(&[f64]) -> f64),
        ("dfa", dfa),
        ("wavelet", hurst_wavelet),
        ("ghe_q1", ghe_q1),
        ("ghe_q2", ghe_q2),
        ("higuchi", hurst_higuchi),
    ];

    println!("\n{}", "=".repeat(80));
    println!("HURST ESTIMATOR ACCURACY REPORT");
    println!("Window size: {WINDOW_SIZE}, Samples per combination: {N_SAMPLES}");
    println!("{}\n", "=".repeat(80));

    for &h_true in &h_values {
        println!("--- H_true = {h_true:.1} ---");
        println!(
            "{:<15} {:>10} {:>10} {:>10} {:>10}",
            "Estimator", "Mean H", "Bias", "RMSE", "Std"
        );

        let mut results: Vec<EstimatorResult> = estimators
            .iter()
            .map(|&(name, func)| evaluate_estimator(name, func, h_true, WINDOW_SIZE, N_SAMPLES))
            .collect();

        results.sort_by(|a, b| a.rmse.partial_cmp(&b.rmse).unwrap());

        for r in &results {
            println!(
                "{:<15} {:>10.4} {:>+10.4} {:>10.4} {:>10.4}",
                r.name,
                h_true + r.bias,
                r.bias,
                r.rmse,
                r.std_dev,
            );
        }
        println!();
    }

    // Compute composite scores
    println!("--- COMPOSITE SCORE (lower RMSE is better) ---");
    println!(
        "{:<15} {:>10} {:>10} {:>10}",
        "Estimator", "Avg RMSE", "Avg |Bias|", "Avg Std"
    );

    for &(name, func) in &estimators {
        let mut total_rmse = 0.0;
        let mut total_abs_bias = 0.0;
        let mut total_std = 0.0;

        for &h_true in &h_values {
            let r = evaluate_estimator(name, func, h_true, WINDOW_SIZE, N_SAMPLES);
            total_rmse += r.rmse;
            total_abs_bias += r.bias.abs();
            total_std += r.std_dev;
        }

        let n_h = h_values.len() as f64;
        println!(
            "{:<15} {:>10.4} {:>10.4} {:>10.4}",
            name,
            total_rmse / n_h,
            total_abs_bias / n_h,
            total_std / n_h,
        );
    }
}

#[test]
fn accuracy_all_estimators_produce_finite_values() {
    let h_values = [0.2, 0.3, 0.5, 0.7, 0.8];
    let estimators: Vec<(&str, fn(&[f64]) -> f64)> = vec![
        ("rs_simple", rssimple as fn(&[f64]) -> f64),
        ("dfa", dfa),
        ("wavelet", hurst_wavelet),
        ("ghe_q1", ghe_q1),
        ("ghe_q2", ghe_q2),
        ("higuchi", hurst_higuchi),
    ];

    for &h_true in &h_values {
        for &(name, func) in &estimators {
            for seed in 0..50u64 {
                let fbm = generate_fbm(WINDOW_SIZE, h_true, seed + 200_000);
                let h_est = func(&fbm);
                assert!(
                    h_est.is_finite(),
                    "{name} returned non-finite H={h_est} for H_true={h_true}, seed={seed}"
                );
            }
        }
    }
}

#[test]
fn accuracy_monotonicity_ordering() {
    // H(trending) > H(random) > H(mean_reverting) should hold most of the time
    let estimators: Vec<(&str, fn(&[f64]) -> f64)> = vec![
        ("rs_simple", rssimple as fn(&[f64]) -> f64),
        ("dfa", dfa),
        ("wavelet", hurst_wavelet),
        ("ghe_q1", ghe_q1),
        ("ghe_q2", ghe_q2),
        ("higuchi", hurst_higuchi),
    ];

    let n_trials = 100;

    println!("\n--- MONOTONICITY TEST (H=0.8 > H=0.5 > H=0.2) ---");
    println!("{:<15} {:>15} {:>15}", "Estimator", "Violations", "Rate");

    for &(name, func) in &estimators {
        let mut violations = 0;
        for trial in 0..n_trials {
            let seed_base = trial as u64 * 3 + 300_000;
            let trending = generate_fbm(WINDOW_SIZE, 0.8, seed_base);
            let random = generate_fbm(WINDOW_SIZE, 0.5, seed_base + 1);
            let reverting = generate_fbm(WINDOW_SIZE, 0.2, seed_base + 2);

            let h_t = func(&trending);
            let h_r = func(&random);
            let h_v = func(&reverting);

            if h_t <= h_r || h_r <= h_v {
                violations += 1;
            }
        }

        let rate = violations as f64 / n_trials as f64;
        println!("{name:<15} {violations:>15} {rate:>15.2}");

        // Most estimators should have < 60% violation rate.
        // R/S simple is known to have poor gradient separation (Zhang et al. 2024).
        assert!(
            rate < 0.60,
            "{name} has too many monotonicity violations: {rate:.2}"
        );
    }
}

#[test]
fn accuracy_gradient_informativeness() {
    // Measures how well each estimator distinguishes different H values
    let h_values = [0.2, 0.3, 0.5, 0.7, 0.8];
    let n_samples = 200;

    let estimators: Vec<(&str, fn(&[f64]) -> f64)> = vec![
        ("rs_simple", rssimple as fn(&[f64]) -> f64),
        ("dfa", dfa),
        ("wavelet", hurst_wavelet),
        ("ghe_q1", ghe_q1),
        ("ghe_q2", ghe_q2),
        ("higuchi", hurst_higuchi),
    ];

    println!("\n--- GRADIENT INFORMATIVENESS ---");
    println!(
        "{:<15} {:>10} {:>10} {:>10}",
        "Estimator", "Slope", "R²", "Cohen_d"
    );

    for &(name, func) in &estimators {
        let mut h_means = Vec::with_capacity(h_values.len());

        for &h_true in &h_values {
            let sum: f64 = (0..n_samples)
                .map(|seed| {
                    let fbm = generate_fbm(WINDOW_SIZE, h_true, seed as u64 + 400_000);
                    func(&fbm)
                })
                .sum();
            h_means.push(sum / n_samples as f64);
        }

        // Linear regression: H_est = a + b * H_true
        let n_h = h_values.len() as f64;
        let x_mean: f64 = h_values.iter().sum::<f64>() / n_h;
        let y_mean: f64 = h_means.iter().sum::<f64>() / n_h;

        let mut sxy = 0.0;
        let mut sxx = 0.0;
        let mut ss_res = 0.0;
        let mut ss_tot = 0.0;

        for (i, &h_true) in h_values.iter().enumerate() {
            sxy += (h_true - x_mean) * (h_means[i] - y_mean);
            sxx += (h_true - x_mean) * (h_true - x_mean);
        }

        let slope = if sxx > 0.0 { sxy / sxx } else { 0.0 };
        let intercept = y_mean - slope * x_mean;

        for (i, &h_true) in h_values.iter().enumerate() {
            let predicted = intercept + slope * h_true;
            ss_res += (h_means[i] - predicted) * (h_means[i] - predicted);
            ss_tot += (h_means[i] - y_mean) * (h_means[i] - y_mean);
        }

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

        // Cohen's d between trending (H=0.8) and mean-reverting (H=0.2)
        let h_trending_samples: Vec<f64> = (0..n_samples)
            .map(|seed| {
                let fbm = generate_fbm(WINDOW_SIZE, 0.8, seed as u64 + 500_000);
                func(&fbm)
            })
            .collect();
        let h_reverting_samples: Vec<f64> = (0..n_samples)
            .map(|seed| {
                let fbm = generate_fbm(WINDOW_SIZE, 0.2, seed as u64 + 600_000);
                func(&fbm)
            })
            .collect();

        let mean_t: f64 = h_trending_samples.iter().sum::<f64>() / n_samples as f64;
        let mean_r: f64 = h_reverting_samples.iter().sum::<f64>() / n_samples as f64;
        let var_t: f64 = h_trending_samples
            .iter()
            .map(|&h| (h - mean_t) * (h - mean_t))
            .sum::<f64>()
            / n_samples as f64;
        let var_r: f64 = h_reverting_samples
            .iter()
            .map(|&h| (h - mean_r) * (h - mean_r))
            .sum::<f64>()
            / n_samples as f64;
        let pooled_std = ((var_t + var_r) / 2.0).sqrt();
        let cohens_d = if pooled_std > 0.0 {
            (mean_t - mean_r) / pooled_std
        } else {
            0.0
        };

        println!("{name:<15} {slope:>10.4} {r_squared:>10.4} {cohens_d:>10.2}");

        // Slope should be positive (higher H_true → higher H_est)
        assert!(
            slope > 0.0,
            "{name} has non-positive gradient slope: {slope}"
        );
    }
}
