"""risk_gates: hard pre-trade gate + dynamic gross scaler (edge-decay θ × Kelly fraction)."""
from __future__ import annotations

from fxhnt.domain.execution.risk_gates import GateConfig, gross_scaler, pretrade_block


def _cfg(**kw):
    base = dict(allow_live=True, kill_switch=False, testnet=False, gross_cap=1.0, session_dd=-0.1, kelly_cap=0.25)
    base.update(kw)
    return GateConfig(**base)


def test_kill_switch_blocks() -> None:
    assert pretrade_block(_cfg(kill_switch=True), session_pnl_pct=0.0) == "kill_switch"


def test_live_requires_allow_live() -> None:
    assert pretrade_block(_cfg(allow_live=False, testnet=False), session_pnl_pct=0.0) == "allow_live"


def test_session_drawdown_breaker() -> None:
    assert pretrade_block(_cfg(session_dd=-0.1), session_pnl_pct=-0.12) == "session_drawdown"


def test_clear_when_safe() -> None:
    assert pretrade_block(_cfg(), session_pnl_pct=-0.02) is None


def test_testnet_bypasses_allow_live() -> None:
    assert pretrade_block(_cfg(allow_live=False, testnet=True), session_pnl_pct=0.0) is None


def test_gross_scaler_shrinks_with_decay_and_kelly() -> None:
    full = gross_scaler(edge_theta=1.0, kelly_fraction=1.0, kelly_cap=0.25, gross_cap=1.0)
    assert abs(full - 0.25) < 1e-9                         # kelly capped at 0.25, theta=1 -> 0.25
    decayed = gross_scaler(edge_theta=0.2, kelly_fraction=1.0, kelly_cap=0.25, gross_cap=1.0)
    assert decayed < full                                  # edge decay shrinks gross
    assert gross_scaler(edge_theta=0.0, kelly_fraction=1.0, kelly_cap=0.25, gross_cap=1.0) == 0.0


# --- additional rigorous edge-branch tests ---

def test_kill_switch_takes_priority_over_allow_live() -> None:
    """kill_switch checked first — even if allow_live is False, reason is kill_switch."""
    assert pretrade_block(_cfg(kill_switch=True, allow_live=False, testnet=False),
                          session_pnl_pct=0.0) == "kill_switch"


def test_session_dd_exact_threshold_is_blocked() -> None:
    """session_pnl_pct == session_dd is at the threshold (<=) — must block."""
    assert pretrade_block(_cfg(session_dd=-0.10), session_pnl_pct=-0.10) == "session_drawdown"


def test_session_dd_just_above_threshold_clears() -> None:
    """session_pnl_pct just above threshold (e.g. -0.09) must not block."""
    assert pretrade_block(_cfg(session_dd=-0.10), session_pnl_pct=-0.09) is None


def test_gross_scaler_kelly_cap_clamp() -> None:
    """kelly_fraction > kelly_cap is clamped to kelly_cap before multiplying."""
    result = gross_scaler(edge_theta=1.0, kelly_fraction=5.0, kelly_cap=0.25, gross_cap=1.0)
    assert abs(result - 0.25) < 1e-9


def test_gross_scaler_gross_cap_binding() -> None:
    """gross_cap is the outer ceiling: theta×kelly cannot exceed it."""
    result = gross_scaler(edge_theta=1.0, kelly_fraction=1.0, kelly_cap=1.0, gross_cap=0.5)
    assert abs(result - 0.5) < 1e-9


def test_gross_scaler_theta_clamped_to_zero() -> None:
    """Negative theta is clamped to 0.0 — result must be 0."""
    assert gross_scaler(edge_theta=-5.0, kelly_fraction=1.0, kelly_cap=0.25, gross_cap=1.0) == 0.0


def test_gross_scaler_theta_clamped_to_one() -> None:
    """theta > 1 is clamped to 1.0 — must not exceed what theta=1 gives."""
    result_over = gross_scaler(edge_theta=2.0, kelly_fraction=1.0, kelly_cap=0.25, gross_cap=1.0)
    result_one = gross_scaler(edge_theta=1.0, kelly_fraction=1.0, kelly_cap=0.25, gross_cap=1.0)
    assert abs(result_over - result_one) < 1e-9


def test_gross_scaler_theta_zero_always_zero() -> None:
    """theta=0 means no edge; result must be 0 regardless of kelly / gross_cap."""
    assert gross_scaler(edge_theta=0.0, kelly_fraction=1.0, kelly_cap=1.0, gross_cap=10.0) == 0.0


def test_gross_scaler_partial_theta_and_kelly() -> None:
    """Intermediate values: theta=0.5, kelly=0.5 (< kelly_cap=0.25? no: 0.5>0.25 -> clamp to 0.25).
    Expect: 0.5 * 0.25 = 0.125, bounded by gross_cap=1.0."""
    result = gross_scaler(edge_theta=0.5, kelly_fraction=0.5, kelly_cap=0.25, gross_cap=1.0)
    assert abs(result - 0.5 * 0.25) < 1e-9


def test_gross_scaler_kelly_below_cap_used_directly() -> None:
    """If kelly_fraction < kelly_cap, use kelly_fraction directly (no clamp needed)."""
    result = gross_scaler(edge_theta=1.0, kelly_fraction=0.10, kelly_cap=0.25, gross_cap=1.0)
    assert abs(result - 0.10) < 1e-9


def test_testnet_plus_kill_switch_still_blocks() -> None:
    """testnet bypasses allow_live check but kill_switch must still block."""
    assert pretrade_block(_cfg(kill_switch=True, testnet=True, allow_live=False),
                          session_pnl_pct=0.0) == "kill_switch"


def test_testnet_session_dd_still_blocks() -> None:
    """testnet bypasses allow_live but session_drawdown check is independent."""
    assert pretrade_block(_cfg(testnet=True, allow_live=False, session_dd=-0.10),
                          session_pnl_pct=-0.15) == "session_drawdown"


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

def test_gross_scaler_nan_edge_theta_returns_zero() -> None:
    """NaN edge_theta -> 0.0 (flat). EMA not yet warmed up must not invert to max exposure."""
    assert gross_scaler(edge_theta=float("nan"), kelly_fraction=1.0, kelly_cap=0.25, gross_cap=1.0) == 0.0


def test_gross_scaler_nan_kelly_fraction_returns_zero() -> None:
    """NaN kelly_fraction (e.g. avg_win=0 divide-by-zero) -> 0.0 (flat)."""
    assert gross_scaler(edge_theta=1.0, kelly_fraction=float("nan"), kelly_cap=0.25, gross_cap=1.0) == 0.0


def test_gross_scaler_nan_kelly_cap_returns_zero() -> None:
    """NaN kelly_cap -> 0.0 (flat). Misconfigured cap must not silently pass through."""
    assert gross_scaler(edge_theta=1.0, kelly_fraction=1.0, kelly_cap=float("nan"), gross_cap=1.0) == 0.0


def test_gross_scaler_nan_gross_cap_returns_zero() -> None:
    """NaN gross_cap -> 0.0 (flat). Missing config must not open max exposure."""
    assert gross_scaler(edge_theta=1.0, kelly_fraction=1.0, kelly_cap=0.25, gross_cap=float("nan")) == 0.0


def test_gross_scaler_inf_gross_cap_returns_zero() -> None:
    """inf gross_cap -> 0.0 (flat). Non-finite is not a valid configuration."""
    assert gross_scaler(edge_theta=1.0, kelly_fraction=1.0, kelly_cap=0.25, gross_cap=float("inf")) == 0.0


def test_gross_scaler_negative_gross_cap_returns_zero() -> None:
    """Negative gross_cap (misconfiguration) must return 0.0, never a negative value that inverts positions."""
    result = gross_scaler(edge_theta=1.0, kelly_fraction=1.0, kelly_cap=0.25, gross_cap=-0.5)
    assert result == 0.0


def test_pretrade_block_nan_session_pnl_blocks() -> None:
    """NaN session_pnl_pct -> block with 'session_pnl_unknown'. Unknown PnL state must not allow trading."""
    assert pretrade_block(_cfg(), session_pnl_pct=float("nan")) == "session_pnl_unknown"


def test_pretrade_block_bad_session_dd_positive_blocks() -> None:
    """session_dd >= 0 is a misconfiguration (must be negative threshold) -> block with 'bad_session_dd_config'."""
    assert pretrade_block(_cfg(session_dd=0.10), session_pnl_pct=0.0) == "bad_session_dd_config"


def test_pretrade_block_bad_session_dd_zero_blocks() -> None:
    """session_dd == 0 is non-negative, hence misconfigured -> block."""
    assert pretrade_block(_cfg(session_dd=0.0), session_pnl_pct=0.0) == "bad_session_dd_config"


def test_pretrade_block_bad_session_dd_nan_blocks() -> None:
    """NaN session_dd is non-finite, hence misconfigured -> block with 'bad_session_dd_config'."""
    assert pretrade_block(_cfg(session_dd=float("nan")), session_pnl_pct=0.0) == "bad_session_dd_config"


def test_pretrade_block_kill_switch_priority_over_bad_session_dd() -> None:
    """kill_switch is checked before session_dd config validation — kill_switch wins."""
    assert pretrade_block(_cfg(kill_switch=True, session_dd=0.10), session_pnl_pct=0.0) == "kill_switch"
