//! End-to-end pipeline test using synthetic Mbp10RawInput data.
//!
//! Pure-CPU test (no GPU, no FOXHUNT_TEST_DATA gate). Confirms that
//! `aggregate_window` emits ts_ns / prev_ts_ns spans matching the design,
//! so the snap-feature Δt Fourier encoder receives the correct per-position
//! dt for each scale.

use ml_alpha::cfc::snap_features::Mbp10RawInput;
use ml_alpha::data::aggregation::{aggregate_window, MultiResolutionConfig};

#[test]
fn default_three_scale_position_layout() {
    let cfg = MultiResolutionConfig::default_three_scale();
    assert_eq!(cfg.scales(), &[(1, 10), (30, 10), (100, 12)]);
    assert_eq!(cfg.total_positions(), 32);
    assert_eq!(cfg.required_lookback_ticks(), 1510);
}

#[test]
fn aggregate_window_30_tick_dt_spans_window() {
    let snaps: Vec<Mbp10RawInput> = (0..30u64)
        .map(|i| {
            let mut x = Mbp10RawInput::default();
            x.ts_ns = 1000 + i * 100;
            x.prev_ts_ns = x.ts_ns.saturating_sub(100);
            x
        })
        .collect();
    let agg = aggregate_window(&snaps);
    let dt = agg.ts_ns - agg.prev_ts_ns;
    assert_eq!(dt, 29 * 100, "30-tick window @ 100ns spacing -> dt = 2900ns");
}

#[test]
fn aggregate_window_100_tick_dt_much_larger_than_raw_dt() {
    // 100-tick window @ 100ns spacing -> dt = 99 * 100 = 9900ns
    let snaps_100: Vec<Mbp10RawInput> = (0..100u64)
        .map(|i| {
            let mut x = Mbp10RawInput::default();
            x.ts_ns = 5000 + i * 100;
            x.prev_ts_ns = x.ts_ns.saturating_sub(100);
            x
        })
        .collect();
    let agg_100 = aggregate_window(&snaps_100);
    let dt_100 = agg_100.ts_ns - agg_100.prev_ts_ns;

    // 1-tick raw position -> dt = 100ns (the input's own prev_ts_ns offset).
    let raw = Mbp10RawInput {
        ts_ns: 5000,
        prev_ts_ns: 4900,
        ..Default::default()
    };
    let dt_raw = raw.ts_ns - raw.prev_ts_ns;

    // 9900 vs 100 -> 99x ratio. Confirms the snap-feature Δt encoder will
    // see a clearly distinct time-scale signature per position.
    assert!(
        dt_100 as f32 / dt_raw as f32 > 50.0,
        "coarse-scale dt={dt_100} should be >50x raw dt={dt_raw}"
    );
}

#[test]
fn aggregate_window_prev_mid_is_first_snapshot_mid() {
    // Walking returns calc: cur.mid (last) - agg.prev_mid (first) should
    // equal the realized return across the window.
    let snaps: Vec<Mbp10RawInput> = (0..30u64)
        .map(|i| {
            let mut x = Mbp10RawInput::default();
            x.prev_mid = 5000.0 + i as f32;
            x.bid_px[0] = x.prev_mid - 0.125;
            x.ask_px[0] = x.prev_mid + 0.125;
            x.ts_ns = 1000 + i * 100;
            x.prev_ts_ns = x.ts_ns.saturating_sub(100);
            x
        })
        .collect();
    let agg = aggregate_window(&snaps);
    assert!(
        (agg.prev_mid - 5000.0).abs() < 1e-5,
        "agg.prev_mid should equal first snapshot's prev_mid"
    );
}
