"""
ErrorReporter - Main error categorization and handling logic
"""

import re
from enum import Enum
from typing import Any, Dict, Optional

from loguru import logger


class ErrorCategory(Enum):
    """Categories of errors that can occur during research"""

    CONNECTION_ERROR = "connection_error"
    MODEL_ERROR = "model_error"
    SEARCH_ERROR = "search_error"
    SYNTHESIS_ERROR = "synthesis_error"
    FILE_ERROR = "file_error"
    RATE_LIMIT_ERROR = "rate_limit_error"
    UNKNOWN_ERROR = "unknown_error"


class ErrorReporter:
    """
    Analyzes and categorizes errors to provide better user feedback
    """

    def __init__(self):
        self.error_patterns = {
            ErrorCategory.CONNECTION_ERROR: [
                r"POST predict.*EOF",
                r"Connection refused",
                r"timeout",
                r"Connection.*failed",
                r"HTTP error \d+",
                r"network.*error",
                r"\[Errno 111\]",
                r"host\.docker\.internal",
                r"host.*localhost.*Docker",
                r"127\.0\.0\.1.*Docker",
                r"localhost.*1234.*Docker",
                r"LM.*Studio.*Docker.*Mac",
                # OpenAI-compatible endpoint tokens (#3878)
                r"Error type: openai_connection_refused",
                r"Error type: openai_timeout",
            ],
            ErrorCategory.MODEL_ERROR: [
                r"Model.*not found",
                r"Invalid.*model",
                r"Ollama.*not available",
                r"API key.*invalid",
                r"Authentication.*error",
                r"max_workers must be greater than 0",
                r"TypeError.*Context.*Size",
                r"'<' not supported between instances of .* and 'NoneType'",
                r"No auth credentials found",
                r"401.*API key",
                # OpenAI-compatible endpoint tokens (#3878)
                r"Error type: openai_auth",
                r"Error type: openai_permission_denied",
                r"Error type: openai_model_not_found",
                r"Error type: openai_bad_request",
                r"Error type: openai_unknown",
            ],
            ErrorCategory.RATE_LIMIT_ERROR: [
                r"429.*resource.*exhausted",
                r"429.*too many requests",
                r"rate limit",
                r"rate_limit",
                r"ratelimit",
                r"quota.*exceeded",
                r"resource.*exhausted.*quota",
                r"threshold.*requests",
                r"LLM rate limit",
                r"API rate limit",
                r"maximum.*requests.*minute",
                r"maximum.*requests.*hour",
                # OpenAI-compatible endpoint token (#3878 follow-up)
                r"Error type: openai_rate_limit",
            ],
            ErrorCategory.SEARCH_ERROR: [
                r"Search.*failed",
                r"No search results",
                r"Search engine.*error",
                r"The search is longer than 256 characters",
                r"Failed to create search engine",
                r"search engine.*could not be found",
                r"GitHub API error",
            ],
            ErrorCategory.SYNTHESIS_ERROR: [
                r"Error.*synthesis",
                r"Failed.*generate",
                r"Synthesis.*timeout",
                r"detailed.*report.*stuck",
                r"report.*taking.*long",
                r"progress.*100.*stuck",
            ],
            ErrorCategory.FILE_ERROR: [
                r"Permission denied",
                r"File.*not found",
                r"Cannot write.*file",
                r"Disk.*full",
                r"No module named.*local_deep_research",
                r"HTTP error 404.*research results",
                r"Attempt to write readonly database",
                r"database is locked",
                r"database table is locked",
            ],
        }

    def categorize_error(self, error_message: str) -> ErrorCategory:
        """
        Categorize an error based on its message

        Args:
            error_message: The error message to categorize

        Returns:
            ErrorCategory: The categorized error type
        """
        error_message = str(error_message).lower()

        for category, patterns in self.error_patterns.items():
            for pattern in patterns:
                if re.search(pattern.lower(), error_message):
                    logger.debug(
                        f"Categorized error as {category.value}: {pattern}"
                    )
                    return category

        return ErrorCategory.UNKNOWN_ERROR

    def get_user_friendly_title(self, category: ErrorCategory) -> str:
        """
        Get a user-friendly title for an error category

        Args:
            category: The error category

        Returns:
            str: User-friendly title
        """
        titles = {
            ErrorCategory.CONNECTION_ERROR: "Connection Issue",
            ErrorCategory.MODEL_ERROR: "LLM Service Error",
            ErrorCategory.SEARCH_ERROR: "Search Service Error",
            ErrorCategory.SYNTHESIS_ERROR: "Report Generation Error",
            ErrorCategory.FILE_ERROR: "File System Error",
            ErrorCategory.RATE_LIMIT_ERROR: "API Rate Limit Exceeded",
            ErrorCategory.UNKNOWN_ERROR: "Unexpected Error",
        }
        return titles.get(category, "Error")

    def get_suggested_actions(self, category: ErrorCategory) -> list:
        """
        Get suggested actions for resolving an error

        Args:
            category: The error category

        Returns:
            list: List of suggested actions
        """
        suggestions = {
            ErrorCategory.CONNECTION_ERROR: [
                "Check if the LLM service (Ollama/LM Studio) is running",
                "Verify network connectivity",
                "Try switching to a different model provider",
                "Check the service logs for more details",
            ],
            ErrorCategory.MODEL_ERROR: [
                "Verify the model name is correct",
                "Check if the model is downloaded and available",
                "Validate API keys if using external services",
                "Try switching to a different model",
            ],
            ErrorCategory.SEARCH_ERROR: [
                "Check internet connectivity",
                "Try reducing the number of search results",
                "Wait a moment and try again",
                "Check if search service is configured correctly",
                "For local documents: ensure the path is absolute and folder exists",
                "Try a different search engine if one is failing",
            ],
            ErrorCategory.SYNTHESIS_ERROR: [
                "The research data was collected successfully",
                "Try switching to a different model for report generation",
                "Check the partial results below",
                "Review the detailed logs for more information",
            ],
            ErrorCategory.FILE_ERROR: [
                "Check disk space availability",
                "Verify write permissions",
                "Try changing the output directory",
                "Restart the application",
            ],
            ErrorCategory.RATE_LIMIT_ERROR: [
                "The API has reached its rate limit",
                "Enable LLM Rate Limiting in Settings → Rate Limiting → Enable LLM Rate Limiting",
                "Once enabled, the system will automatically learn and adapt to API limits",
                "Consider upgrading to a paid API plan for higher limits",
                "Try using a different model temporarily",
            ],
            ErrorCategory.UNKNOWN_ERROR: [
                "Check the detailed logs below for more information",
                "Try running the research again",
                "Report this issue if it persists",
                "Contact support with the error details",
            ],
        }
        return suggestions.get(category, ["Check the logs for more details"])

    def analyze_error(
        self, error_message: str, context: Optional[Dict[str, Any]] = None
    ) -> Dict[str, Any]:
        """
        Perform comprehensive error analysis

        Args:
            error_message: The error message to analyze
            context: Optional context information

        Returns:
            dict: Comprehensive error analysis
        """
        category = self.categorize_error(error_message)

        analysis = {
            "category": category,
            "title": self.get_user_friendly_title(category),
            "original_error": error_message,
            "suggestions": self.get_suggested_actions(category),
            "severity": self._determine_severity(category),
            "recoverable": self._is_recoverable(category),
        }

        # Add context-specific information
        if context:
            analysis["context"] = context
            analysis["has_partial_results"] = bool(
                context.get("findings")
                or context.get("current_knowledge")
                or context.get("search_results")
            )

        # Send notifications for specific error types
        self._send_error_notifications(category, error_message, context)

        return analysis

    def _send_error_notifications(
        self,
        category: ErrorCategory,
        error_message: str,
        context: Optional[Dict[str, Any]] = None,
    ) -> None:
        """
        Send notifications for specific error categories.

        Args:
            category: Error category
            error_message: Error message
            context: Optional context information
        """
        try:
            # Only send notifications for AUTH and QUOTA errors
            if category not in [
                ErrorCategory.MODEL_ERROR,
                ErrorCategory.RATE_LIMIT_ERROR,
            ]:
                return

            # Try to get username from context
            username = None
            if context:
                username = context.get("username")

            # Don't send notifications if we can't determine user
            if not username:
                logger.debug(
                    "No username in context, skipping error notification"
                )
                return

            from ..notifications.manager import NotificationManager
            from ..notifications import EventType
            from ..database.session_context import get_user_db_session

            # Get settings snapshot for notification
            with get_user_db_session(username) as session:
                from ..settings import SettingsManager

                settings_manager = SettingsManager(session)
                settings_snapshot = settings_manager.get_settings_snapshot()

            notification_manager = NotificationManager(
                settings_snapshot=settings_snapshot, user_id=username
            )

            # Determine event type and build context
            if category == ErrorCategory.MODEL_ERROR:
                # Check if it's an auth error specifically
                error_str = error_message.lower()
                if any(
                    pattern in error_str
                    for pattern in [
                        "api key",
                        "authentication",
                        "401",
                        "unauthorized",
                    ]
                ):
                    event_type = EventType.AUTH_ISSUE
                    notification_context = {
                        "service": self._extract_service_name(error_message),
                    }
                else:
                    # Not an auth error, don't notify
                    return

            elif category == ErrorCategory.RATE_LIMIT_ERROR:
                event_type = EventType.API_QUOTA_WARNING
                notification_context = {
                    "service": self._extract_service_name(error_message),
                    "current": "Unknown",
                    "limit": "Unknown",
                    "reset_time": "Unknown",
                }

            else:
                return

            # Send notification
            notification_manager.send_notification(
                event_type=event_type,
                context=notification_context,
            )

        except Exception as e:
            logger.debug(f"Failed to send error notification: {e}")

    def _extract_service_name(self, error_message: str) -> str:
        """
        Extract service name from error message.

        Args:
            error_message: Error message

        Returns:
            Service name or "API Service"
        """
        error_lower = error_message.lower()

        # Check for common service names
        services = [
            "openai",
            "anthropic",
            "google",
            "ollama",
            "searxng",
            "tavily",
            "brave",
        ]

        for service in services:
            if service in error_lower:
                return service.title()

        return "API Service"

    def _determine_severity(self, category: ErrorCategory) -> str:
        """Determine error severity level"""
        severity_map = {
            ErrorCategory.CONNECTION_ERROR: "high",
            ErrorCategory.MODEL_ERROR: "high",
            ErrorCategory.SEARCH_ERROR: "medium",
            ErrorCategory.SYNTHESIS_ERROR: "low",  # Can often show partial results
            ErrorCategory.FILE_ERROR: "medium",
            ErrorCategory.RATE_LIMIT_ERROR: "medium",  # Can be resolved with settings
            ErrorCategory.UNKNOWN_ERROR: "high",
        }
        return severity_map.get(category, "medium")

    def _is_recoverable(self, category: ErrorCategory) -> bool:
        """Determine if error is recoverable with user action"""
        recoverable = {
            ErrorCategory.CONNECTION_ERROR: True,
            ErrorCategory.MODEL_ERROR: True,
            ErrorCategory.SEARCH_ERROR: True,
            ErrorCategory.SYNTHESIS_ERROR: True,
            ErrorCategory.FILE_ERROR: True,
            ErrorCategory.RATE_LIMIT_ERROR: True,  # Can enable rate limiting
            ErrorCategory.UNKNOWN_ERROR: False,
        }
        return recoverable.get(category, False)
