// Milestone fieldtest — scenario 1: a single concrete run, bound BY NAME.
//
// Promise probed: an author declares a strategy's tunable knobs in its schema
// (typed), inspects them via `param_space()`, and binds one concrete runnable
// instance by NAME — `.with("sma_cross.fast", 2).with("scale", 0.5).bootstrap()`.
//
// Public interface only: discovered from the ledger + specs 0015/0030 + rustdoc.
// No crates/*/src was read.
//
// The harness: source -> sma_cross composite { Sma, Sma, Sub } -> Exposure
//              -> SimBroker -> Recorder. The two SMA lengths live INSIDE the
// `sma_cross` composite (so param_space() path-qualifies them as
// `sma_cross.fast` / `sma_cross.slow`), `scale` is a root-level Exposure knob
// (bare). The 0024 root model: the root graph IS a Composite all of whose roles
// are source-bound.

use std::sync::mpsc;

use aura_core::{Firing, Scalar, ScalarKind, Timestamp};
use aura_engine::{
    BindError, BlueprintNode, Composite, Edge, OutField, ParamAlias, Role, Target,
};
use aura_std::{Exposure, Recorder, SimBroker, Sma, Sub};

/// Build the reusable inner 2/4-SMA-cross composite (value-empty recipes; the
/// two lengths are open knobs bound at bootstrap).
fn sma_cross() -> Composite {
    Composite::new(
        "sma_cross",
        vec![
            Sma::builder().into(),
            Sma::builder().into(),
            Sub::builder().into(),
        ],
        // interior edges: fast -> Sub.slot0, slow -> Sub.slot1
        vec![
            Edge { from: 0, to: 2, slot: 0, from_field: 0 },
            Edge { from: 1, to: 2, slot: 1, from_field: 0 },
        ],
        // one OPEN input role (price) fanning into both SMAs' slot 0.
        // source: None => open interior port, wired by the enclosing graph.
        vec![Role {
            name: "price".into(),
            targets: vec![
                Target { node: 0, slot: 0 },
                Target { node: 1, slot: 0 },
            ],
            // source: None => an OPEN interior port, wired by the enclosing graph
            // (0024 root model: Role.source distinguishes open vs source-bound).
            source: None,
        }],
        vec![],                                   // no param aliases
        vec![OutField { node: 2, field: 0, name: "out".into() }],
    )
}

/// The SAME composite, but with C23 ParamAlias overlays so the two interior SMA
/// knobs surface as the DISTINCT names `fast` / `slow` the milestone promise
/// example assumes. This is the work the author must do by hand to make
/// `sma_cross.fast` real.
fn sma_cross_aliased() -> Composite {
    Composite::new(
        "sma_cross",
        vec![
            Sma::builder().into(),
            Sma::builder().into(),
            Sub::builder().into(),
        ],
        vec![
            Edge { from: 0, to: 2, slot: 0, from_field: 0 },
            Edge { from: 1, to: 2, slot: 1, from_field: 0 },
        ],
        vec![Role {
            name: "price".into(),
            targets: vec![
                Target { node: 0, slot: 0 },
                Target { node: 1, slot: 0 },
            ],
            source: None,
        }],
        // ALIASES: rename Sma#0's length slot -> "fast", Sma#1's -> "slow".
        vec![
            ParamAlias { name: "fast".into(), node: 0, slot: 0 },
            ParamAlias { name: "slow".into(), node: 1, slot: 0 },
        ],
        vec![OutField { node: 2, field: 0, name: "out".into() }],
    )
}

/// Harness over an arbitrary inner cross composite (plain or aliased).
fn harness_with(
    inner: Composite,
    tx: mpsc::Sender<(Timestamp, Vec<Scalar>)>,
) -> Composite {
    Composite::new(
        "harness",
        vec![
            BlueprintNode::Composite(inner),
            Exposure::builder().into(),
            SimBroker::builder(1e-4).into(),
            Recorder::builder(vec![ScalarKind::F64], Firing::Any, tx).into(),
        ],
        vec![
            Edge { from: 0, to: 1, slot: 0, from_field: 0 },
            Edge { from: 1, to: 2, slot: 0, from_field: 0 },
            Edge { from: 2, to: 3, slot: 0, from_field: 0 },
        ],
        vec![Role {
            name: "price".into(),
            targets: vec![
                Target { node: 0, slot: 0 },
                Target { node: 2, slot: 1 },
            ],
            source: Some(ScalarKind::F64),
        }],
        vec![],
        vec![],
    )
}

/// The root harness over the PLAIN (un-aliased) cross — its two SMA knobs both
/// surface as `sma_cross.length`.
fn harness(tx: mpsc::Sender<(Timestamp, Vec<Scalar>)>) -> Composite {
    harness_with(sma_cross(), tx)
}

/// The root harness over the ALIASED cross — knobs surface as
/// `sma_cross.fast` / `sma_cross.slow`.
fn harness_aliased(tx: mpsc::Sender<(Timestamp, Vec<Scalar>)>) -> Composite {
    harness_with(sma_cross_aliased(), tx)
}

fn synthetic_prices() -> Vec<(Timestamp, Scalar)> {
    let prices = [
        1.00, 1.01, 1.02, 1.03, 1.05, 1.08, 1.06, 1.04, 1.02, 1.01, 1.03, 1.07,
    ];
    prices
        .iter()
        .enumerate()
        .map(|(i, &p)| (Timestamp(60_000_000_000 * i as i64), Scalar::F64(p)))
        .collect()
}

fn main() {
    // --- 1. Inspect the param-space: does it tell me the knob names? ----------
    let (tx0, _rx0) = mpsc::channel();
    let space = harness(tx0).param_space();
    println!("param_space() knobs (name : kind):");
    for p in &space {
        println!("  {} : {:?}", p.name, p.kind);
    }

    // --- 2a. FIRST ATTEMPT: bind by the milestone-promise names. --------------
    // The carrier / spec 0030 worked example says these are `sma_cross.fast` /
    // `sma_cross.slow`. Try it verbatim and record the diagnostic.
    let (txp, _rxp) = mpsc::channel();
    let promise_form: Result<_, BindError> = harness(txp)
        .with("sma_cross.fast", 2)
        .with("sma_cross.slow", 4)
        .with("scale", 0.5)
        .bootstrap();
    println!("\n[2a] promise-form (sma_cross.fast/.slow) result: {:?}", promise_form.err());

    // --- 2b. SECOND ATTEMPT: bind by the names param_space() ACTUALLY emits. ---
    // Both interior SMA knobs surface as the IDENTICAL `sma_cross.length`.
    let (txq, _rxq) = mpsc::channel();
    let real_name_form: Result<_, BindError> = harness(txq)
        .with("sma_cross.length", 2)
        .with("scale", 0.5)
        .bootstrap();
    println!("[2b] real-emitted-name (sma_cross.length) result: {:?}", real_name_form.err());

    // --- 2c. The FIX: add C23 ParamAlias overlays so fast/slow become real. ---
    // With the aliases, param_space() emits the DISTINCT names the promise wants.
    let (txal0, _rxal0) = mpsc::channel();
    let aliased_space = harness_aliased(txal0).param_space();
    println!("\n[2c] param_space() AFTER aliasing:");
    for p in &aliased_space {
        println!("  {} : {:?}", p.name, p.kind);
    }

    // Now the milestone-promise named bind works AND runs.
    let (tx, rx) = mpsc::channel::<(Timestamp, Vec<Scalar>)>();
    let mut h = harness_aliased(tx)
        .with("sma_cross.fast", 2)
        .with("sma_cross.slow", 4)
        .with("scale", 0.5)
        .bootstrap()
        .expect("aliased harness binds by the promised names");

    h.run(vec![synthetic_prices()]);
    drop(h);

    let rows: Vec<(Timestamp, Vec<Scalar>)> = rx.iter().collect();
    println!("\n[2c] aliased named-bound run — recorded equity rows: {}", rows.len());
    for (ts, row) in &rows {
        if let Scalar::F64(x) = row[0] {
            println!("  ts={:>14} equity_pips={:.4}", ts.0, x);
        }
    }
    assert!(!rows.is_empty(), "named-bound harness must record a populated trace");

    // --- 3. Probe diagnostics: a misspelled knob name (against aliased names). -
    let (txa, _rxa) = mpsc::channel();
    let wrong_name: Result<_, BindError> = harness_aliased(txa)
        .with("sma_cross.fast", 2)
        .with("sma_cross.slo", 4)    // typo: "slo" not "slow"
        .with("scale", 0.5)
        .bootstrap();
    println!("\n[3] wrong-name (sma_cross.slo) result: {:?}", wrong_name.err());

    // --- 4. Probe diagnostics: the SHORT bare name (spec says UnknownKnob). ----
    let (txb, _rxb) = mpsc::channel();
    let bare: Result<_, BindError> = harness_aliased(txb)
        .with("fast", 2)             // unqualified — spec: UnknownKnob
        .with("sma_cross.slow", 4)
        .with("scale", 0.5)
        .bootstrap();
    println!("[4] bare-name (\"fast\") result: {:?}", bare.err());

    // --- 5. Probe diagnostics: a wrong-KIND value. ----------------------------
    let (txc, _rxc) = mpsc::channel();
    let wrong_kind: Result<_, BindError> = harness_aliased(txc)
        .with("sma_cross.fast", 2)
        .with("sma_cross.slow", 4)
        .with("scale", 1)            // I64 literal into an F64 slot -> KindMismatch
        .bootstrap();
    println!("[5] wrong-kind (scale=1 into F64) result: {:?}", wrong_kind.err());

    // --- 6. Probe: binding the AMBIGUOUS un-aliased name -> AmbiguousKnob. -----
    let (txd, _rxd) = mpsc::channel();
    let ambiguous: Result<_, BindError> = harness(txd)   // PLAIN harness, both = sma_cross.length
        .with("sma_cross.length", 2)
        .with("scale", 0.5)
        .bootstrap();
    println!("[6] ambiguous (plain sma_cross.length) result: {:?}", ambiguous.err());

    println!("\nOK: param-space inspected, named bind ran (aliased), diagnostics probed.");
}
