"""Execution quality tracking and feedback at fund level.

Aggregates execution metrics across all strategies and provides
signals for adaptive parameter tuning.
"""

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.exec_quality")


@dataclass(frozen=True)
class ExecutionSnapshot:
    """Point-in-time execution quality for a strategy."""
    strategy: str
    timestamp: float
    fill_rate: float
    avg_slippage_bps: float
    adverse_selection_rate: float
    market_impact_bps: float
    total_fills: int
    total_quotes: int


@dataclass(frozen=True)
class FundExecutionReport:
    """Aggregated execution quality across all strategies."""
    timestamp: float
    strategies: list[ExecutionSnapshot]
    fund_fill_rate: float
    fund_avg_slippage_bps: float
    fund_adverse_selection_rate: float
    worst_strategy: str
    worst_fill_rate: float
    total_fills: int
    total_quotes: int

    def summary(self) -> dict[str, Any]:
        return {
            "fund_fill_rate": round(self.fund_fill_rate, 3),
            "fund_avg_slippage_bps": round(self.fund_avg_slippage_bps, 1),
            "fund_adverse_selection": round(self.fund_adverse_selection_rate, 3),
            "worst_strategy": self.worst_strategy,
            "total_fills": self.total_fills,
            "total_quotes": self.total_quotes,
            "per_strategy": {
                s.strategy: {
                    "fill_rate": round(s.fill_rate, 3),
                    "slippage_bps": round(s.avg_slippage_bps, 1),
                    "adverse_selection": round(s.adverse_selection_rate, 3),
                }
                for s in self.strategies
            },
        }


@dataclass
class _StrategyExecState:
    """Internal tracking state for a strategy's execution quality."""
    fills: int = 0
    quotes: int = 0
    slippage_sum: float = 0.0
    adverse_sum: float = 0.0
    impact_sum: float = 0.0
    recent_fill_rates: deque = field(default_factory=lambda: deque(maxlen=100))
    recent_slippages: deque = field(default_factory=lambda: deque(maxlen=100))


class ExecutionQualityTracker:
    """Tracks execution quality metrics across all fund strategies.

    Collects fill events from each strategy's engine and computes
    rolling execution quality metrics. Provides signals for the
    adaptive execution module to tune parameters.
    """

    def __init__(self) -> None:
        self._states: dict[str, _StrategyExecState] = {}
        self._lock = threading.Lock()
        self._snapshots: deque[FundExecutionReport] = deque(maxlen=10_000)

    def record_fill(
        self,
        strategy: str,
        expected_price: float,
        fill_price: float,
        mid_price_at_fill: float,
        mid_price_after: float | None = None,
        size: float = 0.0,
        side: str = "buy",
    ) -> None:
        """Record a fill event for quality tracking.

        Args:
            strategy: Strategy name.
            expected_price: Price the strategy intended.
            fill_price: Actual fill price.
            mid_price_at_fill: Market mid at time of fill.
            mid_price_after: Market mid shortly after fill (for adverse selection).
            size: Fill size.
            side: "buy" or "sell" -- used for directional adverse selection.
        """
        with self._lock:
            state = self._states.setdefault(strategy, _StrategyExecState())
            state.fills += 1

            # Slippage: difference between expected and actual in bps
            if expected_price > 0:
                slippage_bps = abs(fill_price - expected_price) / expected_price * 10_000
            else:
                slippage_bps = 0.0
            state.slippage_sum += slippage_bps
            state.recent_slippages.append(slippage_bps)

            # Adverse selection: directional -- did the mid move against us?
            if mid_price_after is not None and mid_price_at_fill > 0:
                mid_move = (mid_price_after - mid_price_at_fill) / mid_price_at_fill
                if side == "buy":
                    adverse = max(-mid_move, 0.0)  # Price drop after buying is adverse
                else:
                    adverse = max(mid_move, 0.0)   # Price rise after selling is adverse
                state.adverse_sum += adverse

            # Market impact
            if mid_price_at_fill > 0:
                impact_bps = abs(fill_price - mid_price_at_fill) / mid_price_at_fill * 10_000
                state.impact_sum += impact_bps

    def record_quote(self, strategy: str) -> None:
        """Record a quote submission (used to compute fill rate)."""
        with self._lock:
            state = self._states.setdefault(strategy, _StrategyExecState())
            state.quotes += 1

    def snapshot(self) -> FundExecutionReport:
        """Compute current execution quality report."""
        now = time.time()
        strat_snaps: list[ExecutionSnapshot] = []
        total_fills = 0
        total_quotes = 0

        with self._lock:
            for name, state in self._states.items():
                fill_rate = state.fills / max(state.quotes, 1)
                avg_slip = state.slippage_sum / max(state.fills, 1)
                adv_rate = state.adverse_sum / max(state.fills, 1)
                impact = state.impact_sum / max(state.fills, 1)

                strat_snaps.append(ExecutionSnapshot(
                    strategy=name,
                    timestamp=now,
                    fill_rate=fill_rate,
                    avg_slippage_bps=avg_slip,
                    adverse_selection_rate=adv_rate,
                    market_impact_bps=impact,
                    total_fills=state.fills,
                    total_quotes=state.quotes,
                ))
                total_fills += state.fills
                total_quotes += state.quotes

        fund_fill_rate = total_fills / max(total_quotes, 1)
        fund_slip = sum(s.avg_slippage_bps * s.total_fills for s in strat_snaps) / max(total_fills, 1)
        fund_adv = sum(s.adverse_selection_rate * s.total_fills for s in strat_snaps) / max(total_fills, 1)

        worst = min(strat_snaps, key=lambda s: s.fill_rate) if strat_snaps else None

        report = FundExecutionReport(
            timestamp=now,
            strategies=strat_snaps,
            fund_fill_rate=fund_fill_rate,
            fund_avg_slippage_bps=fund_slip,
            fund_adverse_selection_rate=fund_adv,
            worst_strategy=worst.strategy if worst else "",
            worst_fill_rate=worst.fill_rate if worst else 0.0,
            total_fills=total_fills,
            total_quotes=total_quotes,
        )
        self._snapshots.append(report)
        return report

    def strategy_metrics(self, strategy: str) -> dict[str, float]:
        """Get current metrics for a single strategy."""
        with self._lock:
            state = self._states.get(strategy)
            if state is None:
                return {"fill_rate": 0.0, "avg_slippage_bps": 0.0, "adverse_selection": 0.0}
            return {
                "fill_rate": state.fills / max(state.quotes, 1),
                "avg_slippage_bps": state.slippage_sum / max(state.fills, 1),
                "adverse_selection": state.adverse_sum / max(state.fills, 1),
                "market_impact_bps": state.impact_sum / max(state.fills, 1),
                "total_fills": state.fills,
                "total_quotes": state.quotes,
            }

    def recommendations(self) -> dict[str, dict[str, str]]:
        """Generate execution parameter recommendations per strategy."""
        recs: dict[str, dict[str, str]] = {}
        with self._lock:
            for name, state in self._states.items():
                if state.quotes < 10:
                    continue
                fill_rate = state.fills / max(state.quotes, 1)
                avg_slip = state.slippage_sum / max(state.fills, 1)
                adv_rate = state.adverse_sum / max(state.fills, 1)

                rec: dict[str, str] = {}
                if fill_rate < 0.5:
                    rec["spread"] = "tighten_5pct"
                if adv_rate > 0.01:
                    rec["spread"] = "widen_10pct"
                if avg_slip > 20:
                    rec["size"] = "reduce_20pct"
                if fill_rate > 0.9 and adv_rate < 0.005:
                    rec["size"] = "increase_10pct"

                if rec:
                    recs[name] = rec
        return recs

    def remove_strategy(self, strategy: str) -> None:
        with self._lock:
            self._states.pop(strategy, None)

    def recent_reports(self, limit: int = 10) -> list[FundExecutionReport]:
        return list(self._snapshots)[-limit:]
