"""Cross-exchange smart order routing."""

from __future__ import annotations

import logging
import threading
import time
from collections import deque
from dataclasses import dataclass, field
from typing import Any

logger = logging.getLogger("horizon.fund.routing")


@dataclass(frozen=True)
class RouteScore:
    """Score for routing an order to a specific venue."""
    exchange: str
    price_score: float
    liquidity_score: float
    fee_score: float
    latency_score: float
    composite_score: float
    expected_slippage_bps: float


@dataclass(frozen=True)
class RouteDecision:
    """Routing decision for a trade."""
    market_id: str
    side: str
    size: float
    selected_exchange: str
    scores: list[RouteScore]
    reason: str
    timestamp: float


@dataclass(frozen=True)
class ExchangeHealth:
    """Health status of an exchange connection."""
    exchange: str
    is_healthy: bool
    avg_latency_ms: float
    error_rate: float
    last_fill_age_secs: float
    fill_rate: float
    timestamp: float


class SmartRouter:
    """Routes orders to the best exchange based on price, liquidity, fees, and latency.

    Maintains health metrics for each exchange and uses them alongside
    real-time orderbook data to select the optimal venue.

    Weights:
    - Price: 40% (best bid/ask across venues)
    - Liquidity: 30% (depth available at target price)
    - Fees: 20% (maker/taker fee structure)
    - Latency: 10% (historical fill time)
    """

    def __init__(
        self,
        price_weight: float = 0.40,
        liquidity_weight: float = 0.30,
        fee_weight: float = 0.20,
        latency_weight: float = 0.10,
    ) -> None:
        self._weights = {
            "price": price_weight,
            "liquidity": liquidity_weight,
            "fee": fee_weight,
            "latency": latency_weight,
        }
        self._health: dict[str, ExchangeHealth] = {}
        self._decisions: deque[RouteDecision] = deque(maxlen=10_000)
        self._lock = threading.Lock()
        self._fee_rates: dict[str, float] = {
            "polymarket": 0.0,
            "kalshi": 0.07,
            "paper": 0.001,
        }
        self._latencies: dict[str, float] = {}

    def set_fee_rate(self, exchange: str, rate: float) -> None:
        """Set the fee rate for an exchange (as fraction, e.g., 0.001 = 10bps)."""
        self._fee_rates[exchange] = rate

    def update_latency(self, exchange: str, latency_ms: float) -> None:
        """Update observed latency for an exchange."""
        with self._lock:
            self._latencies[exchange] = latency_ms

    def update_health(
        self,
        exchange: str,
        latency_ms: float,
        error_rate: float,
        fill_rate: float,
        last_fill_age_secs: float,
    ) -> ExchangeHealth:
        """Update health metrics for an exchange."""
        health = ExchangeHealth(
            exchange=exchange,
            is_healthy=error_rate < 0.1 and latency_ms < 5000,
            avg_latency_ms=latency_ms,
            error_rate=error_rate,
            last_fill_age_secs=last_fill_age_secs,
            fill_rate=fill_rate,
            timestamp=time.time(),
        )
        with self._lock:
            self._health[exchange] = health
        return health

    def route(
        self,
        market_id: str,
        side: str,
        size: float,
        venue_prices: dict[str, float],
        venue_depths: dict[str, float] | None = None,
    ) -> RouteDecision:
        """Decide which exchange to route an order to.

        Args:
            market_id: Market identifier.
            side: "buy" or "sell".
            size: Order size.
            venue_prices: Mapping of exchange -> best price.
            venue_depths: Mapping of exchange -> available depth at price.

        Returns:
            RouteDecision with selected venue and scores.
        """
        if not venue_prices:
            raise ValueError("No venues provided for routing")

        depths = venue_depths or {e: 1000.0 for e in venue_prices}
        scores: list[RouteScore] = []

        # Find best price across venues
        if side == "buy":
            best_price = min(venue_prices.values())
        else:
            best_price = max(venue_prices.values())

        max_depth = max(depths.values()) if depths else 1.0
        max_latency = max(self._latencies.values()) if self._latencies else 1000.0

        for exchange, price in venue_prices.items():
            # Skip unhealthy exchanges
            with self._lock:
                health = self._health.get(exchange)
                if health and not health.is_healthy:
                    continue

            # Price score (how close to best)
            if best_price > 0:
                if side == "buy":
                    price_score = best_price / max(price, 1e-10)
                else:
                    price_score = price / max(best_price, 1e-10)
                price_score = min(max(price_score, 0.0), 1.0)
            else:
                price_score = 0.5

            # Liquidity score
            depth = depths.get(exchange, 0.0)
            liquidity_score = min(depth / max(max_depth, 1.0), 1.0)

            # Fee score (lower is better)
            fee_rate = self._fee_rates.get(exchange, 0.01)
            fee_score = max(0.0, 1.0 - fee_rate * 100)  # 1% fee = 0 score

            # Latency score (lower is better)
            latency = self._latencies.get(exchange, 500.0)
            latency_score = max(0.0, 1.0 - latency / max(max_latency, 1.0))

            composite = (
                self._weights["price"] * price_score
                + self._weights["liquidity"] * liquidity_score
                + self._weights["fee"] * fee_score
                + self._weights["latency"] * latency_score
            )

            expected_slip = fee_rate * 10_000  # Convert to bps
            if depth > 0 and size > 0:
                # Add depth-based slippage estimate
                fill_fraction = min(size / depth, 1.0)
                expected_slip += fill_fraction * 50  # Up to 50bps at full depth

            scores.append(RouteScore(
                exchange=exchange,
                price_score=price_score,
                liquidity_score=liquidity_score,
                fee_score=fee_score,
                latency_score=latency_score,
                composite_score=composite,
                expected_slippage_bps=expected_slip,
            ))

        if not scores:
            # Fall back to first venue
            fallback = next(iter(venue_prices))
            decision = RouteDecision(
                market_id=market_id, side=side, size=size,
                selected_exchange=fallback,
                scores=[], reason="all_venues_unhealthy",
                timestamp=time.time(),
            )
            self._decisions.append(decision)
            return decision

        # Select best venue
        best = max(scores, key=lambda s: s.composite_score)
        decision = RouteDecision(
            market_id=market_id,
            side=side,
            size=size,
            selected_exchange=best.exchange,
            scores=sorted(scores, key=lambda s: s.composite_score, reverse=True),
            reason=f"best_composite={best.composite_score:.3f}",
            timestamp=time.time(),
        )
        self._decisions.append(decision)
        return decision

    def exchange_health_report(self) -> list[ExchangeHealth]:
        """Return current health of all tracked exchanges."""
        with self._lock:
            return list(self._health.values())

    def recent_decisions(self, limit: int = 50) -> list[RouteDecision]:
        return list(self._decisions)[-limit:]
