"""Tests for multi-leg atomic execution.

Tests cover:
- Rust types: FillPolicy, LegStatus, Leg, LegResult, MultiLegResult
- Rust functions: validate_legs, aggregate_leg_risk, build_mock_result
- Leg creation, notional calculation, validation edge cases
- Python pipeline function: multi_leg
"""

import pytest


# ---------------------------------------------------------------------------
# 1. Leg
# ---------------------------------------------------------------------------


class TestLeg:
    def test_creation(self):
        from horizon._horizon import Leg

        leg = Leg(market_id="M1", side="buy", size=100.0, price=0.50)
        assert leg.market_id == "M1"
        assert leg.side == "buy"
        assert leg.size == pytest.approx(100.0, abs=1e-10)
        assert leg.price == pytest.approx(0.50, abs=1e-10)
        assert leg.exchange == "paper"  # default

    def test_notional(self):
        from horizon._horizon import Leg

        leg = Leg(market_id="M1", side="buy", size=100.0, price=0.50)
        assert leg.notional() == pytest.approx(50.0, abs=1e-10)

    def test_custom_exchange(self):
        from horizon._horizon import Leg

        leg = Leg(market_id="M1", side="sell", size=50.0, price=0.30, exchange="kalshi")
        assert leg.exchange == "kalshi"

    def test_repr(self):
        from horizon._horizon import Leg

        leg = Leg(market_id="M1", side="buy", size=10.0, price=0.50)
        r = repr(leg)
        assert "M1" in r
        assert "buy" in r


# ---------------------------------------------------------------------------
# 2. validate_legs
# ---------------------------------------------------------------------------


class TestValidateLegs:
    def test_valid(self):
        from horizon._horizon import Leg, validate_legs

        legs = [
            Leg(market_id="M1", side="buy", size=10.0, price=0.5),
            Leg(market_id="M2", side="sell", size=20.0, price=0.3),
        ]
        error = validate_legs(legs)
        assert error == ""

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

        error = validate_legs([])
        assert "no legs" in error

    def test_invalid_size(self):
        from horizon._horizon import Leg, validate_legs

        legs = [Leg(market_id="M1", side="buy", size=0.0, price=0.5)]
        error = validate_legs(legs)
        assert "invalid size" in error

    def test_negative_size(self):
        from horizon._horizon import Leg, validate_legs

        legs = [Leg(market_id="M1", side="buy", size=-5.0, price=0.5)]
        error = validate_legs(legs)
        assert "invalid size" in error

    def test_invalid_price_zero(self):
        from horizon._horizon import Leg, validate_legs

        legs = [Leg(market_id="M1", side="buy", size=10.0, price=0.0)]
        error = validate_legs(legs)
        assert "invalid price" in error

    def test_invalid_price_one(self):
        from horizon._horizon import Leg, validate_legs

        legs = [Leg(market_id="M1", side="buy", size=10.0, price=1.0)]
        error = validate_legs(legs)
        assert "invalid price" in error

    def test_empty_market_id(self):
        from horizon._horizon import Leg, validate_legs

        legs = [Leg(market_id="", side="buy", size=10.0, price=0.5)]
        error = validate_legs(legs)
        assert "empty market_id" in error


# ---------------------------------------------------------------------------
# 3. aggregate_leg_risk
# ---------------------------------------------------------------------------


class TestAggregateRisk:
    def test_basic(self):
        from horizon._horizon import Leg, aggregate_leg_risk

        legs = [
            Leg(market_id="M1", side="buy", size=100.0, price=0.5, exchange="poly"),
            Leg(market_id="M2", side="sell", size=200.0, price=0.3, exchange="kalshi"),
        ]
        total, max_leg, n_exch = aggregate_leg_risk(legs)
        assert total == pytest.approx(110.0, abs=1e-10)  # 50 + 60
        assert max_leg == pytest.approx(60.0, abs=1e-10)
        assert n_exch == 2

    def test_multi_exchange(self):
        from horizon._horizon import Leg, aggregate_leg_risk

        legs = [
            Leg(market_id="M1", side="buy", size=10.0, price=0.5, exchange="poly"),
            Leg(market_id="M2", side="buy", size=10.0, price=0.5, exchange="kalshi"),
            Leg(market_id="M3", side="buy", size=10.0, price=0.5, exchange="paper"),
        ]
        _, _, n_exch = aggregate_leg_risk(legs)
        assert n_exch == 3

    def test_single_exchange(self):
        from horizon._horizon import Leg, aggregate_leg_risk

        legs = [
            Leg(market_id="M1", side="buy", size=10.0, price=0.5, exchange="paper"),
            Leg(market_id="M2", side="buy", size=20.0, price=0.5, exchange="paper"),
        ]
        _, _, n_exch = aggregate_leg_risk(legs)
        assert n_exch == 1

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

        total, max_leg, n_exch = aggregate_leg_risk([])
        assert total == pytest.approx(0.0, abs=1e-10)
        assert max_leg == pytest.approx(0.0, abs=1e-10)
        assert n_exch == 0


# ---------------------------------------------------------------------------
# 4. build_mock_result
# ---------------------------------------------------------------------------


class TestBuildMockResult:
    def test_success(self):
        from horizon._horizon import Leg, FillPolicy, LegStatus, build_mock_result

        legs = [
            Leg(market_id="M1", side="buy", size=10.0, price=0.5),
            Leg(market_id="M2", side="sell", size=20.0, price=0.3),
        ]
        result = build_mock_result(legs, FillPolicy.AllOrNone, True)
        assert result.success
        assert result.total_filled == 2
        assert result.total_legs == 2
        assert result.total_notional == pytest.approx(11.0, abs=1e-10)
        # Check individual legs
        assert len(result.legs) == 2
        assert result.legs[0].status == LegStatus.Filled
        assert result.legs[0].filled_size == pytest.approx(10.0, abs=1e-10)
        assert result.legs[0].order_id.startswith("mock_")

    def test_failure(self):
        from horizon._horizon import Leg, FillPolicy, LegStatus, build_mock_result

        legs = [
            Leg(market_id="M1", side="buy", size=10.0, price=0.5),
        ]
        result = build_mock_result(legs, FillPolicy.BestEffort, False)
        assert not result.success
        assert result.total_filled == 0
        assert result.legs[0].status == LegStatus.Failed
        assert result.legs[0].filled_size == pytest.approx(0.0, abs=1e-10)


# ---------------------------------------------------------------------------
# 5. fill_rate
# ---------------------------------------------------------------------------


class TestFillRate:
    def test_full(self):
        from horizon._horizon import Leg, FillPolicy, build_mock_result

        legs = [
            Leg(market_id="M1", side="buy", size=10.0, price=0.5),
            Leg(market_id="M2", side="sell", size=20.0, price=0.3),
        ]
        result = build_mock_result(legs, FillPolicy.AllOrNone, True)
        assert result.fill_rate() == pytest.approx(1.0, abs=1e-10)

    def test_partial(self):
        from horizon._horizon import Leg, FillPolicy, build_mock_result

        legs = [Leg(market_id="M1", side="buy", size=10.0, price=0.5)]
        result = build_mock_result(legs, FillPolicy.BestEffort, False)
        assert result.fill_rate() == pytest.approx(0.0, abs=1e-10)

    def test_empty_legs(self):
        from horizon._horizon import FillPolicy, build_mock_result

        result = build_mock_result([], FillPolicy.BestEffort, True)
        assert result.fill_rate() == pytest.approx(0.0, abs=1e-10)


# ---------------------------------------------------------------------------
# 6. Pipeline function (Ultra tier)
# ---------------------------------------------------------------------------


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

        fn = multi_leg(
            legs_config=[
                {"market_id": "M1", "side": "buy", "size": 10.0, "price": 0.5},
                {"market_id": "M2", "side": "sell", "size": 20.0, "price": 0.3},
            ],
            policy="best_effort",
        )
        assert callable(fn)
        assert fn.__name__ == "multi_leg"
