//! Runnable demonstration: the GER40 15m session-breakout strategy — the
//! Phase-1 `aura-std` node vocabulary, proven synthetically in
//! `aura-engine/tests/ger40_breakout.rs` — driven over REAL GER40 M1 bars from
//! the local data-server archive (one calendar month).
//!
//! This is a *pure composition* of SHIPPED `aura-std` nodes: no project-specific
//! signal code, so it lives in the engine repo's `examples/` carveout (C9), not
//! a separate project crate. The strategy is authored as a World-consumable
//! `Composite` blueprint (`ger40_breakout_blueprint`) and bootstrapped
//! here by name (`entry_bar=3`, `exit_bar=5`); the four real `M1FieldSource`s
//! feed its four OHLC source roles. The blueprint reproduces the shipped
//! hand-wired `FlatGraph` EXACTLY (C23), so this prints the same report + trace.
//!
//! Run:
//! ```text
//! cargo run -p aura-ingest --example ger40_breakout_real
//! ```
//! Prints the [`RunReport`] metrics (total pips / drawdown / sign-flips) and a
//! compact entry/exit trace — each 0→1 entry and 1→0 exit with its timestamp —
//! so the strategy's behaviour over the ~20 sessions in the month is visibly
//! inspectable. Skips cleanly (no panic) where the archive is absent.

use chrono::TimeZone;
use chrono_tz::Europe::Berlin;
use aura_ingest::{default_data_server, open_ohlc, DEFAULT_DATA_PATH};

// The breakout wiring is defined once in the shared module and included by both
// this example and the gated determinism test — never duplicated.
#[path = "shared/breakout_real.rs"]
mod breakout_real;
use breakout_real::*;

// --- The window: September 2024 (inclusive), trivially changeable. -----------
const YEAR: i32 = 2024;
const MONTH: u32 = 9;

fn main() {
    let server = default_data_server();
    if !server.has_symbol(SYMBOL) {
        println!(
            "skip: no local data at {DEFAULT_DATA_PATH} (symbol {SYMBOL} absent) — \
             nothing to demonstrate here."
        );
        return;
    }

    let (from, to) = utc_month_window(YEAR, MONTH);

    let Some(sources) = open_ohlc(&server, SYMBOL, from, to) else {
        println!(
            "skip: {SYMBOL} has no M1 file overlapping {YEAR}-{MONTH:02} — \
             nothing to demonstrate here."
        );
        return;
    };

    // Author the breakout as a Composite blueprint and bootstrap it by name with
    // the two genuine tuning knobs (entry bar 3, exit bar 5). The bar-period (15m),
    // session open (09:00 Berlin) and delay.lag are structural — bound at
    // construction, not exposed as params.
    let pip_size = pip_size_of(SYMBOL);
    let (bp, taps) =
        ger40_breakout_blueprint(pip_size, BAR_MINUTES, SESSION_HOUR, SESSION_MINUTE, Berlin);
    let mut h = bp
        .with("entry_bar.target", aura_core::Scalar::i64(3))
        .with("exit_bar.target", aura_core::Scalar::i64(5))
        .bootstrap()
        .expect("breakout blueprint bootstraps with entry=3, exit=5");
    h.run(sources);

    // Drain the per-bar trace first (it consumes the equity/held/bars/breakout
    // channels), then fold the report from that same trace so the mpsc channels
    // are drained exactly once (shared with the determinism test).
    let trace = drain_trace(&taps);
    let report = report_from_trace(&trace, from, to);
    let metrics = &report.metrics;

    println!("=== GER40 15m session-breakout — REAL bars ===");
    println!(
        "symbol={SYMBOL}  window={YEAR}-{MONTH:02} (UTC, inclusive)  \
         bars={}  pip_size={pip_size}",
        trace.len()
    );
    println!("session: {SESSION_HOUR:02}:{SESSION_MINUTE:02} Europe/Berlin, {BAR_MINUTES}m bars; entry bar 3, exit bar 5");
    println!();
    println!("--- metrics (sim-optimal broker, pips = index points) ---");
    println!("  total_pips          = {:.1}", metrics.total_pips);
    println!("  max_drawdown        = {:.1}", metrics.max_drawdown);
    println!("  bias_sign_flips     = {}", metrics.bias_sign_flips);
    println!();

    // --- 2. The compact entry/exit trace. -----------------------------------
    // Each 0→1 is an entry (a strict breakout on session bar 3), each 1→0 is the
    // bar-5 exit. Printing only the transitions keeps the ~20 sessions readable.
    println!("--- entry / exit transitions (held 0↔1) ---");
    let mut prev_held = 0.0_f64;
    let mut entries = 0u32;
    let mut exits = 0u32;
    let mut entry_equity = 0.0_f64;
    for b in &trace {
        if b.held == 1.0 && prev_held == 0.0 {
            entries += 1;
            entry_equity = b.equity;
            println!(
                "  ENTER  {}  bar={}  breakout={}  equity={:+.1}",
                fmt_ts(b.ts),
                b.bars_since_open,
                b.breakout,
                b.equity
            );
        } else if b.held == 0.0 && prev_held == 1.0 {
            exits += 1;
            println!(
                "  EXIT   {}  bar={}  equity={:+.1}  (session pnl {:+.1})",
                fmt_ts(b.ts),
                b.bars_since_open,
                b.equity,
                b.equity - entry_equity
            );
        }
        prev_held = b.held;
    }
    if prev_held == 1.0 {
        println!("  (note: still held at end of window — a partial last session)");
    }
    println!();
    println!("sessions entered = {entries}, exited = {exits}");
}

/// Render an epoch-ns [`Timestamp`](aura_core::Timestamp) as a UTC wall-clock
/// `YYYY-MM-DD HH:MM` for the trace. Display only — never feeds the graph.
fn fmt_ts(ts: aura_core::Timestamp) -> String {
    let secs = ts.0.div_euclid(1_000_000_000);
    let nanos = ts.0.rem_euclid(1_000_000_000) as u32;
    match chrono::Utc.timestamp_opt(secs, nanos).single() {
        Some(dt) => dt.format("%Y-%m-%d %H:%M UTC").to_string(),
        None => format!("ts={}", ts.0),
    }
}
