// Task 2 — Walk-forward the SHIPPED breakout across real GER40 history.
//
// The researcher's task: "the engine advertises a World walk-forward family.
// Roll my GER40 breakout across 2018..2024 with in-sample/out-of-sample windows
// and stitch the OOS pip curve." The breakout I want to roll is the SHIPPED one
// (crates/aura-ingest/examples/shared/breakout_real.rs) — a hand-wired FlatGraph.
//
// This file records the discovery journey verbatim. The public walk_forward is:
//   walk_forward(roller: WindowRoller, space: Vec<ParamSpec>,
//                run_window: Fn(WindowBounds) -> WindowRun) -> WalkForwardResult
// with WindowRun { chosen_params: Vec<Cell>, oos_equity, oos_report }.
//
// SEAM 1 (recorded, not worked around): the breakout has NO swept params in this
// scenario (a fixed-param OOS roll), yet walk_forward DEMANDS a space:
// Vec<ParamSpec> and each WindowRun a chosen_params: Vec<Cell>. With a raw
// FlatGraph there is no param_space() to hand it — the FlatGraph is built by
// Harness::bootstrap, which takes no params. So I pass an EMPTY space (vec![]) and
// EMPTY chosen_params (vec![]). Whether that is the intended idiom for a
// param-free strategy is a spec question — recorded.
//
// SEAM 2 (recorded): the breakout source is FOUR M1FieldSources (OHLC) opened by
// Unix-ms, but WindowBounds.{is,oos} are epoch-ns. aura-ingest exports
// unix_ms_to_epoch_ns but NOT its inverse, so I hand-divide ns->ms (the milestone
// fixture's mw_3 hit the same seam).
//
// PUBLIC SURFACE ONLY: walk_forward signature from cargo doc; the breakout wiring
// reused verbatim from the shipped example's shared module.

use std::sync::Arc;

use chrono::TimeZone;
use data_server::{DataServer, DEFAULT_DATA_PATH};

use aura_core::Timestamp;
use aura_engine::{
    walk_forward, RollMode, RunReport, WindowBounds, WindowRoller, WindowRun,
};

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

// SEAM 2: ns -> ms (aura-ingest exports only the forward direction).
fn ns_to_ms(ts: Timestamp) -> i64 {
    ts.0 / 1_000_000
}

// Run the SHIPPED breakout over one [from, to] epoch-ns window of real GER40 M1
// OHLC, returning the OOS pip-equity stream + the folded RunReport.
fn run_breakout_window(
    server: &Arc<DataServer>,
    from: Timestamp,
    to: Timestamp,
) -> (Vec<(Timestamp, f64)>, RunReport) {
    let from_ms = ns_to_ms(from);
    let to_ms = ns_to_ms(to);
    // Open the four OHLC field sources directly (the shipped open_ohlc_sources
    // helper hard-codes Some(from)/Some(to) and returns Option — reuse it).
    let sources = open_ohlc_sources(server, from_ms, to_ms).expect("window overlaps GER40 data");
    let (mut h, taps) = build_harness();
    h.run(sources);
    let trace = drain_trace(&taps);
    let report = report_from_trace(&trace, from_ms, to_ms);
    let oos_equity: Vec<(Timestamp, f64)> = trace.iter().map(|b| (b.ts, b.equity)).collect();
    (oos_equity, report)
}

fn main() {
    let server = Arc::new(DataServer::new(DEFAULT_DATA_PATH));
    if !server.has_symbol(SYMBOL) {
        println!("skip: no local {SYMBOL} data");
        return;
    }

    println!("=== Task 2: walk-forward the shipped GER40 breakout (2018..2024) ===\n");

    // The roll span: 2018-01 .. 2024-12 in epoch-ns. is=12mo, 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(2018, 1, 1, 0, 0, 0).unwrap().timestamp_millis(),
    );
    let end = aura_ingest::unix_ms_to_epoch_ns(
        chrono::Utc.with_ymd_and_hms(2024, 12, 31, 23, 59, 59).unwrap().timestamp_millis(),
    );
    let roller = WindowRoller::new(
        (origin, end),
        12 * month_ns, // in-sample length
        3 * month_ns,  // out-of-sample length
        3 * month_ns,  // step
        RollMode::Rolling,
    )
    .expect("span fits >= 1 window");

    let server_for_closure = Arc::clone(&server);

    // SEAM 1: the breakout is param-free here, so space = vec![] and each
    // WindowRun carries chosen_params = vec![]. There is no in-sample optimize to
    // do (no params to tune with a raw FlatGraph), so each window just runs the
    // fixed strategy on its OOS slice. (Tuning the breakout's structural params is
    // Task 3 — and the seam there is that they are NOT a FlatGraph param_space.)
    let result = walk_forward(roller, vec![], move |w: WindowBounds| -> WindowRun {
        let (oos_equity, oos_report) =
            run_breakout_window(&server_for_closure, w.oos.0, w.oos.1);
        WindowRun { chosen_params: vec![], oos_equity, oos_report }
    });

    println!("walk-forward produced {} windows\n", result.windows.len());
    for (k, w) in result.windows.iter().enumerate() {
        let b = w.bounds;
        println!(
            "  window {k}: is=[{}..{}] oos=[{}..{}]  oos_pips={:>8.1}  oos_dd={:>7.1}",
            ns_to_ms(b.is.0),
            ns_to_ms(b.is.1),
            ns_to_ms(b.oos.0),
            ns_to_ms(b.oos.1),
            w.run.oos_report.metrics.total_pips,
            w.run.oos_report.metrics.max_drawdown,
        );
    }

    let stitched = &result.stitched_oos_equity;
    println!(
        "\nstitched OOS curve: {} points, final cumulative = {:.1} pips",
        stitched.len(),
        stitched.last().map(|(_, v)| *v).unwrap_or(0.0),
    );

    // The stitched OOS curve is the breakout's out-of-sample pip total over the
    // concatenated OOS quarters (no in-sample optimization happened — same fixed
    // FlatGraph strategy everywhere, since a raw FlatGraph has no param_space()).
    println!("\nOK: fixed-param OOS walk-forward over the shipped breakout FlatGraph.");
}
