"""Tests for cross-market hedging modifier."""

import os

os.environ.setdefault("HORIZON_API_KEY", "test-key-123")

from horizon._horizon import Market, Quote, Side, Event, Outcome
from horizon.context import Context, FeedData, InventorySnapshot
from horizon.hedger import cross_hedger


def _mock_position(market_id, side, size):
    class MockPosition:
        def __init__(self, market_id, side, size):
            self.market_id = market_id
            self.side = side
            self.size = size
    return MockPosition(market_id, side, size)


def _make_ctx(market_id="mkt1", inventory=0.0, bid=0.45, ask=0.55, event=None):
    market = Market(id=market_id, name=market_id, slug=market_id)
    positions = []
    if inventory > 0:
        positions = [_mock_position(market_id, Side.Yes, abs(inventory))]
    elif inventory < 0:
        positions = [_mock_position(market_id, Side.No, abs(inventory))]
    return Context(
        feeds={"feed": FeedData(price=0.5, bid=bid, ask=ask, timestamp=1.0)},
        inventory=InventorySnapshot(positions=positions),
        market=market,
        event=event,
        params={},
    )


class TestCrossHedger:
    def test_no_hedge_below_threshold(self):
        hedger = cross_hedger(max_delta=50.0)
        ctx = _make_ctx(inventory=10.0)
        quotes = [Quote(bid=0.45, ask=0.55, size=5.0)]
        result = hedger(ctx, quotes)
        # 10.0 < 50.0 threshold → no hedge quote added
        assert len(result) == 1

    def test_hedge_triggered_above_threshold(self):
        hedger = cross_hedger(max_delta=10.0, hedge_size=5.0)
        # Build up position cache
        ctx1 = _make_ctx(market_id="mkt1", inventory=0.0)
        hedger(ctx1, [])

        # Now check a market with large position
        ctx2 = _make_ctx(market_id="mkt2", inventory=20.0)
        quotes = [Quote(bid=0.45, ask=0.55, size=5.0)]
        result = hedger(ctx2, quotes)
        # 20.0 > 10.0 → hedge quote appended
        assert len(result) == 2

    def test_static_correlation(self):
        hedger = cross_hedger(
            correlations={("mkt1", "mkt2"): -1.0},
            max_delta=5.0,
            hedge_size=5.0,
        )
        # Market 1: long 30
        ctx1 = _make_ctx(market_id="mkt1", inventory=30.0)
        hedger(ctx1, [])

        # Market 2: correlated -1.0 to mkt1
        # portfolio_delta = own(0) + mkt1(30)*(-1.0) = -30
        # excess = 30 - 5 = 25 → hedge
        ctx2 = _make_ctx(market_id="mkt2", inventory=0.0)
        quotes = [Quote(bid=0.45, ask=0.55, size=5.0)]
        result = hedger(ctx2, quotes)
        assert len(result) == 2  # Original + hedge

    def test_reversed_correlation_key(self):
        hedger = cross_hedger(
            correlations={("mkt2", "mkt1"): -0.5},
            max_delta=5.0,
        )
        # Should find correlation via reversed key lookup
        ctx1 = _make_ctx(market_id="mkt1", inventory=50.0)
        hedger(ctx1, [])
        ctx2 = _make_ctx(market_id="mkt2", inventory=0.0)
        result = hedger(ctx2, [Quote(bid=0.45, ask=0.55, size=5.0)])
        # portfolio_delta = 0 + 50*(-0.5) = -25, excess = 20 → hedge
        assert len(result) == 2

    def test_event_correlation(self):
        hedger = cross_hedger(max_delta=5.0, event_correlated=True)
        event = Event(
            id="test_event",
            name="Test Event",
            outcomes=[
                Outcome(name="Yes", market_id="mkt1"),
                Outcome(name="No", market_id="mkt2"),
            ],
        )
        # mkt1: long 30
        ctx1 = _make_ctx(market_id="mkt1", inventory=30.0, event=event)
        hedger(ctx1, [])

        # mkt2: same event → corr=-1.0
        # delta = 0 + 30*(-1) = -30, excess = 25
        ctx2 = _make_ctx(market_id="mkt2", inventory=0.0, event=event)
        result = hedger(ctx2, [Quote(bid=0.45, ask=0.55, size=5.0)])
        assert len(result) == 2

    def test_event_correlation_disabled(self):
        hedger = cross_hedger(max_delta=5.0, event_correlated=False)
        event = Event(
            id="test_event",
            name="Test Event",
            outcomes=[
                Outcome(name="Yes", market_id="mkt1"),
                Outcome(name="No", market_id="mkt2"),
            ],
        )
        ctx1 = _make_ctx(market_id="mkt1", inventory=30.0, event=event)
        hedger(ctx1, [])
        # Without event_correlated, corr=0 → delta = 0 + 30*0 = 0 for mkt2
        ctx2 = _make_ctx(market_id="mkt2", inventory=0.0, event=event)
        result = hedger(ctx2, [Quote(bid=0.45, ask=0.55, size=5.0)])
        assert len(result) == 1  # No hedge

    def test_hedge_size_limited(self):
        hedger = cross_hedger(max_delta=5.0, hedge_size=3.0)
        ctx = _make_ctx(inventory=100.0)
        result = hedger(ctx, [Quote(bid=0.45, ask=0.55, size=5.0)])
        # Hedge size should be min(3.0, excess)
        hedge_quote = result[-1]
        assert hedge_quote.size <= 3.0

    def test_no_quotes_input(self):
        hedger = cross_hedger(max_delta=5.0)
        ctx = _make_ctx(inventory=20.0)
        result = hedger(ctx, None)
        # Should still generate hedge quote
        assert len(result) >= 0  # May or may not hedge based on delta

    def test_empty_quotes_input(self):
        hedger = cross_hedger(max_delta=5.0)
        ctx = _make_ctx(inventory=20.0)
        result = hedger(ctx, [])
        # Hedge quote should still be generated if delta exceeds threshold
        assert len(result) >= 0

    def test_prediction_market_clamping(self):
        hedger = cross_hedger(max_delta=5.0, hedge_spread=0.02)
        ctx = _make_ctx(inventory=100.0, bid=0.98, ask=0.99)
        result = hedger(ctx, [Quote(bid=0.45, ask=0.55, size=5.0)])
        for q in result:
            assert q.bid >= 0.01
            assert q.ask <= 0.99
            assert q.bid < q.ask

    def test_no_market_returns_quotes(self):
        hedger = cross_hedger()
        ctx = Context(
            feeds={"feed": FeedData(price=0.5, bid=0.45, ask=0.55, timestamp=1.0)},
            inventory=InventorySnapshot(),
            market=None,
            params={},
        )
        quotes = [Quote(bid=0.45, ask=0.55, size=5.0)]
        result = hedger(ctx, quotes)
        assert result == quotes

    def test_no_feed_data(self):
        hedger = cross_hedger(max_delta=5.0)
        market = Market(id="mkt1", name="mkt1", slug="mkt1")
        ctx = Context(
            feeds={},
            inventory=InventorySnapshot(
                positions=[_mock_position("mkt1", Side.Yes, 100.0)]
            ),
            market=market,
            params={},
        )
        result = hedger(ctx, [Quote(bid=0.45, ask=0.55, size=5.0)])
        # No mid price → no hedge quote, return original
        assert len(result) == 1

    def test_function_name(self):
        hedger = cross_hedger()
        assert hedger.__name__ == "cross_hedger"

    def test_position_cache_updates(self):
        hedger = cross_hedger(max_delta=5.0, hedge_size=5.0)
        # First cycle: build cache
        ctx1 = _make_ctx(market_id="mkt1", inventory=20.0)
        hedger(ctx1, [])
        # Second cycle: same market, updated inventory
        ctx2 = _make_ctx(market_id="mkt1", inventory=0.0)
        hedger(ctx2, [])
        # Third market should see updated cache
        ctx3 = _make_ctx(market_id="mkt2", inventory=0.0)
        result = hedger(ctx3, [Quote(bid=0.45, ask=0.55, size=5.0)])
        # mkt1 cache was updated to 0.0, so delta for mkt2 = 0, no hedge
        assert len(result) == 1
