"""Execution intelligence: toxicity detection, inventory risk, and execution scheduling.

Wraps Rust VpinDetector, OfiTracker, and Avellaneda-Stoikov functions
to provide market-microstructure-aware execution decisions.
"""

from __future__ import annotations

import logging
import math
import threading
import time
from collections import deque
from dataclasses import dataclass

from horizon._horizon import (
    OfiTracker,
    VpinDetector,
    optimal_spread,
    reservation_price,
)

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


# ---------------------------------------------------------------------------
# Data classes
# ---------------------------------------------------------------------------

@dataclass(frozen=True)
class ToxicityAlert:
    """Alert raised when VPIN exceeds the configured threshold."""
    market_id: str
    vpin: float
    threshold: float
    recommended_action: str
    timestamp: float


@dataclass(frozen=True)
class InventoryRiskReport:
    """Avellaneda-Stoikov inventory risk assessment for a single market."""
    market_id: str
    net_inventory: float
    reservation_price: float
    optimal_spread: float
    recommended_bid: float
    recommended_ask: float
    timestamp: float


@dataclass(frozen=True)
class ExecutionSchedule:
    """Execution plan for splitting a large order into slices."""
    market_id: str
    total_size: float
    slice_count: int
    slice_sizes: list[float]
    interval_secs: float
    expected_cost: float
    method: str


# ---------------------------------------------------------------------------
# Main class
# ---------------------------------------------------------------------------

class ExecutionIntelligence:
    """Toxicity detection, inventory risk management, and execution scheduling.

    Uses Rust-backed VpinDetector and OfiTracker for real-time
    microstructure analysis, and Avellaneda-Stoikov functions for
    inventory-aware quoting.
    """

    def __init__(
        self,
        vpin_threshold: float = 0.7,
        bucket_volume: float = 1000.0,
        gamma: float = 0.1,
        kappa: float = 1.5,
    ) -> None:
        self._vpin_detectors: dict[str, VpinDetector] = {}
        self._ofi_trackers: dict[str, OfiTracker] = {}
        self._vpin_threshold = vpin_threshold
        self._bucket_volume = bucket_volume
        self._gamma = gamma
        self._kappa = kappa
        self._alerts: deque[ToxicityAlert] = deque(maxlen=10_000)
        self._lock = threading.Lock()

    # ------------------------------------------------------------------
    # Toxicity
    # ------------------------------------------------------------------

    def update_toxicity(
        self,
        market_id: str,
        price: float,
        volume: float,
        is_buy: bool,
    ) -> ToxicityAlert | None:
        """Update VPIN for *market_id* and return an alert if above threshold."""
        with self._lock:
            detector = self._vpin_detectors.get(market_id)
            if detector is None:
                detector = VpinDetector(
                    bucket_volume=self._bucket_volume,
                    n_buckets=50,
                )
                self._vpin_detectors[market_id] = detector

            vpin = detector.update(price, volume, is_buy)

            if math.isnan(vpin) or vpin <= self._vpin_threshold:
                return None

            if vpin > 0.9:
                action = "halt_trading"
            elif vpin > 0.8:
                action = "reduce_size"
            else:
                action = "widen_spread"

            alert = ToxicityAlert(
                market_id=market_id,
                vpin=vpin,
                threshold=self._vpin_threshold,
                recommended_action=action,
                timestamp=time.time(),
            )
            self._alerts.append(alert)
            logger.warning(
                "toxicity alert market=%s vpin=%.3f action=%s",
                market_id,
                vpin,
                action,
            )
            return alert

    # ------------------------------------------------------------------
    # Order-flow imbalance
    # ------------------------------------------------------------------

    def update_ofi(
        self,
        market_id: str,
        best_bid_qty: float,
        best_ask_qty: float,
    ) -> float:
        """Update order-flow imbalance tracker and return current OFI value."""
        with self._lock:
            tracker = self._ofi_trackers.get(market_id)
            if tracker is None:
                tracker = OfiTracker()
                self._ofi_trackers[market_id] = tracker

            return tracker.update(best_bid_qty, best_ask_qty)

    # ------------------------------------------------------------------
    # Inventory risk (Avellaneda-Stoikov)
    # ------------------------------------------------------------------

    def inventory_risk(
        self,
        market_id: str,
        mid_price: float,
        net_inventory: float,
        volatility: float,
        time_horizon: float = 1.0,
    ) -> InventoryRiskReport:
        """Compute reservation price and optimal spread for a market."""
        res_price = reservation_price(
            mid=mid_price,
            inventory=net_inventory,
            gamma=self._gamma,
            volatility=volatility,
            time_horizon=time_horizon,
        )
        opt_spread = optimal_spread(
            volatility=volatility,
            inventory=net_inventory,
            gamma=self._gamma,
            kappa=self._kappa,
            time_horizon=time_horizon,
        )

        half_spread = opt_spread / 2.0
        return InventoryRiskReport(
            market_id=market_id,
            net_inventory=net_inventory,
            reservation_price=res_price,
            optimal_spread=opt_spread,
            recommended_bid=res_price - half_spread,
            recommended_ask=res_price + half_spread,
            timestamp=time.time(),
        )

    # ------------------------------------------------------------------
    # Execution scheduling
    # ------------------------------------------------------------------

    def schedule_execution(
        self,
        market_id: str,
        total_size: float,
        urgency: float = 0.5,
        depth: float = 1000.0,
    ) -> ExecutionSchedule:
        """Build an execution schedule that balances urgency vs. market impact.

        Args:
            market_id: Target market.
            total_size: Total size to execute.
            urgency: 0 (patient) to 1 (urgent).
            depth: Estimated market depth (used for slice sizing).
        """
        urgency = max(0.0, min(1.0, urgency))
        depth = max(1.0, depth)

        base_slices = max(1, int(total_size / (depth * 0.1)))

        if urgency > 0.8:
            slice_count = max(1, min(base_slices, 2))
            method = "immediate"
        elif urgency < 0.3:
            slice_count = max(base_slices, 3)
            method = "twap"
        else:
            slice_count = max(1, base_slices)
            method = "adaptive"

        # Distribute size evenly across slices
        base_slice_size = total_size / slice_count
        slice_sizes: list[float] = []
        remaining = total_size
        for i in range(slice_count):
            if i == slice_count - 1:
                slice_sizes.append(remaining)
            else:
                s = round(base_slice_size, 8)
                slice_sizes.append(s)
                remaining -= s

        interval_secs = (1.0 - urgency) * 60.0 + 5.0
        expected_cost = total_size * 0.001 * (1.0 + urgency)

        return ExecutionSchedule(
            market_id=market_id,
            total_size=total_size,
            slice_count=slice_count,
            slice_sizes=slice_sizes,
            interval_secs=round(interval_secs, 2),
            expected_cost=round(expected_cost, 6),
            method=method,
        )

    # ------------------------------------------------------------------
    # Queries
    # ------------------------------------------------------------------

    def current_vpin(self, market_id: str) -> float:
        """Return current VPIN for *market_id* (0.0 if not tracked)."""
        with self._lock:
            detector = self._vpin_detectors.get(market_id)
            if detector is None:
                return 0.0
            val = detector.current_vpin()
            return 0.0 if math.isnan(val) else val

    def current_ofi(self, market_id: str) -> float:
        """Return cumulative OFI for *market_id* (0.0 if not tracked)."""
        with self._lock:
            tracker = self._ofi_trackers.get(market_id)
            if tracker is None:
                return 0.0
            return tracker.cumulative()

    def recent_alerts(self, limit: int = 20) -> list[ToxicityAlert]:
        """Return the most recent toxicity alerts."""
        with self._lock:
            items = list(self._alerts)
        return items[-limit:]

    # ------------------------------------------------------------------
    # Management
    # ------------------------------------------------------------------

    def reset_market(self, market_id: str) -> None:
        """Remove all detectors and trackers for *market_id*."""
        with self._lock:
            self._vpin_detectors.pop(market_id, None)
            self._ofi_trackers.pop(market_id, None)
        logger.info("reset detectors for market=%s", market_id)
