"""TDD for :mod:`cerbero_bite.core.liquidity_gate`.

Spec: ``docs/01-strategy-rules.md §4`` and ``docs/03-algorithms.md §2``.
"""

from __future__ import annotations

from decimal import Decimal

import pytest

from cerbero_bite.config import StrategyConfig, golden_config
from cerbero_bite.core.liquidity_gate import InstrumentSnapshot, check


def _snap(
    *,
    instrument: str = "ETH-13MAY26-2400-P",
    bid: str = "0.090",
    ask: str = "0.100",
    mid: str = "0.095",
    open_interest: int = 500,
    volume_24h: int = 80,
    book_depth_top3: int = 20,
) -> InstrumentSnapshot:
    return InstrumentSnapshot(
        instrument=instrument,
        bid=Decimal(bid),
        ask=Decimal(ask),
        mid=Decimal(mid),
        open_interest=open_interest,
        volume_24h=volume_24h,
        book_depth_top3=book_depth_top3,
    )


@pytest.fixture
def cfg() -> StrategyConfig:
    return golden_config()


# Two-leg vertical: short at 0.095 mid, long at 0.060 mid → credit 0.035 ETH.
# Tight book: per-contract slippage 0.001 ETH → ~2.86% of credit at n=1.
def _good_pair() -> tuple[InstrumentSnapshot, InstrumentSnapshot]:
    short = _snap(bid="0.0945", ask="0.0955", mid="0.0950")
    long_ = _snap(
        instrument="ETH-13MAY26-2300-P",
        bid="0.0595",
        ask="0.0605",
        mid="0.0600",
    )
    return short, long_


def test_clean_pair_passes(cfg: StrategyConfig) -> None:
    # tight book: per-contract slippage 0.001 ETH → 2.86% of 0.035 credit.
    short, long_ = _good_pair()
    res = check(
        short_leg=short,
        long_leg=long_,
        credit=Decimal("0.035"),
        n_contracts=1,
        cfg=cfg,
    )
    assert res.accepted is True
    assert res.reasons == []


def test_short_leg_oi_below_threshold_fails(cfg: StrategyConfig) -> None:
    short, long_ = _good_pair()
    short = _snap(
        instrument=short.instrument,
        bid=str(short.bid),
        ask=str(short.ask),
        mid=str(short.mid),
        open_interest=99,
    )
    res = check(
        short_leg=short,
        long_leg=long_,
        credit=Decimal("0.035"),
        n_contracts=1,
        cfg=cfg,
    )
    assert res.accepted is False
    assert any("open interest" in r for r in res.reasons)


def test_long_leg_volume_below_threshold_fails(cfg: StrategyConfig) -> None:
    short, long_ = _good_pair()
    long_ = _snap(
        instrument=long_.instrument,
        bid=str(long_.bid),
        ask=str(long_.ask),
        mid=str(long_.mid),
        volume_24h=10,
    )
    res = check(
        short_leg=short,
        long_leg=long_,
        credit=Decimal("0.035"),
        n_contracts=1,
        cfg=cfg,
    )
    assert res.accepted is False
    assert any("volume" in r for r in res.reasons)


def test_spread_pct_above_cap_fails(cfg: StrategyConfig) -> None:
    short, long_ = _good_pair()
    # Make bid-ask huge: bid 0.05, ask 0.20 → spread/mid > 0.15
    short = _snap(
        instrument=short.instrument,
        bid="0.050",
        ask="0.200",
        mid="0.125",
    )
    res = check(
        short_leg=short,
        long_leg=long_,
        credit=Decimal("0.060"),
        n_contracts=1,
        cfg=cfg,
    )
    assert res.accepted is False
    assert any("bid-ask" in r for r in res.reasons)


def test_book_depth_below_threshold_fails(cfg: StrategyConfig) -> None:
    short, long_ = _good_pair()
    short = _snap(
        instrument=short.instrument,
        bid=str(short.bid),
        ask=str(short.ask),
        mid=str(short.mid),
        book_depth_top3=4,
    )
    res = check(
        short_leg=short,
        long_leg=long_,
        credit=Decimal("0.035"),
        n_contracts=1,
        cfg=cfg,
    )
    assert res.accepted is False
    assert any("depth" in r for r in res.reasons)


def test_slippage_above_8pct_fails(cfg: StrategyConfig) -> None:
    # tight book * n=10 → 0.010 / 0.035 ≈ 28% slippage
    short, long_ = _good_pair()
    res = check(
        short_leg=short,
        long_leg=long_,
        credit=Decimal("0.035"),
        n_contracts=10,
        cfg=cfg,
    )
    assert res.accepted is False
    assert any("slippage" in r for r in res.reasons)
    assert res.estimated_slippage_pct_of_credit > Decimal("0.08")


def test_slippage_well_below_cap_passes(cfg: StrategyConfig) -> None:
    # tight book: short ask-mid = 0.0005, long mid-bid = 0.0005 → slip = 0.001 per
    # contract, credit 0.035 → 2.85% per contract.
    short = _snap(bid="0.0945", ask="0.0955", mid="0.0950")
    long_ = _snap(
        instrument="ETH-13MAY26-2300-P",
        bid="0.0595",
        ask="0.0605",
        mid="0.0600",
    )
    res = check(
        short_leg=short,
        long_leg=long_,
        credit=Decimal("0.035"),
        n_contracts=1,
        cfg=cfg,
    )
    assert res.accepted is True
    assert res.estimated_slippage_pct_of_credit < Decimal("0.08")


def test_zero_credit_is_rejected(cfg: StrategyConfig) -> None:
    short, long_ = _good_pair()
    res = check(
        short_leg=short,
        long_leg=long_,
        credit=Decimal("0"),
        n_contracts=1,
        cfg=cfg,
    )
    assert res.accepted is False
    assert any("credit" in r for r in res.reasons)


def test_negative_credit_is_rejected(cfg: StrategyConfig) -> None:
    short, long_ = _good_pair()
    res = check(
        short_leg=short,
        long_leg=long_,
        credit=Decimal("-0.001"),
        n_contracts=1,
        cfg=cfg,
    )
    assert res.accepted is False


def test_zero_or_negative_n_contracts_is_rejected(cfg: StrategyConfig) -> None:
    short, long_ = _good_pair()
    for n in (0, -1):
        res = check(
            short_leg=short,
            long_leg=long_,
            credit=Decimal("0.035"),
            n_contracts=n,
            cfg=cfg,
        )
        assert res.accepted is False
        assert any("contracts" in r for r in res.reasons)


def test_non_positive_mid_is_flagged(cfg: StrategyConfig) -> None:
    short, long_ = _good_pair()
    bad_short = _snap(
        instrument=short.instrument,
        bid="0",
        ask="0",
        mid="0",
        open_interest=short.open_interest,
        volume_24h=short.volume_24h,
        book_depth_top3=short.book_depth_top3,
    )
    res = check(
        short_leg=bad_short,
        long_leg=long_,
        credit=Decimal("0.035"),
        n_contracts=1,
        cfg=cfg,
    )
    assert res.accepted is False
    assert any("mid price not positive" in r for r in res.reasons)


def test_failures_accumulate(cfg: StrategyConfig) -> None:
    short = _snap(open_interest=10, volume_24h=2, book_depth_top3=1, bid="0.001", ask="0.500")
    long_ = _snap(
        instrument="ETH-13MAY26-2300-P",
        open_interest=5,
        volume_24h=1,
        book_depth_top3=1,
        bid="0.001",
        ask="0.500",
    )
    res = check(
        short_leg=short,
        long_leg=long_,
        credit=Decimal("0.001"),
        n_contracts=10,
        cfg=cfg,
    )
    assert res.accepted is False
    # at least 4 categories per leg + slippage = many reasons
    assert len(res.reasons) >= 5
