"""Tests for cross-venue liquidity aggregation.

Tests cover:
- Rust functions: aggregate_books, smart_route, best_venue, merged_mid_price
- Rust types: AggregatedBook, AggregatedLevel, SmartRouteDecision, VenueMapping
- Python pipeline functions: aggregate_book, smart_route_pipeline, cross_venue_mm
"""

import math
import pytest


# ---------------------------------------------------------------------------
# 1. aggregate_books
# ---------------------------------------------------------------------------


class TestAggregateBooks:
    def test_basic(self):
        from horizon._horizon import aggregate_books

        bids = [("poly", 0.50, 100.0), ("kalshi", 0.48, 200.0)]
        asks = [("poly", 0.52, 150.0), ("kalshi", 0.55, 100.0)]
        book = aggregate_books(bids, asks)
        assert book.best_bid == pytest.approx(0.50, abs=1e-10)
        assert book.best_ask == pytest.approx(0.52, abs=1e-10)
        assert book.mid_price == pytest.approx(0.51, abs=1e-10)
        assert book.total_bid_size == pytest.approx(300.0, abs=1e-10)
        assert book.total_ask_size == pytest.approx(250.0, abs=1e-10)
        # Bids sorted descending
        assert book.bids[0].price >= book.bids[1].price
        # Asks sorted ascending
        assert book.asks[0].price <= book.asks[1].price

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

        book = aggregate_books([], [])
        assert book.best_bid == 0.0
        assert book.best_ask == 0.0
        assert book.mid_price == 0.0
        assert len(book.bids) == 0
        assert len(book.asks) == 0

    def test_invalid_filtered(self):
        from horizon._horizon import aggregate_books

        book = aggregate_books(
            [("A", -0.5, 100.0), ("B", 0.50, 0.0)],
            [("A", float("nan"), 100.0)],
        )
        assert len(book.bids) == 0
        assert len(book.asks) == 0

    def test_single_venue(self):
        from horizon._horizon import aggregate_books

        book = aggregate_books(
            [("poly", 0.50, 100.0)],
            [("poly", 0.52, 100.0)],
        )
        assert book.best_bid == pytest.approx(0.50, abs=1e-10)
        assert book.best_ask == pytest.approx(0.52, abs=1e-10)

    def test_spread(self):
        from horizon._horizon import aggregate_books

        book = aggregate_books(
            [("A", 0.48, 100.0)],
            [("A", 0.52, 100.0)],
        )
        assert book.spread() == pytest.approx(0.04, abs=1e-10)


# ---------------------------------------------------------------------------
# 2. smart_route
# ---------------------------------------------------------------------------


class TestSmartRoute:
    def test_buy(self):
        from horizon._horizon import aggregate_books, smart_route

        book = aggregate_books(
            [("A", 0.50, 100.0)],
            [("A", 0.52, 50.0), ("B", 0.53, 100.0)],
        )
        decision = smart_route(book, "buy", 80.0)
        assert len(decision.fills_across_venues) == 2
        assert decision.size == pytest.approx(80.0, abs=1e-10)
        # Weighted avg price between 0.52 and 0.53
        assert decision.price > 0.52
        assert decision.price < 0.53
        assert decision.expected_cost > 0.0

    def test_sell(self):
        from horizon._horizon import aggregate_books, smart_route

        book = aggregate_books(
            [("A", 0.50, 50.0), ("B", 0.48, 100.0)],
            [("A", 0.52, 100.0)],
        )
        decision = smart_route(book, "sell", 30.0)
        assert decision.venue == "A"
        assert decision.price == pytest.approx(0.50, abs=1e-10)
        assert decision.size == pytest.approx(30.0, abs=1e-10)

    def test_zero_size(self):
        from horizon._horizon import aggregate_books, smart_route

        book = aggregate_books([], [])
        decision = smart_route(book, "buy", 0.0)
        assert decision.size == pytest.approx(0.0, abs=1e-10)
        assert decision.expected_cost == pytest.approx(0.0, abs=1e-10)
        assert len(decision.fills_across_venues) == 0

    def test_insufficient_liquidity(self):
        from horizon._horizon import aggregate_books, smart_route

        book = aggregate_books(
            [],
            [("A", 0.52, 10.0)],
        )
        decision = smart_route(book, "buy", 100.0)
        # Can only fill 10
        assert decision.size == pytest.approx(10.0, abs=1e-10)


# ---------------------------------------------------------------------------
# 3. best_venue
# ---------------------------------------------------------------------------


class TestBestVenue:
    def test_buy(self):
        from horizon._horizon import aggregate_books, best_venue

        book = aggregate_books(
            [],
            [("poly", 0.52, 100.0), ("kalshi", 0.50, 50.0)],
        )
        venue, price = best_venue(book, "buy")
        assert venue == "kalshi"
        assert price == pytest.approx(0.50, abs=1e-10)

    def test_sell(self):
        from horizon._horizon import aggregate_books, best_venue

        book = aggregate_books(
            [("poly", 0.50, 100.0), ("kalshi", 0.52, 50.0)],
            [],
        )
        venue, price = best_venue(book, "sell")
        assert venue == "kalshi"
        assert price == pytest.approx(0.52, abs=1e-10)

    def test_empty_book(self):
        from horizon._horizon import aggregate_books, best_venue

        book = aggregate_books([], [])
        venue, price = best_venue(book, "buy")
        assert venue == ""
        assert price == 0.0


# ---------------------------------------------------------------------------
# 4. merged_mid_price
# ---------------------------------------------------------------------------


class TestMergedMidPrice:
    def test_basic(self):
        from horizon._horizon import merged_mid_price

        mid = merged_mid_price(
            [("A", 0.50, 100.0)],
            [("A", 0.52, 100.0)],
        )
        assert mid == pytest.approx(0.51, abs=1e-10)

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

        mid = merged_mid_price([], [])
        assert mid == 0.0

    def test_multiple_venues(self):
        from horizon._horizon import merged_mid_price

        mid = merged_mid_price(
            [("A", 0.50, 100.0), ("B", 0.48, 200.0)],
            [("A", 0.52, 100.0), ("B", 0.55, 100.0)],
        )
        # Best bid = 0.50, best ask = 0.52
        assert mid == pytest.approx(0.51, abs=1e-10)


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


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

        fn = aggregate_book(venue_feeds={
            "poly": {"bid_feed": "poly_book"},
            "kalshi": {"bid_feed": "kalshi_book"},
        })
        assert callable(fn)
        assert fn.__name__ == "aggregate_book"

    def test_smart_route_pipeline(self):
        try:
            from horizon.liquidity_agg import smart_route_pipeline
        except PermissionError:
            pytest.skip("Ultra tier required")

        fn = smart_route_pipeline(
            venue_feeds={"poly": {"bid_feed": "poly_book"}},
            default_size=100.0,
        )
        assert callable(fn)
        assert fn.__name__ == "smart_route"

    def test_cross_venue_mm(self):
        try:
            from horizon.liquidity_agg import cross_venue_mm
        except PermissionError:
            pytest.skip("Ultra tier required")

        fn = cross_venue_mm(
            venue_feeds={"poly": {"bid_feed": "poly_book"}},
            spread_markup=0.01,
        )
        assert callable(fn)
        assert fn.__name__ == "cross_venue_mm"
