"""What the ``Backtest`` facade assembles for you — read this to understand
the seams; use ``Backtest`` for real work.

Every object below is one seam of the sim stack:

    feed (ListBarFeed) ──▶ engine (BacktestEngine) ──▶ strategy
                              │
                              ▼
    broker (SimBroker: fills + fees + CombineKernel), all on ONE TestClock

This is the exact same scenario as ``examples/run_combine.py``, wired by
hand: same tape, same strategy, same parameters — and it produces the
byte-identical frozen ``BacktestResult``, because the facade changes no
semantics; it only makes the assembly mistakes unbuildable. The mistake
that matters most is documented at the ``TestClock`` below.

    uv run python examples/hand_wired.py
"""

from __future__ import annotations

import asyncio
from datetime import date
from decimal import Decimal

from sma_cross import SmaCross

from topstep_backtest.clock.test_clock import TestClock
from topstep_backtest.core.instruments import spec_for_symbol
from topstep_backtest.data.feed import ListBarFeed
from topstep_backtest.data.synthetic import synthetic_bars
from topstep_backtest.engine.backtest import BacktestEngine
from topstep_backtest.execution.sim_broker import SimBroker
from topstep_backtest.fills.bar_fill import BarFillModel
from topstep_backtest.fills.fees import TopstepFees
from topstep_backtest.rules.kernel import CombineKernel
from topstep_backtest.rules.params import AccountSize, combine_params

CONTRACT = "CON.F.US.MNQ.U26"


async def main() -> None:
    # -- data: a seeded synthetic tape (identical to run_combine.py) --------
    spec = spec_for_symbol("MNQ")
    bars = synthetic_bars(
        contract_id=CONTRACT,
        spec=spec,
        start_day=date(2026, 5, 4),
        days=40,
        seed=7,
        start_price=Decimal("23000.00"),
        drift_ticks_per_day=30,  # a gently rising tape
        vol_ticks=10,
    )

    # -- rules: real Combine economics — parameters, never knobs ------------
    kernel = CombineKernel(combine_params(AccountSize.S50K))

    # -- ONE clock, shared by broker AND engine ------------------------------
    # The engine advances this clock from feed timestamps, and the broker
    # stamps every accepted order's accepted_ts from it. Give the broker its
    # OWN clock and that clock sits at 0 forever: every order is stamped
    # accepted_ts=0, which makes it eligible for the CURRENT bar (silent
    # look-ahead) and runs every session check in 1970. Sharing this single
    # instance is the assembly invariant the Backtest facade exists to
    # enforce.
    clock = TestClock()

    # -- broker: fills, fees, rule enforcement behind the ctx protocols -----
    broker = SimBroker(
        account_id=1001,
        instruments={CONTRACT: spec},  # registered at construction ONLY; a bar
        # for an unregistered contract is a mid-run KeyError
        fill_model=BarFillModel(),  # Tier-0: next-bar-open markets, trade-through limits
        fee_model=TopstepFees(),  # real per-side fee schedule
        kernel=kernel,  # watches every equity tick, breaches intrabar
        clock=clock,
    )

    # -- strategy + engine: the engine binds ctx and drives handle_* --------
    strategy = SmaCross(CONTRACT, size=2)
    engine = BacktestEngine(feed=ListBarFeed(bars), broker=broker, strategy=strategy, clock=clock)

    # The facade's Report wraps exactly this frozen BacktestResult (plus
    # SummaryStats derived from it and broker.trades).
    result = await engine.run()

    print(f"verdict:        {result.verdict.name}  ({result.reason})")
    print(f"balance:        {result.starting_balance} -> {result.ending_balance}")
    print(f"total profit:   {result.total_profit}  (target {result.profit_target})")
    print(f"best day:       {result.best_day}  (consistency cap: 50% of total)")
    print(f"days traded:    {result.days_traded},  trades: {result.trade_count}")
    print(f"MLL floor:      {result.floor}")
    print("day trail:")
    for record in result.day_records:
        flag = " *" if record.had_trade else ""
        print(
            f"  {record.day}  eod={record.eod_balance:>12}  pnl={record.day_pnl:>10}"
            f"  floor={record.floor_after:>10}{flag}"
        )


if __name__ == "__main__":
    asyncio.run(main())
