#!/usr/bin/env python3
"""
Safety rails for FlowTorch production.
- Jitter budget monitoring
- MI degradation detection
- Rate limiting
- Graceful degradation
"""

import time
from collections import deque
from typing import Optional, Tuple, List
import math


class SafetyMonitor:
    """
    Real-time safety monitoring for basin control.
    Tracks MI degradation, jitter, and enforces rate limits.
    """

    def __init__(
        self,
        mi_floor: float = 0.85,
        mi_window: int = 20,
        jitter_budget: float = 15.0,
        jitter_window: int = 10,
        rate_limit_rpm: int = 300,  # Requests per minute
    ):
        """
        Initialize safety monitor.

        Args:
            mi_floor: Minimum acceptable MI (as fraction of theoretical max)
            mi_window: Window size for MI averaging
            jitter_budget: Max acceptable PHI0 jitter (degrees)
            jitter_window: Window size for jitter averaging
            rate_limit_rpm: Max requests per minute
        """
        # MI tracking
        self.mi_floor = mi_floor
        self.mi_values = deque(maxlen=mi_window)

        # Jitter tracking
        self.jitter_budget = jitter_budget
        self.phi_values = deque(maxlen=jitter_window)

        # Rate limiting
        self.rate_limit_rpm = rate_limit_rpm
        self.request_times = deque(maxlen=rate_limit_rpm)

        # Degradation state
        self.degraded = False
        self.degradation_reason: Optional[str] = None

    def record_mi(self, mi: float, theoretical_max: float) -> bool:
        """
        Record MI measurement and check for degradation.

        Args:
            mi: Measured MI (bits)
            theoretical_max: Theoretical max MI for cardinality

        Returns:
            True if MI is healthy, False if degraded
        """
        # Calculate efficiency
        efficiency = mi / theoretical_max if theoretical_max > 0 else 0.0
        self.mi_values.append(efficiency)

        # Check if degraded (mean efficiency below floor)
        if len(self.mi_values) >= 5:  # Need at least 5 samples
            mean_efficiency = sum(self.mi_values) / len(self.mi_values)

            if mean_efficiency < self.mi_floor:
                self.degraded = True
                self.degradation_reason = f"MI efficiency {mean_efficiency:.3f} < floor {self.mi_floor}"
                return False

        return True

    def record_phi(self, phi: float) -> Tuple[bool, float]:
        """
        Record PHI0 measurement and check jitter.

        Args:
            phi: Measured phase (degrees)

        Returns:
            (jitter_ok, current_jitter) tuple
        """
        self.phi_values.append(phi)

        if len(self.phi_values) < 2:
            return True, 0.0

        # Calculate jitter (circular distance)
        jitters = []
        for i in range(len(self.phi_values) - 1):
            phi1 = self.phi_values[i]
            phi2 = self.phi_values[i + 1]

            # Circular distance
            diff = abs(phi2 - phi1)
            if diff > 180:
                diff = 360 - diff

            jitters.append(diff)

        mean_jitter = sum(jitters) / len(jitters)

        # Check if jitter exceeded
        if mean_jitter > self.jitter_budget:
            self.degraded = True
            self.degradation_reason = f"Jitter {mean_jitter:.1f}° > budget {self.jitter_budget}°"
            return False, mean_jitter

        return True, mean_jitter

    def check_rate_limit(self) -> bool:
        """
        Check if rate limit exceeded.

        Returns:
            True if within limit, False if exceeded
        """
        now = time.time()
        self.request_times.append(now)

        # Count requests in last minute
        cutoff = now - 60.0
        recent = sum(1 for t in self.request_times if t > cutoff)

        if recent > self.rate_limit_rpm:
            self.degraded = True
            self.degradation_reason = f"Rate limit exceeded: {recent}/{self.rate_limit_rpm} rpm"
            return False

        return True

    def get_status(self) -> dict:
        """Get current safety status."""
        # MI stats
        mi_vals = list(self.mi_values)
        mean_mi = sum(mi_vals) / len(mi_vals) if mi_vals else 0.0

        # Jitter stats
        if len(self.phi_values) >= 2:
            jitters = []
            for i in range(len(self.phi_values) - 1):
                diff = abs(self.phi_values[i + 1] - self.phi_values[i])
                if diff > 180:
                    diff = 360 - diff
                jitters.append(diff)
            mean_jitter = sum(jitters) / len(jitters)
        else:
            jitters = []
            mean_jitter = 0.0

        # Rate limit
        now = time.time()
        cutoff = now - 60.0
        requests_last_minute = sum(1 for t in self.request_times if t > cutoff)

        # Overall status
        if self.degraded:
            status = "degraded"
        elif mean_mi < self.mi_floor * 1.1:  # Within 10% of floor
            status = "warning"
        elif mean_jitter > self.jitter_budget * 0.8:  # Within 80% of budget
            status = "warning"
        elif requests_last_minute > self.rate_limit_rpm * 0.8:
            status = "warning"
        else:
            status = "healthy"

        return {
            "status": status,
            "degraded": self.degraded,
            "degradation_reason": self.degradation_reason,
            "mi": {
                "recent_values": mi_vals[-10:],
                "mean": mean_mi,
                "floor": self.mi_floor,
                "below_floor": mean_mi < self.mi_floor,
            },
            "jitter": {
                "recent_values": jitters[-10:] if jitters else [],
                "mean": mean_jitter,
                "budget": self.jitter_budget,
                "exceeded": mean_jitter > self.jitter_budget,
            },
            "rate_limit": {
                "requests_last_minute": requests_last_minute,
                "limit": self.rate_limit_rpm,
                "exceeded": requests_last_minute > self.rate_limit_rpm,
            },
        }

    def reset_degradation(self):
        """Reset degradation state (for recovery)."""
        self.degraded = False
        self.degradation_reason = None


def graceful_degrade(cardinality: int, reason: str) -> Tuple[int, str]:
    """
    Gracefully degrade to lower cardinality when safety rails trigger.

    Args:
        cardinality: Current cardinality
        reason: Degradation reason

    Returns:
        (new_cardinality, degradation_message) tuple

    Examples:
        >>> graceful_degrade(8, "MI degraded")
        (4, "Degraded from 8→4 states (MI degraded)")
        >>> graceful_degrade(2, "Jitter exceeded")
        (2, "Cannot degrade below binary (Jitter exceeded)")
    """
    # Degradation ladder: 8 → 4 → 3 → 2
    degrade_map = {
        8: 4,
        4: 3,
        3: 2,
        2: 2,  # Cannot degrade below binary
    }

    new_card = degrade_map.get(cardinality, 2)

    if new_card == cardinality:
        # Already at minimum
        message = f"Cannot degrade below binary ({reason})"
    else:
        message = f"Degraded from {cardinality}→{new_card} states ({reason})"

    return new_card, message


# Global safety monitor instance
_monitor = SafetyMonitor()


def get_monitor() -> SafetyMonitor:
    """Get global safety monitor instance."""
    return _monitor
