# Copyright (c) 2024-2026 YuyAI / InsAIts Team. All rights reserved.
# Proprietary and confidential. See LICENSE.premium for terms.
"""
InsAIts Quality Guard - Premium Core
======================================
Safety layer that runs AFTER compression decisions and BEFORE they
are applied. Can veto any compression that would degrade reasoning quality.

The guard NEVER allows quality_risk to reach "high" -- if it would,
the entire compression cycle is vetoed.

Author: Cristi Bogdan -- Steddy Nova SRL / YuyAI
"""

from __future__ import annotations

import logging
from typing import Any, Dict, List, Optional

logger = logging.getLogger("insaits.context.quality_guard")


# Import shared types from context/base.py to avoid circular imports
from insa_its.context.base import (
    ApprovalResult,
    VetoReason,
    CompressionApproval,
)


class QualityGuard:
    """Final check before any context compression is applied.

    Vetoes compression if it would degrade reasoning quality.
    Tracks veto history for dashboard metrics.
    """

    def __init__(
        self,
        min_hot_items: int = 20,
        max_compression_pct: float = 40.0,
        recent_critical_window: int = 10,
    ) -> None:
        self._min_hot = min_hot_items
        self._max_pct = max_compression_pct
        self._critical_window = recent_critical_window
        self._veto_history: List[Dict[str, Any]] = []
        self._approval_count: int = 0
        self._veto_count: int = 0

    def approve(
        self,
        compression_plan: List[int],
        messages: List[Dict[str, Any]],
        hot_count: int,
        total_count: int,
    ) -> CompressionApproval:
        """Review a compression plan and approve, partially approve, or veto.

        Args:
            compression_plan: List of message indices to compress.
            messages: Full message history.
            hot_count: Current number of hot items (before compression).
            total_count: Total messages in stream.

        Returns:
            CompressionApproval with result, approved/vetoed indices, and reasons.
        """
        if not compression_plan:
            self._approval_count += 1
            return CompressionApproval(
                ApprovalResult.APPROVED, [], [], []
            )

        vetoed_indices: List[int] = []
        approved_indices: List[int] = list(compression_plan)
        reasons: List[VetoReason] = []

        # Veto condition 1: too few hot items would remain
        remaining_hot = hot_count - len(compression_plan)
        if remaining_hot < self._min_hot:
            excess = self._min_hot - remaining_hot
            # Partially approve: keep enough items hot
            vetoed_from_plan = compression_plan[-excess:]
            approved_indices = [
                i for i in compression_plan if i not in vetoed_from_plan
            ]
            vetoed_indices.extend(vetoed_from_plan)
            reasons.append(VetoReason(
                "active_task_count_below_threshold",
                f"Would leave {remaining_hot} hot items, min is {self._min_hot}. "
                f"Kept {excess} items hot.",
            ))

        # Veto condition 2: compression too aggressive (>max_pct in one cycle)
        pct = (len(compression_plan) / max(total_count, 1)) * 100
        if pct > self._max_pct:
            max_allowed = int(total_count * self._max_pct / 100)
            vetoed_from_plan = compression_plan[max_allowed:]
            approved_indices = [
                i for i in approved_indices if i not in vetoed_from_plan
            ]
            vetoed_indices.extend(vetoed_from_plan)
            reasons.append(VetoReason(
                "compression_ratio_too_aggressive",
                f"Would compress {pct:.1f}% in one cycle, max is {self._max_pct}%.",
            ))

        # Veto condition 3: recent critical anomaly in last N messages
        recent_start = max(0, len(messages) - self._critical_window)
        recent_msgs = messages[recent_start:]
        has_critical = False
        for msg in recent_msgs:
            anomalies = msg.get("anomalies", [])
            if isinstance(anomalies, list):
                for a in anomalies:
                    if isinstance(a, dict) and a.get("severity") == "CRITICAL":
                        if not msg.get("resolved", False):
                            has_critical = True
                            break
            if has_critical:
                break

        if has_critical:
            # Full veto -- do not compress during active critical events
            vetoed_indices = list(compression_plan)
            approved_indices = []
            reasons.append(VetoReason(
                "recent_critical_anomaly",
                f"Unresolved CRITICAL anomaly in last {self._critical_window} messages.",
            ))

        # Veto condition 4: cross-agent dependencies
        for idx in list(approved_indices):
            if idx < len(messages):
                msg = messages[idx]
                text = ""
                for field in ("text", "content", "description"):
                    val = msg.get(field, "")
                    if isinstance(val, str):
                        text = val
                        break
                if any(kw in text.lower() for kw in (
                    "waiting for", "depends on", "blocked by", "needs result from"
                )):
                    approved_indices.remove(idx)
                    vetoed_indices.append(idx)
                    reasons.append(VetoReason(
                        "cross_agent_dependency_detected",
                        f"Message {idx} has cross-agent dependency.",
                    ))

        # Veto condition 5: error resolution in progress
        for idx in list(approved_indices):
            if idx < len(messages):
                msg = messages[idx]
                text = ""
                for field in ("text", "content", "description"):
                    val = msg.get(field, "")
                    if isinstance(val, str):
                        text = val
                        break
                if any(kw in text.lower() for kw in (
                    "debugging", "investigating", "fixing", "diagnosing",
                    "error trace", "stack trace", "traceback",
                )):
                    approved_indices.remove(idx)
                    vetoed_indices.append(idx)
                    reasons.append(VetoReason(
                        "error_resolution_in_progress",
                        f"Message {idx} is part of active error resolution.",
                    ))

        # Determine final result
        if not approved_indices and compression_plan:
            result = ApprovalResult.VETOED
            self._veto_count += 1
            self._veto_history.append({
                "reasons": [r.condition for r in reasons],
                "plan_size": len(compression_plan),
            })
            for r in reasons:
                logger.warning("QualityGuard VETO: %s -- %s", r.condition, r.detail)
        elif vetoed_indices:
            result = ApprovalResult.PARTIAL
            self._approval_count += 1
            for r in reasons:
                logger.info("QualityGuard PARTIAL: %s -- %s", r.condition, r.detail)
        else:
            result = ApprovalResult.APPROVED
            self._approval_count += 1

        return CompressionApproval(
            result=result,
            approved_indices=approved_indices,
            vetoed_indices=vetoed_indices,
            reasons=reasons,
        )

    def get_quality_risk(
        self, hot_count: int, cold_count: int, total: int,
    ) -> str:
        """Assess current quality risk level.

        Returns: "none", "low", or "medium". Never returns "high" --
        if it would be "high", the guard vetoes the cycle instead.
        """
        if total == 0:
            return "none"
        cold_ratio = cold_count / total
        if cold_ratio > 0.7:
            return "medium"
        if cold_ratio > 0.5:
            return "low"
        return "none"

    @property
    def veto_rate(self) -> float:
        """Percentage of cycles that were fully vetoed."""
        total = self._approval_count + self._veto_count
        if total == 0:
            return 0.0
        return (self._veto_count / total) * 100

    def get_stats(self) -> Dict[str, Any]:
        """Return stats for dashboard display."""
        return {
            "approvals": self._approval_count,
            "vetoes": self._veto_count,
            "veto_rate_pct": round(self.veto_rate, 1),
            "recent_veto_reasons": [
                v["reasons"] for v in self._veto_history[-5:]
            ],
        }
