//! SOTA validation tests: IAAFT Surrogate Fatality + AUC-ROC Regime Classification.
//!
//! These implement the deferred SOTA assessments from the plan:
//! - Surrogate Fatality: Does the estimator reject H₀ that the series is structurally random?
//! - AUC-ROC: How well does each estimator discriminate trending vs mean-reverting regimes?
//!
//! Run: cargo test -p opendeviationbar-hurst --test surrogate_and_aucroc --release -- --nocapture

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

type EstFn = fn(&[f64]) -> f64;

const ESTIMATORS: &[(&str, EstFn)] = &[
    ("rs_simple", rssimple as EstFn),
    ("dfa", dfa as EstFn),
    ("wavelet", hurst_wavelet as EstFn),
    ("ghe_q1", ghe_q1 as EstFn),
    ("ghe_q2", ghe_q2 as EstFn),
    ("higuchi", hurst_higuchi as EstFn),
];

// ============================================================================
// Surrogate Fatality Test (IAAFT)
// ============================================================================

/// IAAFT surrogate fatality test.
///
/// For each estimator, generates IAAFT surrogates of fBm with known H=0.7
/// (persistent/trending). The surrogates preserve the amplitude spectrum
/// but destroy nonlinear temporal correlations. If the estimator detects
/// genuine nonlinear structure, H(original) should differ significantly
/// from H(surrogates), yielding |z| > 1.96 (p < 0.05).
///
/// Higher fatality_rate = estimator is more sensitive to temporal structure.
#[test]
fn surrogate_fatality_iaaft() {
    let n_windows = 50; // fBm samples to test
    let n_surrogates = 30; // IAAFT surrogates per window
    let window_size = 256;

    println!(
        "\n=== SURROGATE FATALITY TEST (IAAFT, {n_windows} windows × {n_surrogates} surrogates) ==="
    );
    println!(
        "{:<12} {:>10} {:>10} {:>10} {:>10}",
        "Estimator", "Fatal%", "Mean |z|", "Mean ΔH", "Verdict"
    );

    for &(est_name, est_fn) in ESTIMATORS {
        let mut fatalities = 0;
        let mut total_abs_z = 0.0;
        let mut total_delta_h = 0.0;

        for win_idx in 0..n_windows {
            // Generate fBm with H=0.7 (persistent)
            let fbm = generate_fbm(window_size, 0.7, win_idx as u64 + 700_000);
            let h_original = est_fn(&fbm);

            // Generate IAAFT surrogates
            let mut h_surrogates = Vec::with_capacity(n_surrogates);
            for surr_idx in 0..n_surrogates {
                let seed = (win_idx * 1000 + surr_idx) as u64 + 800_000;
                let surrogate = iaaft_surrogate(&fbm, seed, 100);
                h_surrogates.push(est_fn(&surrogate));
            }

            let mean_surr = h_surrogates.iter().sum::<f64>() / n_surrogates as f64;
            let std_surr = (h_surrogates
                .iter()
                .map(|&h| (h - mean_surr) * (h - mean_surr))
                .sum::<f64>()
                / n_surrogates as f64)
                .sqrt();

            // z-score
            let z = if std_surr > 1e-10 {
                (h_original - mean_surr) / std_surr
            } else {
                0.0
            };

            if z.abs() > 1.96 {
                fatalities += 1;
            }
            total_abs_z += z.abs();
            total_delta_h += (h_original - mean_surr).abs();
        }

        let fatality_rate = fatalities as f64 / n_windows as f64;
        let mean_abs_z = total_abs_z / n_windows as f64;
        let mean_delta_h = total_delta_h / n_windows as f64;

        let verdict = if fatality_rate > 0.5 {
            "SENSITIVE"
        } else if fatality_rate > 0.2 {
            "MODERATE"
        } else {
            "INSENSITIVE"
        };

        println!(
            "{:<12} {:>9.1}% {:>10.2} {:>10.4} {:>10}",
            est_name,
            fatality_rate * 100.0,
            mean_abs_z,
            mean_delta_h,
            verdict
        );
    }
}

// ============================================================================
// AUC-ROC Regime Classification
// ============================================================================

/// AUC-ROC regime classification test.
///
/// Treats regime detection as binary classification:
/// - Positive class: trending (fBm H=0.7)
/// - Negative class: mean-reverting (fBm H=0.3)
///
/// Uses H_est as the classifier score, sweeps threshold, computes ROC curve.
/// AUC = 1.0 means perfect separation, AUC = 0.5 means random.
#[test]
fn aucroc_regime_classification() {
    let n_samples = 200; // per class
    let window_size = 256;

    println!("\n=== AUC-ROC REGIME CLASSIFICATION (H=0.7 vs H=0.3, {n_samples} per class) ===");
    println!(
        "{:<12} {:>8} {:>10} {:>10} {:>12}",
        "Estimator", "AUC", "Youden J", "Threshold", "Verdict"
    );

    for &(est_name, est_fn) in ESTIMATORS {
        // Generate labeled samples
        let mut scores: Vec<(f64, bool)> = Vec::with_capacity(2 * n_samples);

        for i in 0..n_samples {
            let trending = generate_fbm(window_size, 0.7, i as u64 + 1_000_000);
            let reverting = generate_fbm(window_size, 0.3, i as u64 + 2_000_000);

            scores.push((est_fn(&trending), true)); // positive = trending
            scores.push((est_fn(&reverting), false)); // negative = reverting
        }

        // Sort by score (ascending) for threshold sweep
        scores.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap());

        let n_pos = n_samples as f64;
        let n_neg = n_samples as f64;

        // Compute ROC curve via threshold sweep
        let mut roc_points: Vec<(f64, f64)> = Vec::new(); // (FPR, TPR)
        let mut best_youden = f64::NEG_INFINITY;
        let mut best_threshold = 0.0;

        // Start: all predicted positive (threshold = -inf)
        let mut tp = n_pos;
        let mut fp = n_neg;

        roc_points.push((fp / n_neg, tp / n_pos));

        for i in 0..scores.len() {
            // Move threshold past scores[i] — this sample becomes negative
            if scores[i].1 {
                tp -= 1.0; // was true positive, now false negative
            } else {
                fp -= 1.0; // was false positive, now true negative
            }

            // Only add point if score changes (avoid duplicates)
            if i + 1 >= scores.len() || (scores[i + 1].0 - scores[i].0).abs() > 1e-15 {
                let fpr = fp / n_neg;
                let tpr = tp / n_pos;
                roc_points.push((fpr, tpr));

                let youden = tpr - fpr;
                if youden > best_youden {
                    best_youden = youden;
                    best_threshold = scores[i].0;
                }
            }
        }

        // Compute AUC via trapezoidal rule
        let mut auc = 0.0;
        for i in 1..roc_points.len() {
            let dx = (roc_points[i - 1].0 - roc_points[i].0).abs();
            let avg_y = (roc_points[i - 1].1 + roc_points[i].1) / 2.0;
            auc += dx * avg_y;
        }

        let verdict = if auc > 0.9 {
            "EXCELLENT"
        } else if auc > 0.7 {
            "GOOD"
        } else if auc > 0.5 {
            "POOR"
        } else {
            "RANDOM"
        };

        println!(
            "{:<12} {:>8.3} {:>10.3} {:>10.4} {:>12}",
            est_name, auc, best_youden, best_threshold, verdict
        );

        // AUC should be > 0.5 (better than random) for any reasonable estimator
        assert!(auc > 0.45, "{est_name} AUC ({auc:.3}) is worse than random");
    }
}
