"""
InsAIts SDK - Core Models
=========================
Production-grade data models for anomaly detection results.

Provides:
- Severity enum with ordered severity levels
- AnomalyResult: enhanced anomaly with confidence scoring and suggested actions
- MonitorResult: unified result from send_message with halt/alert decisions
- Conversion utilities for backward compatibility with detector.Anomaly
"""

from dataclasses import dataclass, field
from enum import Enum
from typing import Any, Dict, List, Optional


class Severity(str, Enum):
    """Ordered severity levels for anomaly classification."""
    INFO = "info"
    LOW = "low"
    MEDIUM = "medium"
    HIGH = "high"
    CRITICAL = "critical"


# Ordered mapping for comparison operations
_SEVERITY_ORDER = {
    Severity.INFO: 0,
    Severity.LOW: 1,
    Severity.MEDIUM: 2,
    Severity.HIGH: 3,
    Severity.CRITICAL: 4,
}

# Suggested actions based on severity
_DEFAULT_ACTIONS = {
    Severity.INFO: "log",
    Severity.LOW: "log",
    Severity.MEDIUM: "alert",
    Severity.HIGH: "alert",
    Severity.CRITICAL: "halt",
}


def severity_from_string(severity_str: str) -> Severity:
    """Convert a severity string to Severity enum.

    Args:
        severity_str: One of "info", "low", "medium", "high", "critical"

    Returns:
        Corresponding Severity enum value. Defaults to MEDIUM for unknown.
    """
    mapping = {
        "info": Severity.INFO,
        "low": Severity.LOW,
        "medium": Severity.MEDIUM,
        "high": Severity.HIGH,
        "critical": Severity.CRITICAL,
    }
    return mapping.get(severity_str.lower(), Severity.MEDIUM)


def max_severity(severities: List[Severity]) -> Severity:
    """Return the highest severity from a list.

    Args:
        severities: List of Severity values

    Returns:
        The highest severity. Returns INFO if list is empty.
    """
    if not severities:
        return Severity.INFO
    return max(severities, key=lambda s: _SEVERITY_ORDER.get(s, 0))


@dataclass(frozen=True)
class AnomalyResult:
    """Enhanced anomaly result with confidence scoring.

    Immutable dataclass representing a single detected anomaly
    with structured evidence and actionable severity.

    Attributes:
        type: Anomaly category identifier (e.g. "semantic_drift")
        detected: Whether the anomaly was actually triggered
        confidence: Detection confidence from 0.0 to 1.0
        severity: Classified severity level
        description: Human-readable explanation
        evidence: Raw detector output for debugging
        suggested_action: Recommended response ("log", "alert", "halt", "request_human_review")
    """
    type: str
    detected: bool
    confidence: float
    severity: Severity
    description: str
    evidence: Dict[str, Any] = field(default_factory=dict)
    suggested_action: str = "log"

    def to_dict(self) -> Dict[str, Any]:
        """Serialize to plain dict for API responses."""
        return {
            "type": self.type,
            "detected": self.detected,
            "confidence": self.confidence,
            "severity": self.severity.value,
            "description": self.description,
            "suggested_action": self.suggested_action,
        }


@dataclass(frozen=True)
class MonitorResult:
    """Unified result from a monitored message.

    Contains all anomaly results, timing info, and decision helpers.
    Returned as part of send_message response under the 'monitor_result' key.

    Attributes:
        message_id: UUID of the processed message
        sender_id: Agent that sent the message
        receiver_id: Agent that receives the message
        timestamp: ISO timestamp of processing
        anomalies: All detected anomaly results
        clean: True if no anomalies were detected
        highest_severity: Maximum severity across all anomalies
        processing_ms: Time spent processing in milliseconds
    """
    message_id: str
    sender_id: str
    receiver_id: str
    timestamp: str
    anomalies: List[AnomalyResult]
    clean: bool
    highest_severity: Severity
    processing_ms: float

    def should_halt(self) -> bool:
        """Check if any anomaly warrants halting the pipeline."""
        return any(
            a.severity == Severity.CRITICAL
            for a in self.anomalies
            if a.detected
        )

    def should_alert(self) -> bool:
        """Check if any anomaly warrants an alert."""
        return any(
            a.severity in (Severity.HIGH, Severity.CRITICAL)
            for a in self.anomalies
            if a.detected
        )

    def to_dict(self) -> Dict[str, Any]:
        """Serialize to plain dict for API responses."""
        return {
            "message_id": self.message_id,
            "sender_id": self.sender_id,
            "receiver_id": self.receiver_id,
            "clean": self.clean,
            "highest_severity": self.highest_severity.value,
            "anomalies": [a.to_dict() for a in self.anomalies if a.detected],
            "processing_ms": round(self.processing_ms, 2),
        }


def convert_legacy_anomaly(legacy_anomaly: Any) -> AnomalyResult:
    """Convert a detector.Anomaly (legacy) to AnomalyResult.

    Bridges the existing detector.Anomaly dataclass with the new
    AnomalyResult format. Preserves all information while adding
    confidence scoring and suggested actions.

    Args:
        legacy_anomaly: An instance of detector.Anomaly

    Returns:
        Equivalent AnomalyResult with inferred confidence and action.
    """
    severity = severity_from_string(
        getattr(legacy_anomaly, "severity", "medium")
    )

    # Infer confidence from severity and details
    confidence = _infer_confidence(legacy_anomaly)

    # Determine suggested action from severity
    suggested_action = _DEFAULT_ACTIONS.get(severity, "log")

    # Build description from details
    details = getattr(legacy_anomaly, "details", {})
    anomaly_type = getattr(legacy_anomaly, "type", "UNKNOWN")
    description = details.get(
        "description",
        details.get("explanation", f"{anomaly_type} detected"),
    )

    return AnomalyResult(
        type=anomaly_type,
        detected=True,
        confidence=confidence,
        severity=severity,
        description=str(description),
        evidence=dict(details),
        suggested_action=suggested_action,
    )


def _infer_confidence(legacy_anomaly: Any) -> float:
    """Infer confidence score from legacy anomaly data.

    Uses severity and available details to estimate confidence
    on a 0.0-1.0 scale.
    """
    severity_str = getattr(legacy_anomaly, "severity", "medium")
    base_confidence = {
        "low": 0.4,
        "medium": 0.6,
        "high": 0.8,
        "critical": 0.95,
    }.get(severity_str, 0.5)

    details = getattr(legacy_anomaly, "details", {})

    # Boost confidence if specific evidence is present
    if details.get("confidence"):
        try:
            explicit = float(details["confidence"])
            if 0.0 <= explicit <= 1.0:
                return explicit
        except (ValueError, TypeError):
            pass

    if details.get("suspicion_score"):
        try:
            return float(details["suspicion_score"])
        except (ValueError, TypeError):
            pass

    return base_confidence


def build_monitor_result(
    message_id: str,
    sender_id: str,
    receiver_id: str,
    timestamp: str,
    legacy_anomalies: list,
    processing_ms: float,
) -> MonitorResult:
    """Build a MonitorResult from a list of legacy detector.Anomaly objects.

    Args:
        message_id: UUID of the message
        sender_id: Sender agent ID
        receiver_id: Receiver agent ID
        timestamp: ISO timestamp string
        legacy_anomalies: List of detector.Anomaly instances
        processing_ms: Processing time in milliseconds

    Returns:
        Fully populated MonitorResult
    """
    anomaly_results = [
        convert_legacy_anomaly(a) for a in legacy_anomalies
    ]

    detected = [a for a in anomaly_results if a.detected]
    clean = len(detected) == 0

    severities = [a.severity for a in detected]
    highest = max_severity(severities)

    return MonitorResult(
        message_id=message_id,
        sender_id=sender_id,
        receiver_id=receiver_id or "",
        timestamp=timestamp,
        anomalies=anomaly_results,
        clean=clean,
        highest_severity=highest,
        processing_ms=processing_ms,
    )
