from fxhnt.application.backtest_refs import backtest_ref, registry_backtest_kind


class _ReplayStrat:
    def advance(self, last, extra):
        # a 3-day inception series -> a real BacktestSummary
        return [("2024-01-01", 0.01), ("2024-01-02", -0.005), ("2024-01-03", 0.008)], {}


def test_recompute_replay_produces_summary():
    sm = backtest_ref("multistrat", store=None, build_strategy=lambda: _ReplayStrat())
    assert sm is not None
    assert sm.strategy_id == "multistrat"
    assert sm.sharpe == sm.sharpe   # finite (not NaN)


def test_none_kind_returns_none():
    # sixtyforty is absolute-gated -> backtest.kind == "none" -> no ref
    assert registry_backtest_kind("sixtyforty") == "none"
    assert backtest_ref("sixtyforty", store=None) is None


def test_model_ref_kind_never_writes_its_own_summary():
    # multistrat_exec is backtest.kind == "model-ref": it reconciles against its MODELED counterpart's ref,
    # resolved at the gate layer (CockpitBacktestRefProvider._REF_ALIAS), NOT by recomputing/delegating here.
    # Regression: an earlier version delegated to backtest_ref(model_id, ...), which — if this module's caller
    # is ever generalized to pass the exec twin's OWN builder — would silently overwrite the real model's
    # backtest_summary row under a mislabeled sid. Must return None regardless of build_strategy.
    assert registry_backtest_kind("multistrat_exec") == "model-ref"
    sm = backtest_ref("multistrat_exec", store=None, build_strategy=lambda: _ReplayStrat())
    assert sm is None


def _epoch_day(iso: str) -> int:
    import datetime as _dt
    return (_dt.date.fromisoformat(iso) - _dt.date(1970, 1, 1)).days


def test_xsfunding_sleeve_produces_ref_bug_A(monkeypatch):
    """Regression for the live bug: xsfunding is reconciliation-gated but had NO backtest ref (gate stuck at
    WAIT forever). Its `sleeve` provider must yield a BacktestSummary from the funding panel — via the REAL
    `sleeve_returns_from_store(store, edge, ...) -> {epoch_day: ret}` contract (store FIRST, edge SECOND;
    NOT `sleeve_returns_from_store(edge, store) -> [(iso_date, ret), ...]` as an earlier draft assumed)."""
    import fxhnt.application.backtest_refs as mod
    import fxhnt.application.bybit_book_eval as bbe

    monkeypatch.setattr(mod, "registry_backtest_kind", lambda sid: "sleeve")

    sentinel_store = object()
    calls: list[tuple[object, str]] = []
    # deterministic 3-day {epoch_day: ret} series, inserted OUT OF epoch-day order so the test also catches a
    # missing/incorrect sort in the epoch_day -> iso_date row conversion.
    fake_series = {
        _epoch_day("2024-03-03"): 0.003,
        _epoch_day("2024-03-01"): 0.002,
        _epoch_day("2024-03-02"): 0.0015,
    }

    def _fake_sleeve_returns_from_store(store, edge, **kwargs):
        calls.append((store, edge))
        return dict(fake_series)

    monkeypatch.setattr(bbe, "sleeve_returns_from_store", _fake_sleeve_returns_from_store)

    sm = mod.backtest_ref("xsfunding", store=sentinel_store)

    assert sm is not None and sm.strategy_id == "xsfunding"
    # the signature fix: store passed FIRST, the strategy_id (edge) SECOND — mirrors
    # funding_dispersion_eval's sleeve_returns_from_store(bybit_store, "xsfunding", ...) call convention.
    assert calls == [(sentinel_store, "xsfunding")]
    # the {epoch_day: ret} dict was sorted by epoch_day before conversion to (iso_date, ret) rows: the last
    # row (the summary's `as_of`) must be the LATEST date, not the last-inserted dict entry.
    assert sm.as_of == "2024-03-03"
