"""capacity.cap_weights: never let a symbol's target notional exceed per_symbol_cap×NLV or a share of ADV."""
from __future__ import annotations

from fxhnt.domain.execution.capacity import cap_weights


def test_per_symbol_cap_binds() -> None:
    w = {"A": 0.5, "B": -0.5}
    out = cap_weights(w, nlv=10_000.0, adv_usd={"A": 1e9, "B": 1e9}, per_symbol_cap=0.05, participation_rate=1.0)
    assert abs(out["A"]) <= 0.05 + 1e-9 and abs(out["B"]) <= 0.05 + 1e-9


def test_adv_cap_binds_for_thin_symbol() -> None:
    # B is thin: ADV 1000 USD, participation 0.05 -> max 50 USD; at NLV 10k that's weight 0.005
    out = cap_weights({"A": 0.5, "B": 0.5}, nlv=10_000.0, adv_usd={"A": 1e9, "B": 1000.0},
                      per_symbol_cap=1.0, participation_rate=0.05)
    assert abs(out["B"]) <= 0.005 + 1e-9
    assert abs(out["A"]) > abs(out["B"])


def test_nlv_zero_returns_zeros() -> None:
    """NLV ≤ 0 means no capital — all weights must be zeroed."""
    out = cap_weights({"A": 0.5, "B": -0.3}, nlv=0.0, adv_usd={"A": 1e9, "B": 1e9},
                      per_symbol_cap=0.05, participation_rate=1.0)
    assert out == {"A": 0.0, "B": 0.0}


def test_nlv_negative_returns_zeros() -> None:
    out = cap_weights({"X": 0.99}, nlv=-500.0, adv_usd={"X": 1e9},
                      per_symbol_cap=0.1, participation_rate=1.0)
    assert out == {"X": 0.0}


def test_per_symbol_cap_preserves_sign() -> None:
    """Clamp must preserve sign: a short weight stays short after capping."""
    out = cap_weights({"A": -0.8}, nlv=10_000.0, adv_usd={"A": 1e9},
                      per_symbol_cap=0.05, participation_rate=1.0)
    assert out["A"] == -0.05


def test_symbol_missing_from_adv_uses_only_per_symbol_cap() -> None:
    """If a symbol has no ADV entry, only the per_symbol_cap applies (no ADV cap)."""
    out = cap_weights({"Z": 0.99}, nlv=10_000.0, adv_usd={},
                      per_symbol_cap=0.10, participation_rate=0.05)
    assert abs(out["Z"]) <= 0.10 + 1e-9
    assert abs(out["Z"]) > 0.05   # ADV cap did NOT apply (no ADV entry)


def test_adv_cap_binding_vs_per_symbol_cap() -> None:
    """When ADV cap is tighter than per_symbol_cap, ADV cap wins."""
    # ADV=100, participation=0.05 -> max 5 USD; NLV=10k -> max_w = 0.0005
    # per_symbol_cap=0.5 (much larger) — ADV cap must bind
    out = cap_weights({"A": 0.5}, nlv=10_000.0, adv_usd={"A": 100.0},
                      per_symbol_cap=0.5, participation_rate=0.05)
    assert abs(out["A"]) <= 0.0005 + 1e-9


def test_empty_weights_returns_empty() -> None:
    assert cap_weights({}, nlv=10_000.0, adv_usd={}, per_symbol_cap=0.1, participation_rate=1.0) == {}


def test_weight_within_caps_unchanged() -> None:
    """A weight already under both caps must pass through unmodified."""
    w = {"A": 0.02}
    out = cap_weights(w, nlv=10_000.0, adv_usd={"A": 1e9},
                      per_symbol_cap=0.05, participation_rate=1.0)
    assert abs(out["A"] - 0.02) < 1e-12


# --- M1: NaN / non-finite safety tests ---

def test_nan_nlv_returns_all_zeros() -> None:
    """NaN NLV means capital is unknown — zero all weights rather than risk max exposure."""
    out = cap_weights({"A": 0.5, "B": -0.3}, nlv=float("nan"), adv_usd={"A": 1e9, "B": 1e9},
                      per_symbol_cap=0.05, participation_rate=1.0)
    assert out == {"A": 0.0, "B": 0.0}


def test_inf_nlv_returns_all_zeros() -> None:
    """inf NLV is not a valid capital figure — zero all weights."""
    out = cap_weights({"A": 0.5}, nlv=float("inf"), adv_usd={"A": 1e9},
                      per_symbol_cap=0.05, participation_rate=1.0)
    assert out == {"A": 0.0}


def test_nan_weight_zeroed_others_capped_normally() -> None:
    """A NaN weight for one symbol must be zeroed; other symbols still get normal cap treatment."""
    out = cap_weights({"A": float("nan"), "B": 0.5}, nlv=10_000.0, adv_usd={"A": 1e9, "B": 1e9},
                      per_symbol_cap=0.05, participation_rate=1.0)
    assert out["A"] == 0.0
    assert abs(out["B"]) <= 0.05 + 1e-9
    assert abs(out["B"]) > 0.0   # B is not zeroed — only A is affected


def test_nan_adv_falls_back_to_per_symbol_cap() -> None:
    """NaN ADV for a symbol must be ignored; only per_symbol_cap applies (no ADV weakening the cap)."""
    out = cap_weights({"A": 0.99}, nlv=10_000.0, adv_usd={"A": float("nan")},
                      per_symbol_cap=0.10, participation_rate=0.05)
    # ADV cap NOT applied (NaN ADV skipped) → per_symbol_cap=0.10 is the only ceiling
    assert abs(out["A"]) <= 0.10 + 1e-9
    assert abs(out["A"]) > 0.05   # participation cap (0.05 × NaN / NLV) was NOT applied
