"""D2.2/2.3 (Task 3) — exec_vs_sim: THE one shared "how closely real trades follow the plan" signal, reused
by the holdings detail page (this task) and D3/D4 overview surfacing later. Compares a strategy's EXECUTED
forward track's cumulative return (`<sid>_exec`) against its MODELED/sim forward cumulative return (`sid`) —
both already-persisted `forward_summary` numbers (no new compute), plus a median of the strategy-tagged
`exec_fills.shortfall_bps` and a fee total. Fakes stand in for `IbkrAccountRepo` (`fills_for`) and
`ForwardNavRepo` (`cum_return`) — sid-agnostic (matched only by the `_exec` suffix), so the same Fake works
for any strategy_id."""
from __future__ import annotations

from dataclasses import dataclass

from fxhnt.application.exec_reconcile import exec_vs_sim


@dataclass
class _FakeFill:
    shortfall_bps: float
    fee: float


class FakeExec:
    """Stands in for `IbkrAccountRepo.fills_for`. `cum` is accepted (mirrors the real repo's shape from the
    caller's POV) but unused here — the executed cumulative return comes from the forward repo, not the
    fills table."""
    def __init__(self, cum: float | None, fills_bps: list[float], fees: float) -> None:
        self.cum = cum
        n = len(fills_bps)
        per_fill_fee = (fees / n) if n else 0.0
        self._fills = [_FakeFill(shortfall_bps=b, fee=per_fill_fee) for b in fills_bps]

    def fills_for(self, strategy_id: str, limit: int = 20) -> list[_FakeFill]:
        return self._fills


class FakeFwd:
    """Stands in for `ForwardNavRepo.cum_return`. Sid-agnostic: any id ending in `_exec` gets `exec_cum`,
    everything else gets `sim_cum` — so one Fake works regardless of the base strategy_id under test."""
    def __init__(self, exec_cum: float | None, sim_cum: float | None) -> None:
        self._exec_cum = exec_cum
        self._sim_cum = sim_cum

    def cum_return(self, strategy_id: str) -> float | None:
        return self._exec_cum if strategy_id.endswith("_exec") else self._sim_cum


def test_divergence_and_slippage():
    r = exec_vs_sim("multistrat", exec_repo=FakeExec(cum=0.031, fills_bps=[3.0, 5.0, 4.0], fees=12.0),
                    forward_repo=FakeFwd(exec_cum=0.031, sim_cum=0.038))
    assert round(r.divergence, 4) == -0.007      # exec 3.1% vs sim 3.8%
    assert r.median_slippage_bps == 4.0
    assert r.has_exec is True
    assert round(r.cum_exec_return, 4) == 0.031
    assert round(r.cum_sim_return, 4) == 0.038
    assert round(r.total_fees, 4) == 12.0


def test_no_exec_data():
    r = exec_vs_sim("sixtyforty", exec_repo=FakeExec(cum=None, fills_bps=[], fees=0.0),
                    forward_repo=FakeFwd(exec_cum=None, sim_cum=0.05))
    assert r.has_exec is False
    assert r.cum_exec_return == 0.0
    assert r.cum_sim_return == 0.0
    assert r.divergence == 0.0
    assert r.median_slippage_bps == 0.0
    assert r.total_fees == 0.0
