"""Tests for execution quality tracker."""

import os

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

from horizon._horizon import Market, OrderSide
from horizon.context import Context, FeedData, InventorySnapshot
from horizon.execution_tracker import execution_tracker, ExecQuality


class MockFill:
    """Lightweight fill mock for testing."""

    def __init__(self, fill_id, market_id, price, order_side=OrderSide.Buy):
        self.fill_id = fill_id
        self.market_id = market_id
        self.price = price
        self.order_side = order_side


def _make_ctx(market_id="mkt1", bid=0.45, ask=0.55, fills=None, params=None):
    market = Market(id=market_id, name=market_id, slug=market_id)
    p = params if params is not None else {}
    if fills is not None:
        p["_recent_fills"] = fills
    return Context(
        feeds={"feed": FeedData(price=0.5, bid=bid, ask=ask, timestamp=1.0)},
        inventory=InventorySnapshot(),
        market=market,
        params=p,
    )


class TestExecQuality:
    def test_defaults(self):
        eq = ExecQuality()
        assert eq.fill_rate == 0.0
        assert eq.avg_slippage == 0.0
        assert eq.total_fills == 0
        assert eq.total_quotes == 0


class TestExecutionTracker:
    def test_no_fills_zero_rate(self):
        tracker = execution_tracker()
        for _ in range(10):
            ctx = _make_ctx()
            tracker(ctx)
        eq = ctx.params["exec_quality"]
        assert isinstance(eq, ExecQuality)
        assert eq.fill_rate == 0.0
        assert eq.total_quotes == 10

    def test_fill_detected(self):
        tracker = execution_tracker()
        fill = MockFill("f1", "mkt1", 0.50)
        ctx = _make_ctx(fills=[fill])
        tracker(ctx)
        eq = ctx.params["exec_quality"]
        assert eq.total_fills == 1
        assert eq.fill_rate > 0.0

    def test_fill_dedup(self):
        tracker = execution_tracker()
        fill = MockFill("f1", "mkt1", 0.50)
        # Same fill twice
        ctx = _make_ctx(fills=[fill])
        tracker(ctx)
        ctx = _make_ctx(fills=[fill])
        tracker(ctx)
        eq = ctx.params["exec_quality"]
        assert eq.total_fills == 1  # Not double-counted

    def test_fill_rate_calculation(self):
        tracker = execution_tracker(window=10)
        # 5 cycles with fills, 5 without
        for i in range(10):
            if i < 5:
                fills = [MockFill(f"f{i}", "mkt1", 0.50)]
            else:
                fills = []
            ctx = _make_ctx(fills=fills)
            tracker(ctx)
        eq = ctx.params["exec_quality"]
        assert abs(eq.fill_rate - 0.5) < 1e-6

    def test_slippage_calculation(self):
        tracker = execution_tracker()
        # First cycle establishes mid
        ctx = _make_ctx(bid=0.45, ask=0.55)
        tracker(ctx)
        # Fill at 0.52, mid was 0.50 → slippage 0.02
        fill = MockFill("f1", "mkt1", 0.52)
        ctx = _make_ctx(bid=0.45, ask=0.55, fills=[fill])
        tracker(ctx)
        eq = ctx.params["exec_quality"]
        assert eq.avg_slippage > 0

    def test_wrong_market_fills_ignored(self):
        tracker = execution_tracker()
        fill = MockFill("f1", "other_mkt", 0.50)
        ctx = _make_ctx(market_id="mkt1", fills=[fill])
        tracker(ctx)
        eq = ctx.params["exec_quality"]
        assert eq.total_fills == 0

    def test_per_market_isolation(self):
        tracker = execution_tracker()
        fill_a = MockFill("f1", "mkt_a", 0.50)
        fill_b = MockFill("f2", "mkt_b", 0.50)

        ctx_a = _make_ctx(market_id="mkt_a", fills=[fill_a])
        tracker(ctx_a)
        ctx_b = _make_ctx(market_id="mkt_b", fills=[fill_b])
        tracker(ctx_b)

        eq_a = ctx_a.params["exec_quality"]
        eq_b = ctx_b.params["exec_quality"]
        assert eq_a.total_fills == 1
        assert eq_b.total_fills == 1

    def test_adverse_selection_buy(self):
        tracker = execution_tracker()
        # Mid at 0.50, buy fill at 0.50
        ctx = _make_ctx(bid=0.45, ask=0.55)
        tracker(ctx)  # Establish mid
        fill = MockFill("f1", "mkt1", 0.50, OrderSide.Buy)
        # Mid moved down to 0.48 (adverse for buyer)
        ctx = _make_ctx(bid=0.43, ask=0.53, fills=[fill])
        tracker(ctx)
        eq = ctx.params["exec_quality"]
        # adverse = mid(0.48) - fill_price(0.50) = -0.02 (negative = adverse)
        assert eq.adverse_selection < 0

    def test_no_recent_fills_key(self):
        tracker = execution_tracker()
        ctx = _make_ctx(params={})  # No _recent_fills key
        tracker(ctx)
        eq = ctx.params["exec_quality"]
        assert eq.total_fills == 0

    def test_function_name(self):
        tracker = execution_tracker()
        assert tracker.__name__ == "execution_tracker"

    def test_many_fills_dedup_eviction(self):
        """Dedup set should evict old entries at 10k cap."""
        tracker = execution_tracker()
        # Won't actually test 10k fills, just verify it doesn't crash
        for i in range(100):
            fill = MockFill(f"f{i}", "mkt1", 0.50)
            ctx = _make_ctx(fills=[fill])
            tracker(ctx)
        eq = ctx.params["exec_quality"]
        assert eq.total_fills == 100
