//! Gated integration test: the GER40 session-breakout `Composite` blueprint is
//! driven by the World *orchestration families* — `sweep` and
//! `walk_forward` — on REAL GER40 M1 bars, with NO re-authoring of the strategy.
//! This is the milestone-completing executable proof that the #94 friction is
//! gone: the same `ger40_breakout_blueprint` the single backtest bootstraps is
//! consumed verbatim by the World machinery.
//!
//! Mirrors `tests/ger40_breakout_real.rs` / `real_bars.rs`: skips with a note
//! where the local Pepperstone archive is absent, so `cargo test --workspace`
//! stays green anywhere; exercises the real path where the files exist.
//!
//! Two properties, both over the shipped blueprint:
//! - `sweep` returns a ranked grid over the two named params
//!   (`entry_bar.target` × `exit_bar.target`), each point's `total_pips` finite —
//!   the `param_space()` is driven directly.
//! - `walk_forward` returns a NON-EMPTY roll, every window carrying a NON-EMPTY
//!   `chosen_params` (the executable #97 non-degenerate proof: a real, non-empty
//!   space is optimized per window — contrast the fieldtest's empty-space
//!   degenerate roll over the raw FlatGraph).

use std::sync::Arc;

use aura_core::{Cell, Scalar, Timestamp};
use aura_engine::{
    sweep, summarize, walk_forward, GridSpace, RollMode, RunManifest, RunReport, WindowBounds,
    WindowRoller, WindowRun,
};
use chrono::TimeZone;
use chrono_tz::Europe::Berlin;
use aura_ingest::{default_data_server, open_ohlc, DataServer, DEFAULT_DATA_PATH};

#[path = "../examples/shared/breakout_real.rs"]
mod breakout_real;
use breakout_real::*;

/// Bootstrap the shipped blueprint at one `{entry_bar, exit_bar}` point, run it
/// over the epoch-ns [`Timestamp`] window `[from, to]` of real GER40 OHLC, and
/// fold the recorded equity/held taps into a `RunReport` (`summarize` over the
/// drained channels — exactly as the SMA-cross sweep fixture and the fieldtest's
/// `run_point` do). A fresh blueprint per call (fresh nodes + channels) keeps the
/// disjoint sweep points independent (C1).
fn run_point(server: &Arc<DataServer>, point: &[Cell], from: Timestamp, to: Timestamp) -> RunReport {
    let (bp, taps) = ger40_breakout_blueprint(
        pip_size_of(SYMBOL),
        BAR_MINUTES,
        SESSION_HOUR,
        SESSION_MINUTE,
        Berlin,
    );
    let mut h = bp
        .bootstrap_with_cells(point)
        .expect("sweep point kind-checked against param_space");
    let sources = open_ohlc(server, SYMBOL, from, to).expect("window overlaps GER40 data");
    h.run(sources);
    drop(h);
    let equity: Vec<(Timestamp, f64)> = taps.equity.try_iter().collect::<Vec<_>>()
        .iter()
        .map(|(t, r)| (*t, scalar_f64(&r[0])))
        .collect();
    let exposure: Vec<(Timestamp, f64)> = taps.held.try_iter().collect::<Vec<_>>()
        .iter()
        .map(|(t, r)| (*t, scalar_f64(&r[0])))
        .collect();
    RunReport {
        manifest: RunManifest {
            commit: "ger40-breakout-world".to_string(),
            params: vec![],
            defaults: vec![],
            window: (from, to),
            seed: 0,
            broker: "sim-optimal(pip_size=1)".to_string(),
            selection: None,
            instrument: None,
            topology_hash: None,
            project: None,
        },
        metrics: summarize(&equity, &exposure),
    }
}

fn scalar_f64(s: &Scalar) -> f64 {
    match s {
        Scalar::F64(v) => *v,
        other => panic!("expected f64, got {other:?}"),
    }
}

/// Property: the World `sweep` family CONSUMES the shipped breakout blueprint
/// over its two named params on real GER40 — it returns a full ranked grid (no
/// re-authoring), and every grid point's `total_pips` is finite (a backtest
/// actually ran at each point). The grid is built from the blueprint's
/// `param_space()` directly, so the two `EqConst` targets are the swept axes.
#[test]
fn sweep_consumes_blueprint_over_named_params() {
    let server = default_data_server();
    if !server.has_symbol(SYMBOL) {
        eprintln!("skip: no local data at {DEFAULT_DATA_PATH} (symbol {SYMBOL} absent)");
        return;
    }

    let (from, to) = utc_month_window(2024, 9);
    let space = ger40_breakout_blueprint(
        pip_size_of(SYMBOL),
        BAR_MINUTES,
        SESSION_HOUR,
        SESSION_MINUTE,
        Berlin,
    )
    .0
    .param_space();
    // The grid is exactly over `param_space()`: entry ∈ {2,3,4}, exit ∈ {4,5,6}.
    let grid = GridSpace::new(
        &space,
        space
            .iter()
            .map(|ps| match ps.name.as_str() {
                "entry_bar.target" => vec![Scalar::i64(2), Scalar::i64(3), Scalar::i64(4)],
                "exit_bar.target" => vec![Scalar::i64(4), Scalar::i64(5), Scalar::i64(6)],
                other => panic!("unexpected param slot {other}"),
            })
            .collect(),
    )
    .expect("grid well-formed for the breakout param_space");

    let server_for_closure = Arc::clone(&server);
    let family = sweep(&grid, |pt: &[Cell]| {
        run_point(&server_for_closure, pt, from, to)
    });

    // A full 3×3 grid was enumerated over the two named params (no re-authoring).
    assert_eq!(family.points.len(), 9, "the 3×3 entry×exit grid must enumerate 9 points");
    assert_eq!(
        family.space.iter().map(|p| p.name.as_str()).collect::<Vec<_>>(),
        vec!["entry_bar.target", "exit_bar.target"],
        "the swept space is exactly the two genuine tuning knobs",
    );
    // Every point's metrics are finite — a real backtest ran at each grid point.
    for (i, p) in family.points.iter().enumerate() {
        assert!(
            p.report.metrics.total_pips.is_finite(),
            "point {i} {:?} produced a non-finite total_pips",
            family.named_params(i),
        );
    }
    // The best point (max total_pips) is finite — the ranked grid has a real head.
    let best = family
        .points
        .iter()
        .map(|p| p.report.metrics.total_pips)
        .fold(f64::NEG_INFINITY, f64::max);
    assert!(best.is_finite(), "the best grid point's total_pips must be finite");
}

/// Property: the World `walk_forward` family CONSUMES the shipped breakout
/// blueprint NON-DEGENERATELY (the executable #97 resolution). The roll over
/// multi-year GER40 yields a NON-EMPTY set of windows, and EACH window carries a
/// NON-EMPTY `chosen_params` — a real `{entry_bar, exit_bar}` point was selected
/// by an in-sample sweep per window (contrast the fieldtest's empty-space roll
/// over the raw FlatGraph, where `chosen_params == vec![]` everywhere).
#[test]
fn walk_forward_consumes_blueprint_non_degenerate() {
    let server = default_data_server();
    if !server.has_symbol(SYMBOL) {
        eprintln!("skip: no local data at {DEFAULT_DATA_PATH} (symbol {SYMBOL} absent)");
        return;
    }

    // A short 2-window roll over 2023..2024 keeps the gated test cheap while still
    // exercising the per-window in-sample optimize. is=6mo, oos=3mo, step=3mo.
    let day_ns: i64 = 86_400_000 * 1_000_000;
    let month_ns: i64 = 30 * day_ns;
    let origin = aura_ingest::unix_ms_to_epoch_ns(
        chrono::Utc.with_ymd_and_hms(2023, 1, 1, 0, 0, 0).unwrap().timestamp_millis(),
    );
    let end = aura_ingest::unix_ms_to_epoch_ns(
        chrono::Utc.with_ymd_and_hms(2024, 6, 30, 23, 59, 59).unwrap().timestamp_millis(),
    );
    let roller = WindowRoller::new(
        (origin, end),
        6 * month_ns,
        3 * month_ns,
        3 * month_ns,
        RollMode::Rolling,
    )
    .expect("span fits >= 1 window");

    // The non-empty space the in-sample sweep optimizes over per window: the two
    // genuine tuning knobs. This is the #97 resolution — a real space, so the
    // chosen params are populated, never an empty degenerate vec.
    let space = ger40_breakout_blueprint(
        pip_size_of(SYMBOL),
        BAR_MINUTES,
        SESSION_HOUR,
        SESSION_MINUTE,
        Berlin,
    )
    .0
    .param_space();
    let server_for_closure = Arc::clone(&server);

    let result = walk_forward(roller, space.clone(), move |w: WindowBounds| -> WindowRun {
        // In-sample optimize: a tiny entry×exit grid over the IS window, choose the
        // point with the best in-sample total_pips. The blueprint is consumed
        // directly via its param_space() — no re-authoring.
        let is_grid = GridSpace::new(
            &space,
            space
                .iter()
                .map(|ps| match ps.name.as_str() {
                    "entry_bar.target" => vec![Scalar::i64(2), Scalar::i64(3)],
                    "exit_bar.target" => vec![Scalar::i64(5), Scalar::i64(6)],
                    other => panic!("unexpected param slot {other}"),
                })
                .collect(),
        )
        .expect("IS grid well-formed");
        let is_family = sweep(&is_grid, |pt: &[Cell]| {
            run_point(&server_for_closure, pt, w.is.0, w.is.1)
        });
        let best = is_family
            .points
            .iter()
            .max_by(|a, b| {
                a.report
                    .metrics
                    .total_pips
                    .partial_cmp(&b.report.metrics.total_pips)
                    .unwrap_or(std::cmp::Ordering::Equal)
            })
            .expect("the IS grid is non-empty");
        let chosen = best.params.clone();

        // Apply the chosen params on the OOS window.
        let oos_report = run_point(&server_for_closure, &chosen, w.oos.0, w.oos.1);
        // Re-run once on OOS to capture the recorded equity segment for stitching.
        let (bp, taps) = ger40_breakout_blueprint(
            pip_size_of(SYMBOL),
            BAR_MINUTES,
            SESSION_HOUR,
            SESSION_MINUTE,
            Berlin,
        );
        let mut h = bp.bootstrap_with_cells(&chosen).expect("chosen point kind-checked");
        let sources = open_ohlc(&server_for_closure, SYMBOL, w.oos.0, w.oos.1)
            .expect("OOS window overlaps data");
        h.run(sources);
        drop(h);
        let oos_equity: Vec<(Timestamp, f64)> = taps
            .equity
            .try_iter()
            .collect::<Vec<_>>()
            .iter()
            .map(|(t, r)| (*t, scalar_f64(&r[0])))
            .collect();

        WindowRun { chosen_params: chosen, oos_equity, oos_report }
    });

    // Non-degenerate: a non-empty roll, EACH window carrying a non-empty chosen
    // point (the #97 proof — a real space was optimized per window).
    assert!(!result.windows.is_empty(), "walk_forward must yield >= 1 window");
    for (k, w) in result.windows.iter().enumerate() {
        assert!(
            !w.run.chosen_params.is_empty(),
            "window {k}: chosen_params must be NON-empty (the #97 non-degenerate proof)",
        );
        assert_eq!(
            w.run.chosen_params.len(),
            2,
            "window {k}: chosen_params spans the two-param space",
        );
        assert!(
            w.run.oos_report.metrics.total_pips.is_finite(),
            "window {k}: OOS total_pips must be finite",
        );
    }
}
