"""Production Monitor for SAGE Engine.

Provides logging, metrics collection, and alerting for production deployments.
Tracks query patterns, response times, and safety-related events.
"""

import logging
import time
from collections import deque
from dataclasses import dataclass, field
from datetime import datetime, timedelta
from enum import Enum
from typing import Any

from sage.engine.config import MonitorConfig
from sage.engine.router import QueryType, RoutingDecision

logger = logging.getLogger(__name__)


class AlertSeverity(str, Enum):
    """Severity levels for alerts."""

    INFO = "info"
    WARNING = "warning"
    ERROR = "error"
    CRITICAL = "critical"


class AlertType(str, Enum):
    """Types of alerts."""

    SLOW_RESPONSE = "slow_response"
    HIGH_ERROR_RATE = "high_error_rate"
    LOW_CONFIDENCE = "low_confidence"
    ABSTENTION = "abstention"
    ESCALATION = "escalation"
    TOOL_FAILURE = "tool_failure"
    SAFETY_CLASSIFICATION = "safety_classification"


@dataclass
class Alert:
    """An alert triggered by monitoring."""

    alert_type: AlertType
    severity: AlertSeverity
    message: str
    timestamp: datetime = field(default_factory=datetime.now)
    details: dict[str, Any] = field(default_factory=dict)
    acknowledged: bool = False

    def to_dict(self) -> dict[str, Any]:
        """Convert alert to dictionary."""
        return {
            "alert_type": self.alert_type.value,
            "severity": self.severity.value,
            "message": self.message,
            "timestamp": self.timestamp.isoformat(),
            "details": self.details,
            "acknowledged": self.acknowledged,
        }


@dataclass
class QueryMetrics:
    """Metrics for a single query."""

    query_id: str
    query_type: QueryType
    timestamp: datetime
    latency_ms: float
    confidence: float
    tools_used: list[str]
    was_escalated: bool
    was_abstained: bool
    classification: str | None
    error: str | None = None


@dataclass
class AggregateMetrics:
    """Aggregated metrics over a time window."""

    window_start: datetime
    window_end: datetime
    total_queries: int = 0
    queries_by_type: dict[str, int] = field(default_factory=dict)
    avg_latency_ms: float = 0.0
    p95_latency_ms: float = 0.0
    p99_latency_ms: float = 0.0
    avg_confidence: float = 0.0
    error_count: int = 0
    error_rate: float = 0.0
    abstention_count: int = 0
    escalation_count: int = 0
    tool_usage: dict[str, int] = field(default_factory=dict)
    classification_counts: dict[str, int] = field(default_factory=dict)


class ProductionMonitor:
    """Monitors SAGE Engine in production.

    Collects metrics, triggers alerts, and provides observability
    for production deployments.

    Example:
        monitor = ProductionMonitor()

        # Log a query
        monitor.log_query(query, routing_decision)

        # Log response
        monitor.log_response(response, latency_ms=150.0)

        # Check for alerts
        alerts = monitor.check_alerts(response)
        for alert in alerts:
            print(f"{alert.severity}: {alert.message}")

        # Get metrics
        metrics = monitor.get_metrics()
    """

    def __init__(self, config: MonitorConfig | None = None):
        """Initialize the production monitor.

        Args:
            config: Monitor configuration. Uses defaults if not provided.
        """
        self.config = config or MonitorConfig()

        # Query history for metrics calculation
        self._query_history: deque[QueryMetrics] = deque(maxlen=10000)
        self._alert_history: deque[Alert] = deque(maxlen=1000)

        # Running statistics
        self._total_queries = 0
        self._total_errors = 0
        self._start_time = datetime.now()

        # Current request context
        self._current_query_id: str | None = None
        self._current_query_start: float | None = None

    def start_query(self, query_id: str) -> None:
        """Mark the start of query processing.

        Args:
            query_id: Unique identifier for the query.
        """
        self._current_query_id = query_id
        self._current_query_start = time.time()

        if self.config.log_queries:
            logger.info(f"Query started: {query_id}")

    def log_query(
        self,
        query: str,
        routing: RoutingDecision,
        user_id: str = "anonymous",
    ) -> None:
        """Log an incoming query and its routing decision.

        Args:
            query: The user's query string.
            routing: Routing decision from QueryRouter.
            user_id: User identifier.
        """
        if not self.config.log_queries:
            return

        logger.info(
            f"Query routed: type={routing.query_type.value}, "
            f"complexity={routing.complexity.value}, "
            f"tools={routing.required_tools}, "
            f"user={user_id}"
        )

        # Log routing reasons at debug level
        for reason in routing.routing_reasons:
            logger.debug(f"  Routing reason: {reason}")

    def log_response(
        self,
        response: Any,
        query_id: str | None = None,
        latency_ms: float | None = None,
    ) -> None:
        """Log a response and collect metrics.

        Args:
            response: The SAGEResponse object.
            query_id: Query identifier (uses current if not provided).
            latency_ms: Response latency (calculated if not provided).
        """
        query_id = query_id or self._current_query_id or "unknown"

        # Calculate latency if not provided
        if latency_ms is None and self._current_query_start:
            latency_ms = (time.time() - self._current_query_start) * 1000
        latency_ms = latency_ms or 0.0

        # Extract metrics from response
        confidence = getattr(response, "confidence", 0.0)
        was_escalated = getattr(response, "escalation_request_id", None) is not None
        was_abstained = False
        if hasattr(response, "abstention") and response.abstention:
            was_abstained = response.abstention.should_abstain

        classification = None
        if hasattr(response, "classification") and response.classification:
            classification = response.classification.classification.value

        tools_used = []
        if hasattr(response, "tool_results"):
            tools_used = [r.tool_name for r in (response.tool_results or [])]

        error = getattr(response, "error", None)

        # Record metrics
        metrics = QueryMetrics(
            query_id=query_id,
            query_type=getattr(response, "query_type", QueryType.KNOWLEDGE),
            timestamp=datetime.now(),
            latency_ms=latency_ms,
            confidence=confidence,
            tools_used=tools_used,
            was_escalated=was_escalated,
            was_abstained=was_abstained,
            classification=classification,
            error=error,
        )

        self._query_history.append(metrics)
        self._total_queries += 1

        if error:
            self._total_errors += 1

        if self.config.log_responses:
            logger.info(
                f"Query completed: id={query_id}, latency={latency_ms:.0f}ms, "
                f"confidence={confidence:.2f}, classification={classification}"
            )

        # Reset current query context
        self._current_query_id = None
        self._current_query_start = None

    def log_tool_execution(
        self,
        tool_name: str,
        success: bool,
        execution_time_ms: float,
        error: str | None = None,
    ) -> None:
        """Log a tool execution.

        Args:
            tool_name: Name of the tool.
            success: Whether execution succeeded.
            execution_time_ms: Execution time in milliseconds.
            error: Error message if failed.
        """
        if not self.config.log_tool_executions:
            return

        if success:
            logger.info(f"Tool {tool_name} completed in {execution_time_ms:.0f}ms")
        else:
            logger.warning(f"Tool {tool_name} failed: {error}")

    def check_alerts(self, response: Any) -> list[Alert]:
        """Check response for alert conditions.

        Args:
            response: The SAGEResponse object.

        Returns:
            List of triggered alerts.
        """
        alerts = []

        # Check response time
        latency_ms = getattr(response, "latency_ms", 0.0)
        if latency_ms > self.config.response_time_alert_ms:
            alerts.append(
                Alert(
                    alert_type=AlertType.SLOW_RESPONSE,
                    severity=AlertSeverity.WARNING,
                    message=f"Slow response: {latency_ms:.0f}ms > {self.config.response_time_alert_ms:.0f}ms threshold",
                    details={"latency_ms": latency_ms},
                )
            )

        # Check confidence
        confidence = getattr(response, "confidence", 1.0)
        if confidence < self.config.low_confidence_alert_threshold:
            alerts.append(
                Alert(
                    alert_type=AlertType.LOW_CONFIDENCE,
                    severity=AlertSeverity.WARNING,
                    message=f"Low confidence response: {confidence:.2f}",
                    details={"confidence": confidence},
                )
            )

        # Check abstention
        if hasattr(response, "abstention") and response.abstention:
            if response.abstention.should_abstain:
                triggers = [t.value for t in response.abstention.triggers]
                alerts.append(
                    Alert(
                        alert_type=AlertType.ABSTENTION,
                        severity=AlertSeverity.INFO,
                        message=f"Query abstained: {response.abstention.reason}",
                        details={"triggers": triggers},
                    )
                )

        # Check escalation
        if getattr(response, "escalation_request_id", None):
            priority = "unknown"
            if hasattr(response, "escalation") and response.escalation:
                priority = response.escalation.priority.value
            alerts.append(
                Alert(
                    alert_type=AlertType.ESCALATION,
                    severity=AlertSeverity.WARNING,
                    message=f"Response escalated for review (priority: {priority})",
                    details={
                        "request_id": response.escalation_request_id,
                        "priority": priority,
                    },
                )
            )

        # Check classification
        if hasattr(response, "classification") and response.classification:
            classification = response.classification.classification.value
            if classification == "red":
                alerts.append(
                    Alert(
                        alert_type=AlertType.SAFETY_CLASSIFICATION,
                        severity=AlertSeverity.ERROR,
                        message="Response classified as RED - requires review",
                        details={
                            "classification": classification,
                            "reasons": response.classification.reasons,
                        },
                    )
                )
            elif classification == "yellow":
                alerts.append(
                    Alert(
                        alert_type=AlertType.SAFETY_CLASSIFICATION,
                        severity=AlertSeverity.WARNING,
                        message="Response classified as YELLOW - review recommended",
                        details={
                            "classification": classification,
                            "reasons": response.classification.reasons,
                        },
                    )
                )

        # Check error rate
        error_rate = self._calculate_error_rate()
        if error_rate > self.config.error_rate_alert_threshold:
            alerts.append(
                Alert(
                    alert_type=AlertType.HIGH_ERROR_RATE,
                    severity=AlertSeverity.ERROR,
                    message=f"High error rate: {error_rate:.1%}",
                    details={"error_rate": error_rate},
                )
            )

        # Store alerts
        for alert in alerts:
            self._alert_history.append(alert)
            self._log_alert(alert)

        return alerts

    def _log_alert(self, alert: Alert) -> None:
        """Log an alert at appropriate level.

        Args:
            alert: Alert to log.
        """
        log_method = {
            AlertSeverity.INFO: logger.info,
            AlertSeverity.WARNING: logger.warning,
            AlertSeverity.ERROR: logger.error,
            AlertSeverity.CRITICAL: logger.critical,
        }.get(alert.severity, logger.info)

        log_method(f"[ALERT:{alert.alert_type.value}] {alert.message}")

    def _calculate_error_rate(self, window_minutes: int = 5) -> float:
        """Calculate recent error rate.

        Args:
            window_minutes: Time window in minutes.

        Returns:
            Error rate as fraction (0.0 to 1.0).
        """
        cutoff = datetime.now() - timedelta(minutes=window_minutes)
        recent = [q for q in self._query_history if q.timestamp > cutoff]

        if not recent:
            return 0.0

        errors = sum(1 for q in recent if q.error is not None)
        return errors / len(recent)

    def get_metrics(
        self, window_minutes: int = 60
    ) -> AggregateMetrics:
        """Get aggregated metrics for a time window.

        Args:
            window_minutes: Time window in minutes.

        Returns:
            AggregateMetrics for the window.
        """
        window_end = datetime.now()
        window_start = window_end - timedelta(minutes=window_minutes)

        recent = [q for q in self._query_history if q.timestamp > window_start]

        metrics = AggregateMetrics(
            window_start=window_start,
            window_end=window_end,
            total_queries=len(recent),
        )

        if not recent:
            return metrics

        # Count by type
        for query in recent:
            type_name = query.query_type.value
            metrics.queries_by_type[type_name] = (
                metrics.queries_by_type.get(type_name, 0) + 1
            )

        # Latency statistics
        latencies = sorted(q.latency_ms for q in recent)
        metrics.avg_latency_ms = sum(latencies) / len(latencies)
        metrics.p95_latency_ms = latencies[int(len(latencies) * 0.95)] if latencies else 0
        metrics.p99_latency_ms = latencies[int(len(latencies) * 0.99)] if latencies else 0

        # Confidence
        confidences = [q.confidence for q in recent if q.confidence > 0]
        metrics.avg_confidence = sum(confidences) / len(confidences) if confidences else 0

        # Error metrics
        metrics.error_count = sum(1 for q in recent if q.error)
        metrics.error_rate = metrics.error_count / len(recent)

        # Abstention/escalation counts
        metrics.abstention_count = sum(1 for q in recent if q.was_abstained)
        metrics.escalation_count = sum(1 for q in recent if q.was_escalated)

        # Tool usage
        for query in recent:
            for tool in query.tools_used:
                metrics.tool_usage[tool] = metrics.tool_usage.get(tool, 0) + 1

        # Classification counts
        for query in recent:
            if query.classification:
                metrics.classification_counts[query.classification] = (
                    metrics.classification_counts.get(query.classification, 0) + 1
                )

        return metrics

    def get_recent_alerts(
        self,
        count: int = 100,
        severity: AlertSeverity | None = None,
    ) -> list[Alert]:
        """Get recent alerts.

        Args:
            count: Maximum number of alerts to return.
            severity: Filter by severity level.

        Returns:
            List of recent alerts.
        """
        alerts = list(self._alert_history)

        if severity:
            alerts = [a for a in alerts if a.severity == severity]

        return alerts[-count:]

    def acknowledge_alert(self, alert: Alert) -> None:
        """Mark an alert as acknowledged.

        Args:
            alert: Alert to acknowledge.
        """
        alert.acknowledged = True
        logger.info(f"Alert acknowledged: {alert.alert_type.value}")

    def export_metrics(self) -> dict[str, Any]:
        """Export current metrics for external systems.

        Returns:
            Dictionary of metrics suitable for export.
        """
        metrics = self.get_metrics()
        uptime = datetime.now() - self._start_time

        return {
            f"{self.config.metrics_prefix}_total_queries": self._total_queries,
            f"{self.config.metrics_prefix}_total_errors": self._total_errors,
            f"{self.config.metrics_prefix}_uptime_seconds": uptime.total_seconds(),
            f"{self.config.metrics_prefix}_avg_latency_ms": metrics.avg_latency_ms,
            f"{self.config.metrics_prefix}_p95_latency_ms": metrics.p95_latency_ms,
            f"{self.config.metrics_prefix}_p99_latency_ms": metrics.p99_latency_ms,
            f"{self.config.metrics_prefix}_avg_confidence": metrics.avg_confidence,
            f"{self.config.metrics_prefix}_error_rate": metrics.error_rate,
            f"{self.config.metrics_prefix}_abstention_count": metrics.abstention_count,
            f"{self.config.metrics_prefix}_escalation_count": metrics.escalation_count,
        }
