//! Comprehensive Hurst estimator comparison benchmark
//!
//! Evaluates 7 estimators across 6 dimensions using real aggTrade data
//! from BTCUSDT (863K trades), ETHUSDT (401K trades), and SOLUSDT (272K trades).
//!
//! Dimensions:
//! 1. Accuracy (fBm ground truth, 200 samples per H)
//! 2. Speed (real multi-symbol sliding windows)
//! 3. Gradient informativeness (regime separation)
//! 4. Surrogate fatality (structure detection via shuffle)
//! 5. Extrapolation reliability (finite-sample convergence)
//! 6. Monotonicity (ordering correctness)
//!
//! Run: cargo bench --bench hurst_estimator_comparison

use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion};
use opendeviationbar_hurst::fbm::generate_fbm;
use opendeviationbar_hurst::{
    dfa, ghe_q1, ghe_q2, hurst_higuchi, hurst_wavelet, rs_corrected, rssimple,
};
use std::time::Duration;

/// All estimators under test
struct Estimator {
    name: &'static str,
    func: fn(&[f64]) -> f64,
}

/// rs_corrected takes Vec and can panic on degenerate windows (TooSteep error).
/// Wrap with catch_unwind for benchmark safety.
fn rs_corrected_wrapper(x: &[f64]) -> f64 {
    let v = x.to_vec();
    std::panic::catch_unwind(|| rs_corrected(v)).unwrap_or(0.5)
}

fn estimators() -> Vec<Estimator> {
    vec![
        Estimator {
            name: "rs_simple",
            func: rssimple,
        },
        Estimator {
            name: "rs_corrected",
            func: rs_corrected_wrapper,
        },
        Estimator {
            name: "dfa",
            func: dfa,
        },
        Estimator {
            name: "wavelet",
            func: hurst_wavelet,
        },
        Estimator {
            name: "ghe_q1",
            func: ghe_q1,
        },
        Estimator {
            name: "ghe_q2",
            func: ghe_q2,
        },
        Estimator {
            name: "higuchi",
            func: hurst_higuchi,
        },
    ]
}

// ============================================================================
// Real data loading — multi-symbol
// ============================================================================

/// Load prices from a Binance Vision aggTrades CSV (no header).
/// Columns: agg_trade_id, price, volume, first_trade_id, last_trade_id, timestamp, is_buyer_maker, is_best_match
fn load_prices_from_csv(path: &str) -> Vec<f64> {
    let mut rdr = csv::ReaderBuilder::new()
        .has_headers(false)
        .from_path(path)
        .unwrap_or_else(|_| panic!("Failed to open {path}"));

    let mut prices = Vec::with_capacity(900_000);
    for result in rdr.records() {
        let record = result.expect("Bad CSV record");
        if let Some(price_str) = record.get(1) {
            if let Ok(price) = price_str.parse::<f64>() {
                prices.push(price);
            }
        }
    }
    prices
}

/// Load the 10K sample (always available, tracked in git)
fn load_btcusdt_10k() -> Vec<f64> {
    let path = concat!(
        env!("CARGO_MANIFEST_DIR"),
        "/tests/fixtures/BTCUSDT-aggTrades-sample-10k.csv"
    );
    let mut rdr = csv::ReaderBuilder::new()
        .has_headers(true)
        .from_path(path)
        .expect("Failed to open BTCUSDT 10K fixture");

    let mut prices = Vec::with_capacity(10_000);
    for result in rdr.records() {
        let record = result.expect("Bad CSV record");
        let price: f64 = record
            .get(1)
            .expect("No price column")
            .parse()
            .expect("Bad price");
        prices.push(price);
    }
    prices
}

/// Try to load full-day CSVs; fall back to 10K sample if not available.
/// Returns (symbol_name, prices) pairs.
fn load_all_symbols() -> Vec<(&'static str, Vec<f64>)> {
    let manifest = env!("CARGO_MANIFEST_DIR");
    let mut symbols = Vec::new();

    let btc_full = format!("{manifest}/tests/fixtures/BTCUSDT-aggTrades-2024-01-01.csv");
    if std::path::Path::new(&btc_full).exists() {
        symbols.push(("BTCUSDT", load_prices_from_csv(&btc_full)));
    } else {
        symbols.push(("BTCUSDT_10k", load_btcusdt_10k()));
    }

    let eth_full = format!("{manifest}/tests/fixtures/ETHUSDT-aggTrades-2024-01-01.csv");
    if std::path::Path::new(&eth_full).exists() {
        symbols.push(("ETHUSDT", load_prices_from_csv(&eth_full)));
    }

    let sol_full = format!("{manifest}/tests/fixtures/SOLUSDT-aggTrades-2024-01-01.csv");
    if std::path::Path::new(&sol_full).exists() {
        symbols.push(("SOLUSDT", load_prices_from_csv(&sol_full)));
    }

    symbols
}

// ============================================================================
// Group 1: Accuracy — fBm ground truth
// ============================================================================

fn bench_accuracy(c: &mut Criterion) {
    let mut group = c.benchmark_group("hurst_accuracy");
    group.sample_size(20);
    group.measurement_time(Duration::from_secs(5));

    let h_values = [0.2, 0.3, 0.5, 0.7, 0.8];
    let n = 256;
    let n_samples = 200;

    for est in estimators() {
        for &h_true in &h_values {
            group.bench_with_input(
                BenchmarkId::new(format!("{}_H{}", est.name, (h_true * 10.0) as u32), n),
                &h_true,
                |b, &h_true| {
                    let samples: Vec<Vec<f64>> = (0..n_samples)
                        .map(|seed| generate_fbm(n, h_true, seed as u64 + 1000))
                        .collect();

                    b.iter(|| {
                        let mut sum = 0.0;
                        for sample in &samples {
                            sum += (est.func)(black_box(sample));
                        }
                        black_box(sum)
                    });
                },
            );
        }
    }

    group.finish();
}

// ============================================================================
// Group 2: Speed — real multi-symbol sliding windows
// ============================================================================

fn bench_speed(c: &mut Criterion) {
    let symbols = load_all_symbols();
    let mut group = c.benchmark_group("hurst_speed");
    group.sample_size(30);
    group.measurement_time(Duration::from_secs(5));

    let window_sizes = [64, 128, 256, 512];

    for est in estimators() {
        for &win in &window_sizes {
            for (sym_name, prices) in &symbols {
                if win > prices.len() {
                    continue;
                }

                // Use non-overlapping windows for independence, cap at 500 for benchmark speed
                let windows: Vec<&[f64]> =
                    prices.windows(win).step_by(win).take(500).collect();

                group.bench_with_input(
                    BenchmarkId::new(format!("{}_{}", est.name, sym_name), win),
                    &win,
                    |b, _| {
                        b.iter(|| {
                            let mut sum = 0.0;
                            for w in &windows {
                                sum += (est.func)(black_box(w));
                            }
                            black_box(sum)
                        });
                    },
                );
            }
        }
    }

    group.finish();
}

// ============================================================================
// Group 3: Gradient informativeness
// ============================================================================

fn bench_gradient(c: &mut Criterion) {
    let mut group = c.benchmark_group("hurst_gradient");
    group.sample_size(10);
    group.measurement_time(Duration::from_secs(10));

    let h_values = [0.2, 0.3, 0.5, 0.7, 0.8];
    let n = 256;
    let n_samples = 200;

    for est in estimators() {
        group.bench_function(est.name, |b| {
            let all_samples: Vec<Vec<Vec<f64>>> = h_values
                .iter()
                .map(|&h| {
                    (0..n_samples)
                        .map(|seed| generate_fbm(n, h, seed as u64 + 5000))
                        .collect()
                })
                .collect();

            b.iter(|| {
                let mut h_means = Vec::with_capacity(h_values.len());

                for samples in &all_samples {
                    let sum: f64 = samples.iter().map(|s| (est.func)(black_box(s))).sum();
                    h_means.push(sum / n_samples as f64);
                }

                // Gradient slope: d(H_est)/d(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;
                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);
                }

                black_box(if sxx > 0.0 { sxy / sxx } else { 0.0 })
            });
        });
    }

    group.finish();
}

// ============================================================================
// Group 4: Surrogate fatality (shuffle-based, real multi-symbol data)
// ============================================================================

fn shuffle(data: &mut [f64], seed: u64) {
    let n = data.len();
    let mut state = seed;
    for i in (1..n).rev() {
        state = state
            .wrapping_mul(6_364_136_223_846_793_005)
            .wrapping_add(1);
        let j = (state >> 33) as usize % (i + 1);
        data.swap(i, j);
    }
}

fn bench_surrogate(c: &mut Criterion) {
    let symbols = load_all_symbols();
    let mut group = c.benchmark_group("hurst_surrogate");
    group.sample_size(10);
    group.measurement_time(Duration::from_secs(15));

    let win = 256;
    let n_windows = 50;
    let n_surrogates = 30;

    for est in estimators() {
        for (sym_name, prices) in &symbols {
            if win > prices.len() {
                continue;
            }

            // Non-overlapping windows from real data
            let windows: Vec<Vec<f64>> = prices
                .windows(win)
                .step_by(win)
                .take(n_windows)
                .map(<[f64]>::to_vec)
                .collect();

            group.bench_function(format!("{}_{}", est.name, sym_name), |b| {
                b.iter(|| {
                    let mut fatality_count = 0;

                    for window in &windows {
                        let h_original = (est.func)(black_box(window));

                        let mut h_surr_sum = 0.0;
                        let mut h_surr_sq_sum = 0.0;

                        for s in 0..n_surrogates {
                            let mut surrogate = window.clone();
                            shuffle(&mut surrogate, (s as u64 + 1) * 7919);
                            let h_surr = (est.func)(black_box(&surrogate));
                            h_surr_sum += h_surr;
                            h_surr_sq_sum += h_surr * h_surr;
                        }

                        let mean_surr = h_surr_sum / n_surrogates as f64;
                        let var_surr =
                            h_surr_sq_sum / n_surrogates as f64 - mean_surr * mean_surr;
                        let std_surr = var_surr.max(1e-10).sqrt();

                        let z = (h_original - mean_surr) / std_surr;
                        if z.abs() > 1.96 {
                            fatality_count += 1;
                        }
                    }

                    black_box(fatality_count)
                });
            });
        }
    }

    group.finish();
}

// ============================================================================
// Group 5: Extrapolation reliability
// ============================================================================

fn bench_extrapolation(c: &mut Criterion) {
    let mut group = c.benchmark_group("hurst_extrapolation");
    group.sample_size(10);
    group.measurement_time(Duration::from_secs(10));

    let window_sizes = [32, 64, 128, 256, 512];
    let h_true = 0.5;
    let n_samples = 100;

    for est in estimators() {
        group.bench_function(est.name, |b| {
            let all_samples: Vec<Vec<Vec<f64>>> = window_sizes
                .iter()
                .map(|&n| {
                    (0..n_samples)
                        .map(|seed| generate_fbm(n, h_true, seed as u64 + 9000))
                        .collect()
                })
                .collect();

            b.iter(|| {
                let mut rmse_values = Vec::with_capacity(window_sizes.len());

                for samples in &all_samples {
                    let mut sq_err_sum = 0.0;
                    for sample in samples {
                        let h_est = (est.func)(black_box(sample));
                        let err = h_est - h_true;
                        sq_err_sum += err * err;
                    }
                    rmse_values.push((sq_err_sum / n_samples as f64).sqrt());
                }

                black_box(rmse_values)
            });
        });
    }

    group.finish();
}

// ============================================================================
// Group 6: Monotonicity — ordering correctness
// ============================================================================

fn bench_monotonicity(c: &mut Criterion) {
    let mut group = c.benchmark_group("hurst_monotonicity");
    group.sample_size(10);
    group.measurement_time(Duration::from_secs(5));

    let n = 256;
    let n_trials = 100;

    for est in estimators() {
        group.bench_function(est.name, |b| {
            let trending: Vec<Vec<f64>> = (0..n_trials)
                .map(|seed| generate_fbm(n, 0.8, seed as u64 + 20_000))
                .collect();
            let random: Vec<Vec<f64>> = (0..n_trials)
                .map(|seed| generate_fbm(n, 0.5, seed as u64 + 30_000))
                .collect();
            let reverting: Vec<Vec<f64>> = (0..n_trials)
                .map(|seed| generate_fbm(n, 0.2, seed as u64 + 40_000))
                .collect();

            b.iter(|| {
                let mut violations = 0;
                for i in 0..n_trials {
                    let h_trend = (est.func)(black_box(&trending[i]));
                    let h_rand = (est.func)(black_box(&random[i]));
                    let h_rev = (est.func)(black_box(&reverting[i]));

                    if h_trend <= h_rand || h_rand <= h_rev {
                        violations += 1;
                    }
                }
                black_box(violations)
            });
        });
    }

    group.finish();
}

criterion_group!(
    benches,
    bench_accuracy,
    bench_speed,
    bench_gradient,
    bench_surrogate,
    bench_extrapolation,
    bench_monotonicity,
);
criterion_main!(benches);
