"""Fund alerting and notification system."""

from __future__ import annotations

import json
import logging
import threading
import time
import urllib.request
from collections import deque
from concurrent.futures import ThreadPoolExecutor
from dataclasses import dataclass, field
from enum import Enum
from typing import Any, Callable, Protocol, runtime_checkable

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


class AlertLevel(str, Enum):
    """Alert severity level."""
    INFO = "info"
    WARNING = "warning"
    CRITICAL = "critical"


class AlertCategory(str, Enum):
    """Alert category for routing and rate limiting."""
    KILL_SWITCH = "kill_switch"
    STRATEGY_FAILURE = "strategy_failure"
    DRAWDOWN_BREACH = "drawdown_breach"
    SETTLEMENT = "settlement"
    PROMOTION_READY = "promotion_ready"
    DECAY_WARNING = "decay_warning"
    EXCHANGE_OUTAGE = "exchange_outage"
    CORRELATION_SPIKE = "correlation_spike"
    RECOVERY = "recovery"
    THRESHOLD_UPDATE = "threshold_update"


@dataclass(frozen=True)
class Alert:
    """A single alert event."""
    level: AlertLevel
    category: AlertCategory
    message: str
    timestamp: float
    strategy_name: str | None = None
    details: dict[str, Any] = field(default_factory=dict)

    def to_dict(self) -> dict[str, Any]:
        return {
            "level": self.level.value,
            "category": self.category.value,
            "message": self.message,
            "timestamp": self.timestamp,
            "strategy_name": self.strategy_name,
            "details": self.details,
        }


@runtime_checkable
class AlertChannel(Protocol):
    """Protocol for alert delivery channels."""
    def send(self, alert: Alert) -> bool: ...


class WebhookChannel:
    """Delivers alerts via HTTP POST to a webhook URL.

    Uses a single-worker thread pool to avoid blocking the oversight loop.
    """

    def __init__(self, url: str, timeout_secs: float = 10.0) -> None:
        if not url.startswith(("http://", "https://")):
            raise ValueError(f"Webhook URL must start with http:// or https://, got: {url[:50]}")
        self._url = url
        self._timeout = timeout_secs
        self._executor = ThreadPoolExecutor(max_workers=1, thread_name_prefix="alert-webhook")

    def send(self, alert: Alert) -> bool:
        try:
            self._executor.submit(self._post, alert)
            return True
        except RuntimeError:
            return False

    def _post(self, alert: Alert) -> None:
        try:
            payload = json.dumps(alert.to_dict()).encode("utf-8")
            req = urllib.request.Request(
                self._url,
                data=payload,
                headers={"Content-Type": "application/json"},
                method="POST",
            )
            urllib.request.urlopen(req, timeout=self._timeout)
        except Exception as e:
            logger.warning("Webhook delivery failed for %s: %s", self._url[:60], e)

    def shutdown(self) -> None:
        self._executor.shutdown(wait=False)


class CallbackChannel:
    """Delivers alerts via a Python callable."""

    def __init__(self, callback: Callable[[Alert], Any]) -> None:
        self._callback = callback

    def send(self, alert: Alert) -> bool:
        try:
            self._callback(alert)
            return True
        except Exception as e:
            logger.warning("Callback channel error: %s", e)
            return False


class AlertManager:
    """Central alert manager with rate limiting and channel fan-out.

    Rate-limits alerts per category (max 5 per 60 seconds) to prevent
    spam during sustained adverse conditions.
    """

    def __init__(
        self,
        max_per_category_per_min: int = 5,
        rate_window_secs: float = 60.0,
    ) -> None:
        self._channels: list[AlertChannel] = []
        self._history: deque[Alert] = deque(maxlen=10_000)
        self._rate_limits: dict[str, deque[float]] = {}
        self._max_per_min = max_per_category_per_min
        self._rate_window = rate_window_secs
        self._lock = threading.Lock()

    def register_channel(self, channel: AlertChannel) -> None:
        """Register a delivery channel (webhook, callback, etc.)."""
        with self._lock:
            self._channels.append(channel)

    def fire(self, alert: Alert) -> bool:
        """Fire an alert. Returns False if rate-limited.

        Alert is always recorded in history even if rate-limited,
        but only delivered to channels if within rate limits.
        """
        with self._lock:
            self._history.append(alert)

            # Rate limit check
            cat_key = alert.category.value
            timestamps = self._rate_limits.setdefault(cat_key, deque(maxlen=self._max_per_min * 2))
            now = alert.timestamp
            cutoff = now - self._rate_window

            # Prune old entries
            while timestamps and timestamps[0] < cutoff:
                timestamps.popleft()

            if len(timestamps) >= self._max_per_min:
                logger.debug("Alert rate-limited: %s", cat_key)
                return False

            timestamps.append(now)
            channels = list(self._channels)

        # Deliver to all channels (outside lock)
        for channel in channels:
            try:
                channel.send(alert)
            except Exception as e:
                logger.debug("Channel delivery error: %s", e)

        if alert.level == AlertLevel.CRITICAL:
            logger.critical("ALERT [%s] %s", alert.category.value, alert.message)
        elif alert.level == AlertLevel.WARNING:
            logger.warning("ALERT [%s] %s", alert.category.value, alert.message)
        else:
            logger.info("ALERT [%s] %s", alert.category.value, alert.message)

        return True

    def recent_alerts(self, limit: int = 50, category: AlertCategory | None = None) -> list[Alert]:
        """Return recent alerts, optionally filtered by category."""
        with self._lock:
            alerts = list(self._history)
        if category is not None:
            alerts = [a for a in alerts if a.category == category]
        return alerts[-limit:]

    def alert_counts(self) -> dict[str, int]:
        """Count alerts by category."""
        with self._lock:
            counts: dict[str, int] = {}
            for a in self._history:
                counts[a.category.value] = counts.get(a.category.value, 0) + 1
            return counts

    def shutdown(self) -> None:
        """Shut down webhook executors."""
        with self._lock:
            for ch in self._channels:
                if isinstance(ch, WebhookChannel):
                    ch.shutdown()
