//! Ring 1b — trainer/backtest parity check. See spec §8 Ring 1b.
//!
//! Verifies that the data-loading path used by the backtest harness
//! (`inference_only=true`) produces an Mbp10RawInput that is byte-equal
//! to what the trainer's loader produces from the same source.
//! Since both paths share the same loader code and same Mbp10Snapshot
//! → Mbp10RawInput conversion, the equality is structurally guaranteed —
//! but the test guards against any future refactor that breaks the claim.
//!
//! The full inference-output parity (asserting [probs; N_HORIZONS] from
//! the trunk's captured graph is bit-equal between training-time loader
//! and backtest-time loader paths) requires a real ml-alpha checkpoint,
//! which is deferred until a checkpoint-format is pinned in ml-alpha.
//! When a checkpoint is available, FOXHUNT_TEST_CKPT will gate the
//! gpu-inference assertion below.

use anyhow::Result;
use ml_alpha::data::loader::{
    discover_mbp10_files_sorted, MultiHorizonLoader, MultiHorizonLoaderConfig,
};
use std::path::PathBuf;

fn try_loader(inference_only: bool) -> Option<MultiHorizonLoader> {
    let root = std::env::var("FOXHUNT_TEST_DATA").ok()?;
    let mbp10 = PathBuf::from(&root).join("ES.FUT");
    if !mbp10.exists() {
        return None;
    }
    let files = discover_mbp10_files_sorted(&mbp10).ok()?;
    let cfg = MultiHorizonLoaderConfig {
        files,
        predecoded_dir: mbp10.clone(),
        seq_len: 1,
        horizons: [30, 100, 300, 1000, 6000],
        n_max_sequences: 1,
        seed: 0xCAFEF00D,
        decision_stride: 1,
        inference_only,
    };
    match MultiHorizonLoader::new(&cfg) {
        Ok(l) => Some(l),
        Err(e) => {
            eprintln!("skipping: fixture data not usable ({e})");
            None
        }
    }
}

#[test]
#[ignore = "requires populated FOXHUNT_TEST_DATA"]
fn peek_first_byte_equal_across_modes() -> Result<()> {
    let Some(loader_train) = try_loader(false) else { return Ok(()); };
    let Some(loader_bt)    = try_loader(true)  else { return Ok(()); };

    let first_train = loader_train.peek_first()?;
    let first_bt    = loader_bt.peek_first()?;

    // Inputs must be byte-equal (Mbp10RawInput is Pod via #[derive(Default)]
    // — bid_px/sz/ask_px/sz arrays of f32, plus a handful of scalars).
    assert_eq!(first_train.ts_ns, first_bt.ts_ns);
    assert_eq!(first_train.prev_ts_ns, first_bt.prev_ts_ns);
    for k in 0..10 {
        assert_eq!(
            first_train.bid_px[k].to_bits(),
            first_bt.bid_px[k].to_bits(),
            "bid_px[{k}] bit-mismatch between training and inference loader"
        );
        assert_eq!(first_train.bid_sz[k].to_bits(), first_bt.bid_sz[k].to_bits());
        assert_eq!(first_train.ask_px[k].to_bits(), first_bt.ask_px[k].to_bits());
        assert_eq!(first_train.ask_sz[k].to_bits(), first_bt.ask_sz[k].to_bits());
    }
    for k in 0..6 {
        assert_eq!(
            first_train.regime[k].to_bits(),
            first_bt.regime[k].to_bits(),
            "regime[{k}] bit-mismatch"
        );
    }
    assert_eq!(first_train.prev_mid.to_bits(), first_bt.prev_mid.to_bits());
    assert_eq!(first_train.trade_signed_vol.to_bits(), first_bt.trade_signed_vol.to_bits());
    assert_eq!(first_train.trade_count, first_bt.trade_count);

    Ok(())
}

#[test]
#[ignore = "requires populated FOXHUNT_TEST_DATA"]
fn inference_iteration_matches_chronological_snapshots() -> Result<()> {
    let Some(mut loader) = try_loader(true) else { return Ok(()); };

    // Pull 5 inference inputs; verify timestamps are monotone non-decreasing
    // and each input's prev_ts_ns equals the previous input's ts_ns
    // (after the first, which uses cur=prev semantics).
    let mut prev_ts_ns = 0u64;
    let mut first_iter = true;
    for i in 0..5 {
        let snap = loader.next_inference_input()?.expect("stream not exhausted");
        if first_iter {
            // peek_first/next-first uses cur==prev → prev_ts == ts.
            assert_eq!(snap.prev_ts_ns, snap.ts_ns, "first snap has prev_ts == ts");
            first_iter = false;
        } else {
            assert_eq!(
                snap.prev_ts_ns, prev_ts_ns,
                "snap {i}: prev_ts_ns should equal previous snap's ts_ns"
            );
        }
        assert!(snap.ts_ns >= prev_ts_ns, "snap {i}: monotonic ts violated");
        prev_ts_ns = snap.ts_ns;
    }
    Ok(())
}
