"""Task 3 — `record_allocation`'s equity merge: `equity_allocation_inputs` wired in AFTER
`honest_allocation_inputs`, ONLY for funded `ibkr-equity` deploy sids, inside the `store is not None` branch.

BYTE-IDENTITY (Global Constraints): `record_allocation` with `store=None`, or with `store` but no equity
deploy sid, must be UNCHANGED — the equity provider must not even be called in either case.

Positive case: an equity deploy sid with a SHORT forward-nav record (<60 days, `policy.min_history_days`)
must NOT be dropped by the allocation engine's eligibility gate, because the merged honest series is much
longer than the forward record — the exact bug this task fixes (memory: "multistrat was dropped for <60
forward days")."""
from __future__ import annotations

import datetime as dt
import math

from fxhnt import registry
from fxhnt.adapters.persistence.forward_nav import ForwardNavRepo
from fxhnt.adapters.warehouse.timescale_feature_store import TimescaleFeatureStore
from fxhnt.application.allocation_ingest import record_allocation
from fxhnt.application.allocation_policy import AllocationPolicy
from fxhnt.application.forward_models import ForwardNavRow as NavRowDTO
from fxhnt.config import get_settings
from fxhnt.domain.strategies.multistrat import FUND_INSTRUMENTS

_BASE = dt.date(2026, 1, 1)
_AT = dt.datetime(2026, 7, 9, 23, 30)


def _seed_deploy_sid(repo: ForwardNavRepo, sid: str, *, days: int = 60, flip: int = 1) -> None:
    dates = [(_BASE + dt.timedelta(days=i)).isoformat() for i in range(days)]
    for i, d in enumerate(dates):
        repo.upsert_rows([NavRowDTO(strategy_id=sid, date=d,
                                    ret=(0.01 if (i % 2) == flip else -0.008), nav=1.0)], at=_AT)


def _seed_equity_pit(repo: ForwardNavRepo, *, days: int = 150) -> None:
    """Real-enough PIT closes+volumes for FUND_INSTRUMENTS (huge dollar-ADV so capacity never binds — this
    test isolates the eligibility/plumbing fix, not the cost model).

    `record_allocation` defaults `equity_execution_regime` to `"ucits"` (Task 2), so
    `equity_allocation_inputs` sources each sleeve's ADV from the UCITS-mapped ticker's OWN
    `yahoo_pit_volumes` (e.g. `CSPX.L`, not `SPY`) — the US ticker's volume seeded below is unused under
    the default regime. Seed the same honest, high-coverage volume series under EACH sleeve's
    `settings.ucits.map[sym].yahoo_ticker` too, so the ADV-coverage guard doesn't trip and the ucits-regime
    capacity model has real data to size against (mirrors the US seeding above, just under the UCITS key)."""
    ucits_map = get_settings().ucits.map
    for j, sym in enumerate(FUND_INSTRUMENTS):
        closes: dict[str, float] = {}
        volumes: dict[str, float] = {}
        price = 100.0 + 10.0 * j
        for d in range(days):
            date = (_BASE + dt.timedelta(days=d)).isoformat()
            price *= 1.0 + 0.004 * math.sin(0.05 * d + j)
            closes[date] = price
            volumes[date] = 5_000_000.0 + 200_000.0 * math.sin(0.07 * d + j)
        repo.snapshot_yahoo_closes(sym, closes, _AT)
        repo.snapshot_yahoo_volumes(sym, volumes, _AT)
        listing = ucits_map.get(sym)
        if listing is not None:
            repo.snapshot_yahoo_volumes(listing.yahoo_ticker, volumes, _AT)


def test_store_none_is_unaffected_even_with_an_equity_deploy_sid(tmp_path, monkeypatch):
    dsn = f"sqlite:///{tmp_path}/eq_none.db"
    repo = ForwardNavRepo(dsn)
    repo.migrate()
    monkeypatch.setitem(registry.STRATEGY_REGISTRY, "multistrat",
                        {"display_name": "M", "sleeve": "tradfi-multistrat", "venue": "ibkr-equity",
                         "gate_spec": {}, "tier": "deploy"})
    _seed_deploy_sid(repo, "multistrat", days=60)

    out_before = record_allocation(dsn, equity=20_000.0, store=None)

    # the equity provider must never even be CALLED when store is None -- prove it, don't just assume it.
    import fxhnt.application.equity_allocation_inputs as eq_mod

    def _boom(*_a, **_kw):
        raise AssertionError("equity_allocation_inputs must not be called when store is None")

    monkeypatch.setattr(eq_mod, "equity_allocation_inputs", _boom)
    out_after = record_allocation(dsn, equity=20_000.0, store=None)

    assert out_after == out_before


def test_store_present_no_equity_sid_skips_provider_entirely(tmp_path, monkeypatch):
    dsn = f"sqlite:///{tmp_path}/eq_no_sid.db"
    repo = ForwardNavRepo(dsn)
    repo.migrate()
    monkeypatch.setitem(registry.STRATEGY_REGISTRY, "d1",
                        {"display_name": "D1", "sleeve": "x", "gate_spec": {}, "tier": "deploy"})
    _seed_deploy_sid(repo, "d1", days=60)
    store = TimescaleFeatureStore("sqlite://", table="bybit_features")

    import fxhnt.application.equity_allocation_inputs as eq_mod

    def _boom(*_a, **_kw):
        raise AssertionError("equity_allocation_inputs must not be called with no ibkr-equity deploy sid")

    monkeypatch.setattr(eq_mod, "equity_allocation_inputs", _boom)

    out = record_allocation(dsn, equity=20_000.0, store=store)
    store.close()

    assert "d1" in out


def test_equity_sid_with_short_forward_history_is_not_dropped(tmp_path, monkeypatch):
    dsn = f"sqlite:///{tmp_path}/eq_sized.db"
    repo = ForwardNavRepo(dsn)
    repo.migrate()
    monkeypatch.setitem(registry.STRATEGY_REGISTRY, "multistrat",
                        {"display_name": "M", "sleeve": "tradfi-multistrat", "venue": "ibkr-equity",
                         "gate_spec": {}, "tier": "deploy"})
    _seed_equity_pit(repo, days=150)
    # SHORT forward nav record -- well under policy.min_history_days (60) -- the pre-fix drop condition.
    _seed_deploy_sid(repo, "multistrat", days=20)
    store = TimescaleFeatureStore("sqlite://", table="bybit_features")
    # capacity_min_sharpe=-10 isolates the eligibility/plumbing fix from the modeled book's actual Sharpe
    # sign; capital_ceiling raised so the $100k target isn't clipped by the human gross-dollar gate.
    pol = AllocationPolicy(capacity_min_sharpe=-10.0, capital_ceiling=1_000_000.0, marginal_gate=False)

    out = record_allocation(dsn, equity=100_000.0, store=store, policy=pol)
    store.close()

    assert "multistrat" in out
    assert out["multistrat"] != 0.0


def test_equity_sid_alone_with_only_short_forward_history_and_no_merge_would_be_dropped(tmp_path, monkeypatch):
    """Sanity check on the fixture itself: WITHOUT the equity merge (no PIT data seeded, so
    equity_allocation_inputs finds nothing and the sid falls back to its 20-day nav record), the sid IS
    dropped by the eligibility gate -- proving the positive test above is exercising the real fix, not a
    fixture that would pass regardless."""
    dsn = f"sqlite:///{tmp_path}/eq_dropped.db"
    repo = ForwardNavRepo(dsn)
    repo.migrate()
    monkeypatch.setitem(registry.STRATEGY_REGISTRY, "multistrat",
                        {"display_name": "M", "sleeve": "tradfi-multistrat", "venue": "ibkr-equity",
                         "gate_spec": {}, "tier": "deploy"})
    _seed_deploy_sid(repo, "multistrat", days=20)  # no PIT data seeded -> no honest series available
    store = TimescaleFeatureStore("sqlite://", table="bybit_features")
    pol = AllocationPolicy(capacity_min_sharpe=-10.0, capital_ceiling=1_000_000.0, marginal_gate=False)

    out = record_allocation(dsn, equity=100_000.0, store=store, policy=pol)
    store.close()

    assert out.get("multistrat", 0.0) == 0.0
