"""Tests for options/derivatives overlay.

Tests cover:
- Rust functions: build_options_chain, vol_surface, term_structure,
  synthetic_butterfly, synthetic_straddle, calendar_spread_legs
- Rust types: SyntheticOption, VolSurfacePoint, TermStructurePoint, SpreadStrategy
- Python pipeline functions: options_chain, delta_hedge_continuous, vol_surface_monitor
"""

import math
import pytest


# ---------------------------------------------------------------------------
# 1. build_options_chain
# ---------------------------------------------------------------------------


class TestBuildOptionsChain:
    def test_basic(self):
        from horizon._horizon import build_options_chain

        chain = build_options_chain(
            ["M1", "M2"], [0.6, 0.3], [30.0, 60.0]
        )
        assert len(chain) == 2
        assert chain[0].market_id == "M1"
        assert chain[0].implied_vol > 0.0
        assert chain[0].delta > 0.0
        assert chain[0].price == pytest.approx(0.6, abs=1e-6)
        assert chain[0].expiry_days == pytest.approx(30.0, abs=1e-6)
        assert math.isfinite(chain[0].gamma)
        assert math.isfinite(chain[0].theta)
        assert math.isfinite(chain[0].vega)

    def test_edge_prices(self):
        from horizon._horizon import build_options_chain

        # Extreme price should be clamped and produce finite values
        chain = build_options_chain(["M1"], [0.001], [30.0])
        assert len(chain) == 1
        assert math.isfinite(chain[0].implied_vol)
        assert math.isfinite(chain[0].delta)

        chain_high = build_options_chain(["M1"], [0.999], [30.0])
        assert len(chain_high) == 1
        assert math.isfinite(chain_high[0].implied_vol)

    def test_empty(self):
        from horizon._horizon import build_options_chain

        chain = build_options_chain([], [], [])
        assert len(chain) == 0

    def test_atm(self):
        from horizon._horizon import build_options_chain

        # ATM (price=0.5) should have lower IV
        chain = build_options_chain(["M1"], [0.5], [30.0])
        assert len(chain) == 1
        assert chain[0].implied_vol >= 0.0

    def test_multiple_expiries(self):
        from horizon._horizon import build_options_chain

        chain = build_options_chain(
            ["M1", "M2", "M3"],
            [0.5, 0.5, 0.5],
            [7.0, 30.0, 90.0],
        )
        assert len(chain) == 3
        for opt in chain:
            assert math.isfinite(opt.implied_vol)
            assert math.isfinite(opt.delta)


# ---------------------------------------------------------------------------
# 2. vol_surface
# ---------------------------------------------------------------------------


class TestVolSurface:
    def test_basic(self):
        from horizon._horizon import vol_surface

        surface = vol_surface(
            [0.3, 0.5, 0.7],
            [30.0, 30.0, 30.0],
            [0.3, 0.5, 0.7],
        )
        assert len(surface) == 3
        for point in surface:
            assert math.isfinite(point.implied_vol)
            assert point.implied_vol >= 0.0

    def test_sorted(self):
        from horizon._horizon import vol_surface

        surface = vol_surface(
            [0.3, 0.5, 0.7],
            [30.0, 30.0, 30.0],
            [0.3, 0.5, 0.7],
        )
        # vol_surface does NOT sort (term_structure does)
        assert surface[0].strike == pytest.approx(0.3, abs=1e-6)
        assert surface[1].strike == pytest.approx(0.5, abs=1e-6)

    def test_empty(self):
        from horizon._horizon import vol_surface

        surface = vol_surface([], [], [])
        assert len(surface) == 0

    def test_single_point(self):
        from horizon._horizon import vol_surface

        surface = vol_surface([0.5], [30.0], [0.5])
        assert len(surface) == 1


# ---------------------------------------------------------------------------
# 3. term_structure
# ---------------------------------------------------------------------------


class TestTermStructure:
    def test_basic(self):
        from horizon._horizon import term_structure

        ts = term_structure([30.0, 60.0, 90.0], [0.5, 0.5, 0.5])
        assert len(ts) == 3
        for point in ts:
            assert math.isfinite(point.implied_vol)
            assert point.price == pytest.approx(0.5, abs=1e-6)

    def test_sorted_by_expiry(self):
        from horizon._horizon import term_structure

        # Input in reverse order
        ts = term_structure([90.0, 30.0, 60.0], [0.5, 0.5, 0.5])
        assert ts[0].expiry_days <= ts[1].expiry_days
        assert ts[1].expiry_days <= ts[2].expiry_days

    def test_empty(self):
        from horizon._horizon import term_structure

        ts = term_structure([], [])
        assert len(ts) == 0

    def test_single_expiry(self):
        from horizon._horizon import term_structure

        ts = term_structure([30.0], [0.6])
        assert len(ts) == 1
        assert ts[0].price == pytest.approx(0.6, abs=1e-6)


# ---------------------------------------------------------------------------
# 4. Spread strategies
# ---------------------------------------------------------------------------


class TestSpreads:
    def test_butterfly(self):
        from horizon._horizon import synthetic_butterfly

        legs = synthetic_butterfly("A", "B", "C", 10.0)
        assert len(legs) == 3
        assert legs[0] == ("A", "buy", 10.0)
        assert legs[1] == ("B", "sell", 20.0)
        assert legs[2] == ("C", "buy", 10.0)

    def test_straddle(self):
        from horizon._horizon import synthetic_straddle

        legs = synthetic_straddle("M1", 5.0)
        assert len(legs) == 2
        assert legs[0][0] == "M1"
        assert legs[0][1] == "buy_yes"
        assert legs[0][2] == pytest.approx(5.0, abs=1e-10)
        assert legs[1][0] == "M1"
        assert legs[1][1] == "buy_no"
        assert legs[1][2] == pytest.approx(5.0, abs=1e-10)

    def test_calendar(self):
        from horizon._horizon import calendar_spread_legs

        legs = calendar_spread_legs("NEAR", "FAR", 10.0)
        assert len(legs) == 2
        assert legs[0] == ("NEAR", "sell", 10.0)
        assert legs[1] == ("FAR", "buy", 10.0)

    def test_butterfly_zero_size(self):
        from horizon._horizon import synthetic_butterfly

        legs = synthetic_butterfly("A", "B", "C", 0.0)
        assert legs[0][2] == pytest.approx(0.0, abs=1e-10)
        assert legs[1][2] == pytest.approx(0.0, abs=1e-10)


# ---------------------------------------------------------------------------
# 5. Pipeline functions (Ultra tier)
# ---------------------------------------------------------------------------


class TestPipeline:
    def test_options_chain(self):
        try:
            from horizon.options_overlay import options_chain
        except PermissionError:
            pytest.skip("Ultra tier required")

        fn = options_chain(market_feeds={"m1": "f1", "m2": "f2"})
        assert callable(fn)
        assert fn.__name__ == "options_chain"

    def test_delta_hedge_continuous(self):
        try:
            from horizon.options_overlay import delta_hedge_continuous
        except PermissionError:
            pytest.skip("Ultra tier required")

        fn = delta_hedge_continuous(
            market_feeds={"m1": "f1"},
            hedge_threshold=0.1,
        )
        assert callable(fn)
        assert fn.__name__ == "delta_hedge_continuous"

    def test_vol_surface_monitor(self):
        try:
            from horizon.options_overlay import vol_surface_monitor
        except PermissionError:
            pytest.skip("Ultra tier required")

        fn = vol_surface_monitor(
            market_feeds={"m1": "f1", "m2": "f2"},
            default_expiry=30.0,
        )
        assert callable(fn)
        assert fn.__name__ == "vol_surface_monitor"
