from fxhnt.application.forward_definition import definition_hash, inception_override, track_definition
from fxhnt.registry import STRATEGY_REGISTRY


def test_hash_is_stable_under_key_reordering():
    assert definition_hash({"a": 1, "b": 2}, 1) == definition_hash({"b": 2, "a": 1}, 1)


def test_hash_changes_when_a_param_changes():
    # the BTC-drop case: instruments 6 -> 5 must change the hash
    six = definition_hash({"instruments": ["SPY", "IEF", "GLD", "PDBC", "DBMF", "BTC-USD"]}, 1)
    five = definition_hash({"instruments": ["SPY", "IEF", "GLD", "PDBC", "DBMF"]}, 1)
    assert six != five


def test_hash_changes_when_version_bumps():
    assert definition_hash({"a": 1}, 1) != definition_hash({"a": 1}, 2)


def test_every_active_track_declares_a_definition():
    for sid in STRATEGY_REGISTRY:
        params, version, mode = track_definition(sid)
        assert isinstance(params, dict) and isinstance(version, int)
        assert mode in ("recompute", "observed")


def test_observed_mode_is_exactly_the_executed_twins():
    # Pin is deliberately exhaustive: whenever a new track adopts record_mode="observed", update this set
    # (C2 Task 3 added bybit_4edge_exec) so the change is reviewed, not silently absorbed. `xsfunding` moved
    # OFF observed (Phase 0b venue consolidation: standalone Binance `XsFundingForward` -> its own Bybit
    # single-sleeve `recompute` track, mirroring `positioning`) — the remaining observed tracks are the
    # OBSERVED-fills executed twins (fills are observed, not recomputable).
    observed = {sid for sid in STRATEGY_REGISTRY if track_definition(sid)[2] == "observed"}
    assert observed == {"multistrat_exec", "bybit_4edge_exec"}


def test_inception_override_returns_date_when_version_matches(monkeypatch):
    monkeypatch.setitem(STRATEGY_REGISTRY, "t_ov",
                        {"definition": {"params": {"v": 1}, "version": 2, "record_mode": "recompute",
                                        "inception_t0": {"date": "2026-06-20", "version": 2}}})
    assert inception_override("t_ov") == "2026-06-20"


def test_inception_override_ignored_when_version_stale(monkeypatch):
    # a genuine re-tune bumped the definition to v3 but left the v2 override → stale → ignored (fresh clock)
    monkeypatch.setitem(STRATEGY_REGISTRY, "t_ov",
                        {"definition": {"params": {"v": 3}, "version": 3, "record_mode": "recompute",
                                        "inception_t0": {"date": "2026-06-20", "version": 2}}})
    assert inception_override("t_ov") is None


def test_inception_override_absent_returns_none(monkeypatch):
    monkeypatch.setitem(STRATEGY_REGISTRY, "t_ov",
                        {"definition": {"params": {"v": 1}, "version": 1, "record_mode": "recompute"}})
    assert inception_override("t_ov") is None


def test_inception_t0_is_not_hashed():
    # the recovered-window pin MUST live outside `params` — else declaring it would change the definition hash
    # and itself re-incept the live anchor, resetting the OOS clock (the exact opposite of its purpose).
    for sid in ("xsfunding", "unlock", "positioning", "bybit_4edge", "bybit_4edge_levered"):
        assert "inception_t0" not in track_definition(sid)[0]


def test_re_homed_sleeves_pin_their_recovered_inception():
    # Regression: the Phase-0b faithful re-homes keep their pinned pre-re-home OOS start (recovered 2026-07-15)
    # so a DB rebuild reproduces the window from git instead of restarting at today. If a sleeve is genuinely
    # re-tuned later, its version bumps and the (now version-stale) pin is correctly ignored — update it here.
    # 2026-07-20: positioning coin_gross_cap=0.07 re-inception (v3) backdated bybit_4edge/bybit_4edge_levered's
    # pin from 2026-07-07 to 2026-07-06 (positioning's date, unchanged) so the whole book re-warms together.
    expected = {"xsfunding": "2026-07-07", "unlock": "2026-06-20", "positioning": "2026-07-06",
                "bybit_4edge": "2026-07-06", "bybit_4edge_levered": "2026-07-06"}
    for sid, date in expected.items():
        assert inception_override(sid) == date
