"""Timeout prediction based on timing patterns.

Predicts which operations are at risk of timing out based on
observed latencies and configured timeouts.
"""

from __future__ import annotations

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

if TYPE_CHECKING:
    from autodebug.detection.transcript_analyzer import Transcript


class RiskLevel(str, Enum):
    """Timeout risk level."""

    CRITICAL = "critical"  # >90% of timeout
    HIGH = "high"  # >70% of timeout
    MEDIUM = "medium"  # >50% of timeout
    LOW = "low"  # <50% of timeout


@dataclass
class TimeoutRisk:
    """A predicted timeout risk for an operation."""

    operation_type: str  # "http", "sql", "redis"
    operation: Any  # The record
    duration_ms: float
    timeout_ms: float
    risk_level: RiskLevel
    headroom_ms: float  # Time left before timeout
    recommendation: str
    evidence: Dict[str, Any] = field(default_factory=dict)

    @property
    def headroom_percent(self) -> float:
        """Percentage of timeout remaining."""
        if self.timeout_ms == 0:
            return 0.0
        return ((self.timeout_ms - self.duration_ms) / self.timeout_ms) * 100

    @property
    def usage_percent(self) -> float:
        """Percentage of timeout used."""
        if self.timeout_ms == 0:
            return 100.0
        return (self.duration_ms / self.timeout_ms) * 100

    def __str__(self) -> str:
        return (
            f"[{self.risk_level.value.upper()}] {self.operation_type} "
            f"used {self.usage_percent:.0f}% of {self.timeout_ms:.0f}ms timeout"
        )


@dataclass
class TimeoutPrediction:
    """Summary of timeout predictions for a transcript."""

    risks: List[TimeoutRisk]
    total_operations: int
    at_risk_operations: int
    highest_risk_level: Optional[RiskLevel]
    recommendations: List[str]

    @property
    def risk_ratio(self) -> float:
        """Ratio of at-risk operations."""
        if self.total_operations == 0:
            return 0.0
        return self.at_risk_operations / self.total_operations

    def summary(self) -> str:
        """Get a one-line summary."""
        if not self.risks:
            return "No timeout risks detected"
        return (
            f"{self.at_risk_operations}/{self.total_operations} operations "
            f"at timeout risk (highest: {self.highest_risk_level.value if self.highest_risk_level else 'none'})"
        )


class TimeoutPredictor:
    """Predict timeout risks based on operation timing.

    Analyzes captured timing data to identify operations that
    are at risk of timing out, and provides recommendations.
    """

    # Default timeout values (in milliseconds)
    DEFAULT_TIMEOUTS = {
        "http": 30000,  # 30 seconds
        "sql": 30000,  # 30 seconds
        "redis": 5000,  # 5 seconds
    }

    # Risk thresholds (as ratio of timeout)
    RISK_THRESHOLDS = {
        RiskLevel.CRITICAL: 0.90,
        RiskLevel.HIGH: 0.70,
        RiskLevel.MEDIUM: 0.50,
    }

    def __init__(
        self,
        timeouts: Optional[Dict[str, float]] = None,
        risk_thresholds: Optional[Dict[RiskLevel, float]] = None,
    ):
        """Initialize the timeout predictor.

        Args:
            timeouts: Custom timeout values by operation type
            risk_thresholds: Custom risk thresholds
        """
        self.timeouts = timeouts or self.DEFAULT_TIMEOUTS.copy()
        self.risk_thresholds = risk_thresholds or self.RISK_THRESHOLDS.copy()

    def predict(self, transcript: Transcript) -> TimeoutPrediction:
        """Predict timeout risks for all operations.

        Args:
            transcript: I/O transcript with timing data

        Returns:
            TimeoutPrediction with all risks
        """
        risks: List[TimeoutRisk] = []
        total_operations = 0

        # Analyze HTTP requests
        for record in transcript.http_records:
            total_operations += 1
            risk = self._check_http_timeout(record)
            if risk:
                risks.append(risk)

        # Analyze SQL queries
        for record in transcript.sql_records:
            total_operations += 1
            risk = self._check_sql_timeout(record)
            if risk:
                risks.append(risk)

        # Analyze Redis commands if present
        redis_records = getattr(transcript, "redis_records", [])
        for record in redis_records:
            total_operations += 1
            risk = self._check_redis_timeout(record)
            if risk:
                risks.append(risk)

        # Sort by risk level
        risks.sort(key=lambda r: -self._risk_level_order(r.risk_level))

        # Determine highest risk
        highest = risks[0].risk_level if risks else None

        # Generate recommendations
        recommendations = self._generate_recommendations(risks)

        return TimeoutPrediction(
            risks=risks,
            total_operations=total_operations,
            at_risk_operations=len(risks),
            highest_risk_level=highest,
            recommendations=recommendations,
        )

    def predict_timeout_risk(self, transcript: Transcript) -> List[TimeoutRisk]:
        """Get list of timeout risks (simplified API).

        Args:
            transcript: I/O transcript

        Returns:
            List of timeout risks
        """
        return self.predict(transcript).risks

    def _check_http_timeout(self, record: Any) -> Optional[TimeoutRisk]:
        """Check HTTP request for timeout risk.

        Args:
            record: HTTP record

        Returns:
            TimeoutRisk if at risk, None otherwise
        """
        duration = getattr(record, "duration_ms", None)
        if duration is None or duration <= 0:
            return None

        timeout = self._get_timeout_for_http(record)
        risk_level = self._calculate_risk_level(duration, timeout)

        if risk_level is None:
            return None

        return TimeoutRisk(
            operation_type="http",
            operation=record,
            duration_ms=duration,
            timeout_ms=timeout,
            risk_level=risk_level,
            headroom_ms=timeout - duration,
            recommendation=self._get_http_recommendation(duration, timeout, risk_level),
            evidence={
                "url": getattr(record, "url_canonical", "unknown"),
                "status": getattr(record, "status", None),
                "method": getattr(record, "method", "GET"),
            },
        )

    def _check_sql_timeout(self, record: Any) -> Optional[TimeoutRisk]:
        """Check SQL query for timeout risk.

        Args:
            record: SQL record

        Returns:
            TimeoutRisk if at risk, None otherwise
        """
        duration = getattr(record, "duration_ms", None)
        if duration is None or duration <= 0:
            return None

        timeout = self.timeouts.get("sql", self.DEFAULT_TIMEOUTS["sql"])
        risk_level = self._calculate_risk_level(duration, timeout)

        if risk_level is None:
            return None

        query = getattr(record, "sql_canonical", "")
        query_preview = query[:100] + "..." if len(query) > 100 else query

        return TimeoutRisk(
            operation_type="sql",
            operation=record,
            duration_ms=duration,
            timeout_ms=timeout,
            risk_level=risk_level,
            headroom_ms=timeout - duration,
            recommendation=self._get_sql_recommendation(duration, timeout, risk_level),
            evidence={"query": query_preview},
        )

    def _check_redis_timeout(self, record: Any) -> Optional[TimeoutRisk]:
        """Check Redis command for timeout risk.

        Args:
            record: Redis record

        Returns:
            TimeoutRisk if at risk, None otherwise
        """
        duration = getattr(record, "duration_ms", None)
        if duration is None or duration <= 0:
            return None

        timeout = self.timeouts.get("redis", self.DEFAULT_TIMEOUTS["redis"])
        risk_level = self._calculate_risk_level(duration, timeout)

        if risk_level is None:
            return None

        return TimeoutRisk(
            operation_type="redis",
            operation=record,
            duration_ms=duration,
            timeout_ms=timeout,
            risk_level=risk_level,
            headroom_ms=timeout - duration,
            recommendation=self._get_redis_recommendation(duration, timeout, risk_level),
            evidence={"command": getattr(record, "command", "unknown")},
        )

    def _get_timeout_for_http(self, record: Any) -> float:
        """Determine timeout for HTTP request.

        May vary by URL or other factors.

        Args:
            record: HTTP record

        Returns:
            Timeout in milliseconds
        """
        # Could be customized based on URL patterns
        return self.timeouts.get("http", self.DEFAULT_TIMEOUTS["http"])

    def _calculate_risk_level(
        self,
        duration: float,
        timeout: float,
    ) -> Optional[RiskLevel]:
        """Calculate risk level based on duration vs timeout.

        Args:
            duration: Operation duration in ms
            timeout: Timeout value in ms

        Returns:
            RiskLevel or None if not at risk
        """
        if timeout <= 0:
            return None

        ratio = duration / timeout

        for level in [RiskLevel.CRITICAL, RiskLevel.HIGH, RiskLevel.MEDIUM]:
            threshold = self.risk_thresholds[level]
            if ratio >= threshold:
                return level

        return None  # Low risk, don't report

    def _risk_level_order(self, level: RiskLevel) -> int:
        """Get numeric order for risk level (for sorting)."""
        return {
            RiskLevel.CRITICAL: 3,
            RiskLevel.HIGH: 2,
            RiskLevel.MEDIUM: 1,
            RiskLevel.LOW: 0,
        }.get(level, 0)

    def _get_http_recommendation(
        self,
        duration: float,
        timeout: float,
        risk_level: RiskLevel,
    ) -> str:
        """Get recommendation for HTTP timeout risk.

        Args:
            duration: Actual duration
            timeout: Configured timeout
            risk_level: Risk level

        Returns:
            Recommendation string
        """
        if risk_level == RiskLevel.CRITICAL:
            return (
                f"URGENT: Request used {duration/timeout*100:.0f}% of timeout. "
                "Either increase timeout or add retry with exponential backoff."
            )
        elif risk_level == RiskLevel.HIGH:
            return (
                "Consider adding retry logic with backoff. "
                "Investigate why this API is slow."
            )
        else:
            return "Monitor this endpoint. Consider caching if called frequently."

    def _get_sql_recommendation(
        self,
        duration: float,
        timeout: float,
        risk_level: RiskLevel,
    ) -> str:
        """Get recommendation for SQL timeout risk.

        Args:
            duration: Actual duration
            timeout: Configured timeout
            risk_level: Risk level

        Returns:
            Recommendation string
        """
        if risk_level == RiskLevel.CRITICAL:
            return (
                f"URGENT: Query used {duration/timeout*100:.0f}% of timeout. "
                "Run EXPLAIN, add indexes, or paginate results."
            )
        elif risk_level == RiskLevel.HIGH:
            return (
                "Query is slow. Run EXPLAIN ANALYZE to identify bottlenecks. "
                "Consider adding indexes or rewriting query."
            )
        else:
            return "Consider adding an index if this query runs frequently."

    def _get_redis_recommendation(
        self,
        duration: float,
        timeout: float,
        risk_level: RiskLevel,
    ) -> str:
        """Get recommendation for Redis timeout risk.

        Args:
            duration: Actual duration
            timeout: Configured timeout
            risk_level: Risk level

        Returns:
            Recommendation string
        """
        if risk_level == RiskLevel.CRITICAL:
            return (
                "URGENT: Redis command is very slow. "
                "Check for blocking operations, large values, or network issues."
            )
        else:
            return "Check Redis server load and network latency."

    def _generate_recommendations(self, risks: List[TimeoutRisk]) -> List[str]:
        """Generate overall recommendations.

        Args:
            risks: List of timeout risks

        Returns:
            List of recommendation strings
        """
        recommendations: List[str] = []

        # Count by type and risk
        http_critical = sum(
            1 for r in risks
            if r.operation_type == "http" and r.risk_level == RiskLevel.CRITICAL
        )
        sql_slow = sum(1 for r in risks if r.operation_type == "sql")

        if http_critical > 0:
            recommendations.append(
                f"Add retry logic with exponential backoff for {http_critical} HTTP request(s)"
            )

        if sql_slow > 0:
            recommendations.append(
                f"Optimize {sql_slow} SQL query/queries using EXPLAIN ANALYZE"
            )

        if len(risks) > 5:
            recommendations.append(
                "Consider implementing circuit breaker pattern for external dependencies"
            )

        return recommendations

    def format_report(self, prediction: TimeoutPrediction) -> str:
        """Format prediction as readable report.

        Args:
            prediction: Timeout prediction

        Returns:
            Formatted report string
        """
        if not prediction.risks:
            return "No timeout risks detected."

        lines = [
            "Timeout Risk Analysis",
            "=" * 50,
            prediction.summary(),
            "",
        ]

        # Group by operation type
        by_type: Dict[str, List[TimeoutRisk]] = {}
        for risk in prediction.risks:
            if risk.operation_type not in by_type:
                by_type[risk.operation_type] = []
            by_type[risk.operation_type].append(risk)

        for op_type, type_risks in by_type.items():
            lines.append(f"\n{op_type.upper()} Risks ({len(type_risks)}):")
            for risk in type_risks[:5]:
                lines.append(f"  {risk}")
                lines.append(f"    Headroom: {risk.headroom_ms:.0f}ms ({risk.headroom_percent:.0f}%)")
                lines.append(f"    {risk.recommendation}")

        if prediction.recommendations:
            lines.append("\nOverall Recommendations:")
            for rec in prediction.recommendations:
                lines.append(f"  - {rec}")

        return "\n".join(lines)


# Convenience function
def predict_timeouts(
    transcript: Transcript,
    timeouts: Optional[Dict[str, float]] = None,
) -> TimeoutPrediction:
    """Predict timeout risks for a transcript.

    Args:
        transcript: I/O transcript
        timeouts: Optional custom timeout values

    Returns:
        TimeoutPrediction
    """
    predictor = TimeoutPredictor(timeouts=timeouts)
    return predictor.predict(transcript)
