"""Tests for cross-platform price aggregation."""

import pytest
from unittest.mock import MagicMock

from horizon.aggregator import AggregatedPrice, PriceAggregator


def _mock_engine(snapshots: dict):
    """Create a mock engine with feed_snapshot returning given snapshots."""
    engine = MagicMock()

    def feed_snapshot(name):
        return snapshots.get(name)

    engine.feed_snapshot = feed_snapshot
    return engine


def _mock_snapshot(price=0.0, bid=0.0, ask=0.0):
    snap = MagicMock()
    snap.price = price
    snap.bid = bid
    snap.ask = ask
    return snap


class TestPriceAggregator:
    def test_aggregate_no_mapping(self):
        agg = PriceAggregator()
        engine = _mock_engine({})
        assert agg.aggregate(engine, "unknown") is None

    def test_aggregate_no_feed_data(self):
        agg = PriceAggregator()
        agg.add_mapping("mkt1", [("feed_a", "exch_a")])
        engine = _mock_engine({})
        assert agg.aggregate(engine, "mkt1") is None

    def test_aggregate_single_exchange(self):
        agg = PriceAggregator()
        agg.add_mapping("mkt1", [("feed_a", "polymarket")])
        snap = _mock_snapshot(price=0.55, bid=0.54, ask=0.56)
        engine = _mock_engine({"feed_a": snap})

        result = agg.aggregate(engine, "mkt1")
        assert result is not None
        assert result.best_bid == 0.54
        assert result.best_ask == 0.56
        assert result.best_bid_exchange == "polymarket"
        assert result.best_ask_exchange == "polymarket"
        assert result.num_exchanges == 1
        assert result.divergence == 0.0

    def test_aggregate_two_exchanges(self):
        agg = PriceAggregator()
        agg.add_mapping("mkt1", [
            ("feed_poly", "polymarket"),
            ("feed_kalshi", "kalshi"),
        ])
        snap_poly = _mock_snapshot(price=0.55, bid=0.54, ask=0.56)
        snap_kalshi = _mock_snapshot(price=0.57, bid=0.56, ask=0.58)
        engine = _mock_engine({
            "feed_poly": snap_poly,
            "feed_kalshi": snap_kalshi,
        })

        result = agg.aggregate(engine, "mkt1")
        assert result is not None
        # Best bid = highest bid across exchanges
        assert result.best_bid == 0.56
        assert result.best_bid_exchange == "kalshi"
        # Best ask = lowest ask across exchanges
        assert result.best_ask == 0.56
        assert result.best_ask_exchange == "polymarket"
        assert result.num_exchanges == 2
        assert result.divergence > 0

    def test_aggregate_composite_mid(self):
        agg = PriceAggregator()
        agg.add_mapping("mkt1", [
            ("feed_a", "exch_a"),
            ("feed_b", "exch_b"),
        ])
        snap_a = _mock_snapshot(price=0.50, bid=0.49, ask=0.51)
        snap_b = _mock_snapshot(price=0.60, bid=0.59, ask=0.61)
        engine = _mock_engine({"feed_a": snap_a, "feed_b": snap_b})

        result = agg.aggregate(engine, "mkt1")
        assert result is not None
        # Composite mid = avg of mids: (0.50 + 0.60) / 2 = 0.55
        assert abs(result.composite_mid - 0.55) < 1e-10

    def test_aggregate_frozen_dataclass(self):
        ap = AggregatedPrice(
            market_id="test",
            best_bid=0.5,
            best_ask=0.6,
            best_bid_exchange="a",
            best_ask_exchange="b",
            composite_mid=0.55,
            divergence=0.01,
            num_exchanges=2,
        )
        assert ap.market_id == "test"
        with pytest.raises(Exception):
            ap.market_id = "other"  # frozen


class TestDivergenceAlert:
    def test_no_divergence(self):
        agg = PriceAggregator()
        agg.add_mapping("mkt1", [("feed_a", "exch_a")])
        snap = _mock_snapshot(price=0.55, bid=0.54, ask=0.56)
        engine = _mock_engine({"feed_a": snap})
        assert agg.divergence_alert(engine, "mkt1", threshold=0.02) is False

    def test_high_divergence(self):
        agg = PriceAggregator()
        agg.add_mapping("mkt1", [
            ("feed_a", "exch_a"),
            ("feed_b", "exch_b"),
        ])
        snap_a = _mock_snapshot(price=0.50, bid=0.49, ask=0.51)
        snap_b = _mock_snapshot(price=0.60, bid=0.59, ask=0.61)
        engine = _mock_engine({"feed_a": snap_a, "feed_b": snap_b})
        assert agg.divergence_alert(engine, "mkt1", threshold=0.02) is True

    def test_unknown_market(self):
        agg = PriceAggregator()
        engine = _mock_engine({})
        assert agg.divergence_alert(engine, "unknown") is False
