//! Engine throughput: bars/s through a bootstrapped harness — an SMA-cross
//! bias graph fed by the seeded synthetic walk via `VecSource`. Fingerprint:
//! the `SeriesReducer` finalize row `[last, max_drawdown, sign_flips]`.

use super::{RepOutcome, Sizing};
use crate::synth::synthetic_walk;
use aura_core::{Firing, Scalar, ScalarKind, Timestamp};
use aura_engine::{GraphBuilder, Harness, Source, VecSource};
use aura_std::{SeriesReducer, Sma, Sub};
use std::collections::BTreeMap;
use std::sync::mpsc;
use std::time::Instant;

/// 10M bars lands the release-profile measurement at ~0.7 s wall — big enough
/// that in-process timing jitter sits far under the 10% NOTICE threshold.
pub const FULL_BARS: usize = 10_000_000;
pub const QUICK_BARS: usize = 2_000;

pub fn rep(sizing: Sizing) -> Result<RepOutcome, String> {
    let n_bars = if sizing.quick { QUICK_BARS } else { FULL_BARS };
    let (tx, rx) = mpsc::channel::<(Timestamp, Vec<Scalar>)>();
    let mut g = GraphBuilder::new("bench_sma_cross");
    let fast = g.add(Sma::builder().bind("length", Scalar::i64(12)));
    let slow = g.add(Sma::builder().bind("length", Scalar::i64(48)));
    let bias = g.add(Sub::builder());
    let sink = g.add(SeriesReducer::builder(Firing::Any, tx));
    let price = g.source_role("price", ScalarKind::F64);
    g.feed(price, [fast.input("series"), slow.input("series")]);
    g.connect(fast.output("value"), bias.input("lhs"));
    g.connect(slow.output("value"), bias.input("rhs"));
    g.connect(bias.output("value"), sink.input("col[0]"));
    let flat = g
        .build()
        .map_err(|e| format!("bench graph must wire: {e:?}"))?
        .compile_with_params(&[])
        .map_err(|e| format!("bench graph must compile: {e:?}"))?;
    let mut h = Harness::bootstrap(flat).map_err(|e| format!("bench graph must bootstrap: {e:?}"))?;
    let prices = synthetic_walk(n_bars);
    let src: Vec<Box<dyn Source>> = vec![Box::new(VecSource::new(prices))];
    let t = Instant::now();
    h.run(src);
    let wall = t.elapsed().as_secs_f64();
    let (_, row) = rx.try_iter().last().ok_or("SeriesReducer must emit its finalize row")?;
    let fingerprint = format!(
        "last={:.6} max_dd={:.6} flips={} n={}",
        row[0].as_f64(),
        row[1].as_f64(),
        row[2].as_i64(),
        n_bars
    );
    let mut metrics = BTreeMap::new();
    metrics.insert("wall_s".to_string(), wall);
    metrics.insert("bars_per_s".to_string(), n_bars as f64 / wall);
    Ok(RepOutcome { metrics, fingerprint })
}

#[cfg(test)]
mod tests {
    use super::*;

    /// The determinism smoke for the engine surface: two quick reps in-process
    /// must produce byte-identical fingerprints (C1 — this is the harness's
    /// entire premise, pinned as a currently-green test).
    #[test]
    fn engine_surface_fingerprint_is_deterministic_in_quick_mode() {
        let a = rep(Sizing { quick: true }).expect("quick rep runs");
        let b = rep(Sizing { quick: true }).expect("quick rep runs");
        assert_eq!(a.fingerprint, b.fingerprint);
        assert!(a.fingerprint.starts_with("last="), "shape: {}", a.fingerprint);
        assert!(a.metrics.contains_key("bars_per_s"));
    }
}
