"""
DriftDetector — rolling risk-score drift detection for llm-guard-kit.

Compares the mean risk score over the current 7-day window against the
previous 7-day window.  If the shift exceeds `threshold` (default 0.10)
and there are at least 10 samples in the current window, a DriftAlert
is returned.

Usage:
    from qppg_service.drift import DriftDetector
    from qppg_service.store import ChainStore

    store    = ChainStore()
    detector = DriftDetector(threshold=0.10)
    alert    = detector.check("prod", store)
    if alert:
        print(f"Drift detected: {alert.delta:+.3f} ({alert.recommendation})")
"""

from __future__ import annotations

import time
from dataclasses import dataclass
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from .store import ChainStore


@dataclass
class DriftAlert:
    """Fired when rolling mean risk score shifts beyond threshold."""
    domain:         str
    current_mean:   float   # mean risk in [now-7d, now]
    previous_mean:  float   # mean risk in [now-14d, now-7d]
    delta:          float   # current_mean - previous_mean
    n_current:      int     # samples in current window
    n_previous:     int     # samples in previous window
    direction:      str     # "UP" or "DOWN"
    recommendation: str


class DriftDetector:
    """
    Detects distributional drift in agent risk scores.

    Parameters
    ----------
    threshold   : absolute change in mean risk that triggers an alert (default 0.10)
    window_days : size of each comparison window in days (default 7)
    min_samples : minimum samples in current window to trigger alert (default 10)
    """

    def __init__(
        self,
        threshold: float = 0.10,
        window_days: int = 7,
        min_samples: int = 10,
    ):
        self.threshold   = threshold
        self.window_days = window_days
        self.min_samples = min_samples

    def check(self, domain: str, store: "ChainStore") -> DriftAlert | None:
        """
        Compare current window vs previous window for the given domain.

        Returns DriftAlert if |delta| > threshold and n_current >= min_samples,
        else None.
        """
        now = time.time()
        window_sec = self.window_days * 86400

        current_end   = now
        current_start = now - window_sec
        prev_end      = current_start
        prev_start    = current_start - window_sec

        current_risks  = store.get_risk_window(domain, current_start, current_end)
        previous_risks = store.get_risk_window(domain, prev_start, prev_end)

        n_current  = len(current_risks)
        n_previous = len(previous_risks)

        if n_current < self.min_samples:
            return None

        current_mean  = sum(current_risks)  / n_current
        previous_mean = (sum(previous_risks) / n_previous) if n_previous > 0 else current_mean

        delta = current_mean - previous_mean

        if abs(delta) <= self.threshold:
            return None

        direction = "UP" if delta > 0 else "DOWN"
        recommendation = self._recommend(direction, delta, n_current)

        return DriftAlert(
            domain        = domain,
            current_mean  = round(current_mean, 4),
            previous_mean = round(previous_mean, 4),
            delta         = round(delta, 4),
            n_current     = n_current,
            n_previous    = n_previous,
            direction     = direction,
            recommendation = recommendation,
        )

    def _recommend(self, direction: str, delta: float, n: int) -> str:
        if direction == "UP":
            return (
                f"Risk scores increased by {delta:+.3f} over the last {self.window_days} days "
                f"(n={n}). Possible causes: new query distribution, model change, or "
                f"degraded retrieval. Run `llm-guard-kit recalibrate` if model changed."
            )
        else:
            return (
                f"Risk scores decreased by {delta:+.3f} over the last {self.window_days} days "
                f"(n={n}). Agent quality may have improved, or easier queries are arriving. "
                f"Verify calibration is still representative."
            )
