"""Trade surveillance: detect wash trading, spoofing, layering, and other manipulation."""

from __future__ import annotations

import logging
import time
import uuid
from collections import deque
from typing import Any

from horizon.compliance._audit import AuditTrail
from horizon.compliance._store import ComplianceStore
from horizon.compliance._types import (
    AuditEventType, ComplianceConfig, SurveillanceAlert, SurveillanceAlertType,
)

logger = logging.getLogger("horizon.compliance.surveillance")


class TradeSurveillance:
    """Real-time trade surveillance with sliding window detection.

    Detects:
    - Wash trading: buy + sell same market within threshold
    - Spoofing: large order placed and canceled before fill
    - Layering: N stacked orders on one side canceled after opposite fill
    - Rapid-fire: order rate exceeding configured limit
    - Concentration: single market exceeding portfolio share limit
    """

    def __init__(
        self,
        config: ComplianceConfig,
        store: ComplianceStore,
        audit: AuditTrail,
    ) -> None:
        self._config = config
        self._store = store
        self._audit = audit
        window = int(config.surveillance_window_secs * 10)  # generous deque size
        self._recent_orders: deque[tuple[float, str, str, float, str]] = deque(maxlen=max(window, 1000))
        self._recent_cancels: deque[tuple[float, str, str]] = deque(maxlen=max(window, 1000))
        self._recent_fills: deque[tuple[float, str, str, float]] = deque(maxlen=max(window, 1000))
        self._order_count_window: deque[float] = deque(maxlen=10000)
        self._cancel_count_window: deque[float] = deque(maxlen=10000)
        self.recent_alerts: list[SurveillanceAlert] = []

    def record_order(self, timestamp: float, market_id: str, side: str, size: float, order_id: str) -> None:
        """Record an order submission for surveillance tracking."""
        self._recent_orders.append((timestamp, market_id, side, size, order_id))
        self._order_count_window.append(timestamp)

    def record_cancel(self, timestamp: float, order_id: str, market_id: str = "") -> None:
        """Record an order cancellation for surveillance tracking."""
        self._recent_cancels.append((timestamp, order_id, market_id))
        self._cancel_count_window.append(timestamp)

    def record_fill(self, timestamp: float, market_id: str, side: str, size: float) -> None:
        """Record a fill for surveillance tracking."""
        self._recent_fills.append((timestamp, market_id, side, size))

    def analyze_cycle(self, engine: Any = None) -> list[SurveillanceAlert]:
        """Run all detection algorithms. Called once per strategy cycle."""
        if not self._config.surveillance_enabled:
            return []

        now = time.time()
        alerts: list[SurveillanceAlert] = []

        alerts.extend(self._detect_wash_trading(now))
        alerts.extend(self._detect_spoofing(now))
        alerts.extend(self._detect_layering(now))
        alerts.extend(self._detect_rapid_fire(now))

        if engine is not None:
            alerts.extend(self._detect_concentration(engine))

        # Persist and record
        for alert in alerts:
            self._store.insert_alert(alert)
            self._audit.record(
                AuditEventType.SURVEILLANCE_ALERT, "surveillance",
                alert.to_dict(), market_id=alert.market_id,
            )

        self.recent_alerts = alerts
        return alerts

    def _detect_wash_trading(self, now: float) -> list[SurveillanceAlert]:
        """Detect buy + sell on same market within wash_trade_threshold_secs."""
        threshold = self._config.wash_trade_threshold_secs
        alerts: list[SurveillanceAlert] = []

        # Group recent fills by market
        fills_by_market: dict[str, list[tuple[float, str, float]]] = {}
        for ts, mid, side, size in self._recent_fills:
            if now - ts > self._config.surveillance_window_secs:
                continue
            fills_by_market.setdefault(mid, []).append((ts, side, size))

        for market_id, fills in fills_by_market.items():
            buys = [(ts, size) for ts, side, size in fills if "buy" in side.lower()]
            sells = [(ts, size) for ts, side, size in fills if "sell" in side.lower()]

            for b_ts, b_size in buys:
                for s_ts, s_size in sells:
                    if abs(b_ts - s_ts) < threshold:
                        alerts.append(SurveillanceAlert(
                            alert_id=uuid.uuid4().hex,
                            alert_type=SurveillanceAlertType.WASH_TRADING,
                            severity="critical",
                            timestamp=now,
                            market_id=market_id,
                            description=(
                                f"Buy and sell on {market_id} within "
                                f"{abs(b_ts - s_ts):.1f}s (threshold: {threshold}s)"
                            ),
                            evidence={
                                "buy_timestamp": b_ts, "buy_size": b_size,
                                "sell_timestamp": s_ts, "sell_size": s_size,
                                "time_delta_secs": abs(b_ts - s_ts),
                            },
                        ))
                        break  # One alert per market per cycle
                else:
                    continue
                break

        return alerts

    def _detect_spoofing(self, now: float) -> list[SurveillanceAlert]:
        """Detect orders placed and canceled within spoof_cancel_threshold_secs."""
        threshold = self._config.spoof_cancel_threshold_secs
        alerts: list[SurveillanceAlert] = []
        cancel_ids = {
            oid: ts for ts, oid, _ in self._recent_cancels
            if now - ts < self._config.surveillance_window_secs
        }

        for o_ts, market_id, side, size, order_id in self._recent_orders:
            if now - o_ts > self._config.surveillance_window_secs:
                continue
            if order_id in cancel_ids:
                c_ts = cancel_ids[order_id]
                delta = c_ts - o_ts
                if 0 < delta < threshold:
                    alerts.append(SurveillanceAlert(
                        alert_id=uuid.uuid4().hex,
                        alert_type=SurveillanceAlertType.SPOOFING,
                        severity="warning",
                        timestamp=now,
                        market_id=market_id,
                        description=(
                            f"Order {order_id[:12]} placed and canceled within "
                            f"{delta:.2f}s on {market_id}"
                        ),
                        evidence={
                            "order_id": order_id, "submit_time": o_ts,
                            "cancel_time": c_ts, "time_delta_secs": delta,
                            "side": side, "size": size,
                        },
                    ))

        return alerts

    def _detect_layering(self, now: float) -> list[SurveillanceAlert]:
        """Detect N stacked orders on one side within the surveillance window."""
        depth = self._config.layering_depth
        alerts: list[SurveillanceAlert] = []

        # Group active orders by (market, side)
        stacks: dict[tuple[str, str], list[str]] = {}
        for o_ts, market_id, side, size, order_id in self._recent_orders:
            if now - o_ts > self._config.surveillance_window_secs:
                continue
            key = (market_id, side.lower())
            stacks.setdefault(key, []).append(order_id)

        for (market_id, side), order_ids in stacks.items():
            if len(order_ids) >= depth:
                alerts.append(SurveillanceAlert(
                    alert_id=uuid.uuid4().hex,
                    alert_type=SurveillanceAlertType.LAYERING,
                    severity="warning",
                    timestamp=now,
                    market_id=market_id,
                    description=(
                        f"{len(order_ids)} stacked {side} orders on {market_id} "
                        f"(threshold: {depth})"
                    ),
                    evidence={
                        "order_count": len(order_ids),
                        "side": side,
                        "order_ids": order_ids[:10],
                    },
                ))

        return alerts

    def _detect_rapid_fire(self, now: float) -> list[SurveillanceAlert]:
        """Detect order rate exceeding max_orders_per_minute."""
        max_rate = self._config.regulatory_limits.max_orders_per_minute
        cutoff = now - 60.0
        recent = sum(1 for ts in self._order_count_window if ts > cutoff)

        if recent > max_rate:
            return [SurveillanceAlert(
                alert_id=uuid.uuid4().hex,
                alert_type=SurveillanceAlertType.RAPID_FIRE,
                severity="warning",
                timestamp=now,
                market_id="*",
                description=f"Order rate {recent}/min exceeds limit {max_rate}/min",
                evidence={"orders_last_minute": recent, "limit": max_rate},
            )]
        return []

    def _detect_concentration(self, engine: Any) -> list[SurveillanceAlert]:
        """Detect single market exceeding concentration limit."""
        max_pct = self._config.regulatory_limits.max_concentration_pct
        if max_pct >= 100.0:
            return []

        try:
            positions = engine.positions()
        except Exception:
            return []

        if not positions:
            return []

        # Sum notional by market
        notional_by_market: dict[str, float] = {}
        total = 0.0
        for pos in positions:
            n = pos.size * pos.avg_entry_price
            notional_by_market[pos.market_id] = notional_by_market.get(pos.market_id, 0.0) + n
            total += n

        if total <= 0:
            return []

        alerts: list[SurveillanceAlert] = []
        for market_id, notional in notional_by_market.items():
            pct = (notional / total) * 100.0
            if pct > max_pct:
                alerts.append(SurveillanceAlert(
                    alert_id=uuid.uuid4().hex,
                    alert_type=SurveillanceAlertType.CONCENTRATION,
                    severity="warning",
                    timestamp=time.time(),
                    market_id=market_id,
                    description=(
                        f"Market {market_id} is {pct:.1f}% of portfolio "
                        f"(limit: {max_pct:.1f}%)"
                    ),
                    evidence={
                        "concentration_pct": pct, "limit_pct": max_pct,
                        "market_notional": notional, "total_notional": total,
                    },
                ))

        return alerts
