"""
Safety Guard — Phase 23.7

Prevents premature or harmful experience mutations.
All update operations MUST pass the SafetyGuard before executing.

Rules:
  1. Minimum sample size  — at least N outcomes required before update
  2. Success rate floor   — must meet minimum success_rate to "reinforce"
  3. Deprecation floor    — must have low enough score AND enough samples
  4. Version cap          — hard limit on number of versions per experience
  5. Cooldown             — minimum time between updates (seconds)

Default config is intentionally conservative (Lite mode).
Auto-improve is OFF by default — callers must explicitly opt in.
"""

import logging
import time
from dataclasses import dataclass, field
from typing import Optional

logger = logging.getLogger("openmemo.improvement.safety")


@dataclass
class SafetyConfig:
    """
    Configuration for the SafetyGuard.

    Fields:
        min_outcomes        — minimum outcomes before any update
        min_success_rate    — minimum success_rate to reinforce
        deprecation_score   — max score to allow deprecation
        deprecation_samples — minimum samples before deprecation
        max_versions        — hard cap on versions per experience
        cooldown_seconds    — min seconds between updates for same experience
        auto_improve        — if False, updates are blocked (data-collection mode)
    """
    min_outcomes: int = 5
    min_success_rate: float = 0.5
    deprecation_score: float = 0.35
    deprecation_samples: int = 10
    max_versions: int = 20
    cooldown_seconds: float = 300.0
    auto_improve: bool = False


@dataclass
class SafetyCheckResult:
    """Result of a SafetyGuard check."""
    allowed: bool
    reason: str
    details: dict = field(default_factory=dict)

    def to_dict(self) -> dict:
        return {
            "allowed": self.allowed,
            "reason": self.reason,
            "details": self.details,
        }


BLOCK = lambda reason, **kw: SafetyCheckResult(False, reason, kw)
ALLOW = lambda reason, **kw: SafetyCheckResult(True, reason, kw)


class SafetyGuard:
    """
    Validates that experience update operations are safe to execute.

    Args:
        config  — SafetyConfig
        store   — SQLiteStore (for reading version history + last-update time)
    """

    def __init__(self, config: Optional[SafetyConfig] = None, store=None):
        self.config = config or SafetyConfig()
        self._store = store

    def check_update(self, experience_id: str,
                     evaluation,
                     action: str) -> SafetyCheckResult:
        """
        Check whether an update action is safe to execute.

        Args:
            experience_id — target experience
            evaluation    — EvaluationResult from EvaluationEngine
            action        — "reinforce" | "downweight" | "deprecate" | "keep"

        Returns:
            SafetyCheckResult(allowed, reason, details)
        """
        cfg = self.config

        # Lite mode: auto-improve is off → block all mutations
        if not cfg.auto_improve:
            return BLOCK(
                "auto_improve_disabled",
                action=action,
                hint="Set auto_improve=True to enable experience evolution",
            )

        n = evaluation.total_outcomes

        # 1. Minimum sample check
        if n < cfg.min_outcomes:
            return BLOCK(
                "insufficient_samples",
                required=cfg.min_outcomes,
                actual=n,
                action=action,
            )

        # 2. Cooldown check
        if self._store:
            cooldown_ok, last_updated = self._check_cooldown(experience_id)
            if not cooldown_ok:
                return BLOCK(
                    "cooldown_active",
                    cooldown_seconds=cfg.cooldown_seconds,
                    last_updated=last_updated,
                    action=action,
                )

        # 3. Version cap check
        if self._store:
            version_count = self._count_versions(experience_id)
            if version_count >= cfg.max_versions:
                return BLOCK(
                    "version_cap_reached",
                    max_versions=cfg.max_versions,
                    current_versions=version_count,
                )

        # 4. Action-specific checks
        if action == "reinforce":
            if evaluation.success_rate < cfg.min_success_rate:
                return BLOCK(
                    "success_rate_too_low_to_reinforce",
                    required=cfg.min_success_rate,
                    actual=evaluation.success_rate,
                )

        if action == "deprecate":
            if n < cfg.deprecation_samples:
                return BLOCK(
                    "insufficient_samples_to_deprecate",
                    required=cfg.deprecation_samples,
                    actual=n,
                )
            if evaluation.score > cfg.deprecation_score:
                return BLOCK(
                    "score_too_high_to_deprecate",
                    max_score=cfg.deprecation_score,
                    actual=evaluation.score,
                )

        return ALLOW(
            "all_checks_passed",
            action=action,
            score=evaluation.score,
            samples=n,
        )

    def check_rollback(self, experience_id: str,
                       target_version: int) -> SafetyCheckResult:
        """Always allow rollbacks (safety escape hatch)."""
        if self._store:
            version = self._store.get_experience_version(
                experience_id, target_version
            )
            if not version:
                return BLOCK(
                    "version_not_found",
                    experience_id=experience_id,
                    target_version=target_version,
                )
        return ALLOW("rollback_allowed", target_version=target_version)

    # ─── Helpers ───────────────────────────────────────────────────

    def _check_cooldown(self, experience_id: str):
        """Returns (ok, last_updated_timestamp)."""
        rows = self._store.list_experience_versions(
            experience_id=experience_id, limit=1
        )
        if not rows:
            return True, None
        last_ts = rows[0].get("created_at", 0)
        elapsed = time.time() - last_ts
        if elapsed < self.config.cooldown_seconds:
            return False, last_ts
        return True, last_ts

    def _count_versions(self, experience_id: str) -> int:
        rows = self._store.list_experience_versions(
            experience_id=experience_id, limit=1000
        )
        return len(rows)
