"""Smart error recovery system with 3 escalation levels."""

from __future__ import annotations

import time
from dataclasses import dataclass, field
from enum import IntEnum, StrEnum
from typing import Any


class RecoveryLevel(IntEnum):
    """Escalation levels for error recovery."""

    SMART_RETRY = 1  # Try alternative approach
    FALLBACK = 2  # Try alternative tool/source
    ASK_USER = 3  # Present problem + attempts to user


class ErrorCategory(StrEnum):
    """Categories of errors for routing to recovery strategies."""

    NETWORK = "network"
    RATE_LIMIT = "rate_limit"
    AUTH = "auth"
    BLOCKED = "blocked"
    TIMEOUT = "timeout"
    PARSE = "parse"
    NOT_FOUND = "not_found"
    LLM_ERROR = "llm_error"
    TOOL_ERROR = "tool_error"
    UNKNOWN = "unknown"


@dataclass
class RecoveryAttempt:
    """A single recovery attempt."""

    level: RecoveryLevel
    strategy: str
    description: str
    succeeded: bool = False
    error: str = ""
    timestamp: float = field(default_factory=time.time)


@dataclass
class RecoveryContext:
    """Context for a recovery operation."""

    original_error: str
    error_category: ErrorCategory
    tool_name: str = ""
    tool_input: dict[str, Any] = field(default_factory=dict)
    attempts: list[RecoveryAttempt] = field(default_factory=list)
    resolved: bool = False
    resolution: str = ""


# Mapping of error keywords to categories
_ERROR_PATTERNS: dict[ErrorCategory, list[str]] = {
    ErrorCategory.RATE_LIMIT: [
        "rate limit",
        "too many requests",
        "429",
        "throttl",
        "quota exceeded",
    ],
    ErrorCategory.TIMEOUT: [
        "timeout",
        "timed out",
        "deadline exceeded",
        "connection timed",
    ],
    ErrorCategory.BLOCKED: [
        "blocked",
        "forbidden",
        "403",
        "captcha",
        "cloudflare",
        "access denied",
    ],
    ErrorCategory.AUTH: [
        "unauthorized",
        "401",
        "authentication",
        "invalid api key",
        "invalid token",
    ],
    ErrorCategory.NETWORK: [
        "connection refused",
        "connection reset",
        "dns",
        "network",
        "unreachable",
    ],
    ErrorCategory.NOT_FOUND: [
        "not found",
        "404",
        "no results",
        "does not exist",
    ],
    ErrorCategory.PARSE: [
        "parse error",
        "json",
        "invalid format",
        "unexpected token",
        "decode",
    ],
    ErrorCategory.LLM_ERROR: [
        "model",
        "completion",
        "context length",
        "max tokens",
        "content filter",
    ],
}

# Recovery strategies per error category
_RECOVERY_STRATEGIES: dict[ErrorCategory, list[dict[str, str]]] = {
    ErrorCategory.RATE_LIMIT: [
        {"strategy": "backoff", "description": "Wait and retry with exponential backoff"},
        {"strategy": "alt_provider", "description": "Try alternative API provider"},
        {"strategy": "queue", "description": "Queue task for later execution"},
    ],
    ErrorCategory.BLOCKED: [
        {"strategy": "alt_user_agent", "description": "Retry with different user agent"},
        {"strategy": "mobile_site", "description": "Try mobile version of the site"},
        {"strategy": "alt_source", "description": "Try alternative data source"},
    ],
    ErrorCategory.TIMEOUT: [
        {"strategy": "retry", "description": "Retry the request"},
        {"strategy": "reduce_scope", "description": "Reduce request scope/size"},
        {"strategy": "alt_provider", "description": "Try alternative provider"},
    ],
    ErrorCategory.AUTH: [
        {"strategy": "check_credentials", "description": "Verify API credentials"},
        {"strategy": "alt_provider", "description": "Try provider that doesn't need auth"},
    ],
    ErrorCategory.NETWORK: [
        {"strategy": "retry", "description": "Retry the connection"},
        {"strategy": "alt_endpoint", "description": "Try alternative endpoint"},
    ],
    ErrorCategory.NOT_FOUND: [
        {"strategy": "broaden_search", "description": "Broaden search terms"},
        {"strategy": "alt_source", "description": "Try alternative data source"},
    ],
    ErrorCategory.PARSE: [
        {"strategy": "retry", "description": "Retry and reparse"},
        {"strategy": "alt_format", "description": "Request alternative format"},
    ],
    ErrorCategory.LLM_ERROR: [
        {"strategy": "alt_model", "description": "Route to different LLM model"},
        {"strategy": "reduce_context", "description": "Reduce context window size"},
        {"strategy": "alt_provider", "description": "Try alternative LLM provider"},
    ],
    ErrorCategory.UNKNOWN: [
        {"strategy": "retry", "description": "Retry the operation"},
    ],
}

# Fallback tool mappings
_FALLBACK_TOOLS: dict[str, list[str]] = {
    "web_search": ["web_search_brave", "web_search_serpapi", "web_search_tavily"],
    "browse_page": ["extract_text", "fetch_url"],
    "web_search_tavily": ["web_search_brave", "web_search"],
    "web_search_brave": ["web_search_tavily", "web_search"],
}


def classify_error(error_message: str) -> ErrorCategory:
    """Classify an error message into a category."""
    lower = error_message.lower()
    for category, patterns in _ERROR_PATTERNS.items():
        if any(pattern in lower for pattern in patterns):
            return category
    return ErrorCategory.UNKNOWN


class RecoveryEngine:
    """Manages error recovery with 3 escalation levels.

    Level 1 (Smart Retry): Analyze the error, try a modified approach.
    Level 2 (Fallback): Switch to alternative tools/sources.
    Level 3 (Ask User): Present the problem and ask for guidance.
    """

    def __init__(self, max_retries: int = 3) -> None:
        self._max_retries = max_retries
        self._history: list[RecoveryContext] = []

    def create_context(
        self,
        error_message: str,
        tool_name: str = "",
        tool_input: dict[str, Any] | None = None,
    ) -> RecoveryContext:
        """Create a recovery context for an error."""
        category = classify_error(error_message)
        ctx = RecoveryContext(
            original_error=error_message,
            error_category=category,
            tool_name=tool_name,
            tool_input=tool_input or {},
        )
        self._history.append(ctx)
        return ctx

    def get_recovery_strategies(
        self,
        ctx: RecoveryContext,
    ) -> list[dict[str, str]]:
        """Get available recovery strategies for a context."""
        strategies = _RECOVERY_STRATEGIES.get(ctx.error_category, [])
        # Filter out already-attempted strategies
        attempted = {a.strategy for a in ctx.attempts}
        return [s for s in strategies if s["strategy"] not in attempted]

    def get_current_level(self, ctx: RecoveryContext) -> RecoveryLevel:
        """Determine current recovery level based on attempts."""
        attempt_count = len(ctx.attempts)
        if attempt_count == 0:
            return RecoveryLevel.SMART_RETRY
        if attempt_count < self._max_retries:
            return RecoveryLevel.FALLBACK
        return RecoveryLevel.ASK_USER

    def get_fallback_tools(self, tool_name: str) -> list[str]:
        """Get fallback tool alternatives for a given tool."""
        return _FALLBACK_TOOLS.get(tool_name, [])

    def record_attempt(
        self,
        ctx: RecoveryContext,
        strategy: str,
        description: str,
        succeeded: bool,
        error: str = "",
    ) -> RecoveryAttempt:
        """Record a recovery attempt."""
        level = self.get_current_level(ctx)
        attempt = RecoveryAttempt(
            level=level,
            strategy=strategy,
            description=description,
            succeeded=succeeded,
            error=error,
        )
        ctx.attempts.append(attempt)
        if succeeded:
            ctx.resolved = True
            ctx.resolution = description
        return attempt

    def should_ask_user(self, ctx: RecoveryContext) -> bool:
        """Check if we should escalate to asking the user."""
        return self.get_current_level(ctx) == RecoveryLevel.ASK_USER

    def build_user_message(self, ctx: RecoveryContext) -> str:
        """Build a message explaining the problem to the user."""
        lines = [
            f"I encountered an error: {ctx.original_error}",
            f"Error type: {ctx.error_category.value}",
        ]

        if ctx.tool_name:
            lines.append(f"While using: {ctx.tool_name}")

        if ctx.attempts:
            lines.append("\nRecovery attempts:")
            for i, attempt in enumerate(ctx.attempts, 1):
                status = "succeeded" if attempt.succeeded else "failed"
                lines.append(f"  {i}. {attempt.description} — {status}")
                if attempt.error:
                    lines.append(f"     Error: {attempt.error}")

        lines.append("\nHow would you like me to proceed?")
        return "\n".join(lines)

    @property
    def history(self) -> list[RecoveryContext]:
        return self._history
