import datetime as dt

from fxhnt.adapters.persistence.cockpit_models import StrategyAllocationRow
from fxhnt.adapters.persistence.forward_nav import ForwardNavRepo
from fxhnt.application.multistrat_exec_record import plan_and_record


class _FakePlan:
    def __init__(self):
        self.orders = []
        self.notes = []
        self.nlv = 100_000.0
        self.blocked = False
        self.executed = True


class _FakeExecSvc:
    def __init__(self, blocked=False, degraded=False):
        self.last = None
        self._blocked = blocked
        self._degraded = degraded          # orders attempted but a leg didn't fill -> executed False

    def rebalance_weights(self, name, weights, prices, *, max_gross, execute, contract_specs=None, sizing_capital=None):
        self.last = dict(weights)
        plan = _FakePlan()
        plan.blocked = self._blocked
        if self._degraded:
            plan.orders = [object()]       # an order WAS placed
            plan.executed = False          # ...but it did not fill
        return plan


def _seed_alloc(repo, dollars, at):
    repo.replace_allocations([StrategyAllocationRow(
        strategy_id="multistrat", target_weight=0.5, target_dollars=dollars, policy_version=1,
        policy_hash="h", as_of="2026-07-10", computed_at=at)], at)


def test_envelope_scales_weights_and_records_on_execute(tmp_path):
    repo = ForwardNavRepo(f"sqlite:///{tmp_path}/e.db")
    repo.migrate()
    at = dt.datetime(2026, 7, 10, 23, 30)
    _seed_alloc(repo, 10_000.0, at)
    svc = _FakeExecSvc()
    plan, env = plan_and_record(repo=repo, exec_svc=svc, within_weights={"SPY": 0.6, "IEF": 0.4},
                                prices={"SPY": 500.0, "IEF": 100.0}, nlv=100_000.0,
                                today="2026-07-10", at=at, max_gross=2.0, execute=True)
    assert env == 10_000.0
    assert abs(svc.last["SPY"] - 0.06) < 1e-12                       # 0.6 * 10k/100k
    assert repo.get_book_state("multistrat_exec_pos")["envelope"] == 10_000.0   # recorded
    # positions_after is the TARGET book we rebalanced to: SPY 0.06*100k/500=12, IEF 0.04*100k/100=40
    pos_after = repo.get_book_state("multistrat_exec_pos")["positions"]
    assert pos_after.keys() == {"SPY", "IEF"}
    assert abs(pos_after["SPY"] - 12.0) < 1e-9
    assert abs(pos_after["IEF"] - 40.0) < 1e-9


def test_zero_allocation_flattens(tmp_path):
    repo = ForwardNavRepo(f"sqlite:///{tmp_path}/z.db")
    repo.migrate()
    at = dt.datetime(2026, 7, 10, 23, 30)                            # no allocation row for multistrat
    svc = _FakeExecSvc()
    plan, env = plan_and_record(repo=repo, exec_svc=svc, within_weights={"SPY": 0.6, "IEF": 0.4},
                                prices={"SPY": 500.0, "IEF": 100.0}, nlv=100_000.0,
                                today="2026-07-10", at=at, max_gross=2.0, execute=False)
    assert env == 0.0 and svc.last == {"SPY": 0.0, "IEF": 0.0}       # $0/absent → flatten


def test_blocked_plan_records_nothing(tmp_path):
    """A safety-gate-tripped plan (blocked=True, zero orders placed) must NOT be recorded: no forward_record
    row for multistrat_exec, and the multistrat_exec_pos book-state must stay untouched (still empty on a
    first run) — the account did not change, so the basis must not either."""
    repo = ForwardNavRepo(f"sqlite:///{tmp_path}/b.db")
    repo.migrate()
    at = dt.datetime(2026, 7, 10, 23, 30)
    _seed_alloc(repo, 10_000.0, at)
    svc = _FakeExecSvc(blocked=True)
    plan, env = plan_and_record(repo=repo, exec_svc=svc, within_weights={"SPY": 0.6, "IEF": 0.4},
                                prices={"SPY": 500.0, "IEF": 100.0}, nlv=100_000.0,
                                today="2026-07-10", at=at, max_gross=2.0, execute=True)
    assert plan.blocked is True
    assert repo.get_book_state("multistrat_exec_pos") == {}                    # unchanged — still empty
    assert repo.nav_history("multistrat_exec") == []                           # no forward_record row
    assert not any(s.strategy_id == "multistrat_exec" for s in repo.all_summaries())


def test_degraded_plan_records_nothing(tmp_path):
    """A DEGRADED plan (orders placed but a leg did NOT fill — Error 478/10349 live 2026-07-20) must NOT be
    recorded: recording would advance the basis to the INTENDED target book, which the account does not hold
    (the un-filled leg), permanently diverging the executed track. Same skip as a blocked plan — err
    false-WAIT."""
    repo = ForwardNavRepo(f"sqlite:///{tmp_path}/d.db")
    repo.migrate()
    at = dt.datetime(2026, 7, 10, 23, 30)
    _seed_alloc(repo, 10_000.0, at)
    svc = _FakeExecSvc(degraded=True)
    plan, env = plan_and_record(repo=repo, exec_svc=svc, within_weights={"SPY": 0.6, "IEF": 0.4},
                                prices={"SPY": 500.0, "IEF": 100.0}, nlv=100_000.0,
                                today="2026-07-10", at=at, max_gross=2.0, execute=True)
    assert plan.executed is False and plan.orders                              # degraded: placed but unfilled
    assert repo.get_book_state("multistrat_exec_pos") == {}                    # NOT advanced
    assert repo.nav_history("multistrat_exec") == []                           # no phantom forward_record
    assert not any(s.strategy_id == "multistrat_exec" for s in repo.all_summaries())
