"""
WebhookAlerter — fire-and-forget HTTP POSTs for drift and alert-rate spikes.

Integrates with QppgMonitor to notify Slack, Discord, PagerDuty, or any
HTTP webhook when:
  • A DriftDetector alert fires (risk distribution shifted)
  • The recent alert rate in a domain exceeds a threshold

Usage:
    from qppg_service.alerting import WebhookAlerter
    from qppg_service.monitor import QppgMonitor

    alerter = WebhookAlerter(
        url="https://hooks.slack.com/services/...",
        alert_rate_threshold=0.30,
    )
    monitor = QppgMonitor(threshold=0.65, alerter=alerter)

The payload is Slack-compatible (``text`` + ``attachments``), so it works
out of the box with Slack incoming webhooks, Discord webhooks (via the
``content`` alias), and most generic HTTP receivers.
"""

from __future__ import annotations

import json
import threading
import time
from typing import TYPE_CHECKING, Optional
from urllib.request import Request as _UrlRequest
from urllib.request import urlopen

if TYPE_CHECKING:
    from .drift import DriftAlert


class WebhookAlerter:
    """
    Sends an HTTP POST to a webhook URL when drift is detected or the
    alert rate in a domain exceeds a threshold.

    Parameters
    ----------
    url                  : Webhook endpoint (Slack, Discord, custom HTTP).
    alert_rate_threshold : Fraction [0–1] above which an alert-rate spike fires.
    min_samples          : Minimum recent chains before a rate spike can fire.
    cooldown_seconds     : Silence period per domain per alert type (default 3600).
    """

    def __init__(
        self,
        url: str,
        alert_rate_threshold: float = 0.30,
        min_samples: int = 10,
        cooldown_seconds: int = 3600,
    ):
        self.url                  = url
        self.alert_rate_threshold = alert_rate_threshold
        self.min_samples          = min_samples
        self.cooldown_seconds     = cooldown_seconds
        self._last_fired: dict[str, float] = {}

    # ── Public helpers called by QppgMonitor ───────────────────────────────────

    def fire_drift(self, drift_alert: "DriftAlert") -> None:
        """Post a drift alert. Silently ignored during cooldown."""
        key = f"drift:{drift_alert.domain}"
        if self._is_cooling(key):
            return
        text = (
            f":warning: *llm-guard-kit Drift Alert* — domain `{drift_alert.domain}`\n"
            f"Risk score shifted {drift_alert.delta:+.3f} "
            f"({drift_alert.previous_mean:.3f} → {drift_alert.current_mean:.3f})\n"
            f"_{drift_alert.recommendation}_"
        )
        payload = {
            "text": text,
            "attachments": [{
                "color": "#f59e0b",
                "fields": [
                    {"title": "Domain",   "value": drift_alert.domain,                 "short": True},
                    {"title": "Delta",    "value": f"{drift_alert.delta:+.3f}",        "short": True},
                    {"title": "Current",  "value": f"{drift_alert.current_mean:.3f}",  "short": True},
                    {"title": "Previous", "value": f"{drift_alert.previous_mean:.3f}", "short": True},
                ],
            }],
        }
        self._post(payload, key)

    def fire_alert_rate(self, domain: str, alert_rate: float, n_recent: int) -> None:
        """Post an alert-rate spike. Ignored during cooldown or below threshold."""
        if alert_rate < self.alert_rate_threshold or n_recent < self.min_samples:
            return
        key = f"rate:{domain}"
        if self._is_cooling(key):
            return
        text = (
            f":rotating_light: *llm-guard-kit Alert Spike* — domain `{domain}`\n"
            f"Alert rate: {alert_rate:.1%}  ({n_recent} recent chains)\n"
            f"Threshold: {self.alert_rate_threshold:.1%}"
        )
        payload = {
            "text": text,
            "attachments": [{
                "color": "#ef4444",
                "fields": [
                    {"title": "Domain",     "value": domain,                                 "short": True},
                    {"title": "Alert rate", "value": f"{alert_rate:.1%}",                    "short": True},
                    {"title": "Threshold",  "value": f"{self.alert_rate_threshold:.1%}",     "short": True},
                    {"title": "Samples",    "value": str(n_recent),                          "short": True},
                ],
            }],
        }
        self._post(payload, key)

    # ── Internal ───────────────────────────────────────────────────────────────

    def _is_cooling(self, key: str) -> bool:
        return time.time() - self._last_fired.get(key, 0) < self.cooldown_seconds

    def _post(self, payload: dict, key: str) -> None:
        """Fire-and-forget HTTP POST on a daemon thread."""
        self._last_fired[key] = time.time()
        data = json.dumps(payload).encode()
        url  = self.url

        def _send() -> None:
            try:
                req = _UrlRequest(
                    url, data=data,
                    headers={"Content-Type": "application/json"},
                )
                with urlopen(req, timeout=5):
                    pass
            except Exception:  # noqa: BLE001 — alerting must never crash the agent
                pass

        threading.Thread(target=_send, daemon=True).start()
