"""Error classification utilities for the Trax platform.

This module provides utilities for classifying, categorizing, and handling
different types of errors in a consistent manner.
"""

import inspect
import traceback
from typing import Any, Callable, Dict, List, Optional, Type, Union

from .base import (
    TraxError, NetworkError, APIError, FileSystemError, ValidationError,
    ProcessingError, ConfigurationError, ResourceError
)
from .codes import ErrorCode, ErrorCategory, ErrorSeverity, get_error_code


def classify_error(error: Exception) -> Type[TraxError]:
    """Classify an exception into the appropriate Trax error type."""
    if isinstance(error, TraxError):
        return type(error)
    
    error_message = str(error).lower()
    error_type = type(error).__name__.lower()
    
    # Network errors
    if any(keyword in error_message for keyword in [
        "connection", "timeout", "network", "dns", "resolve", "unreachable"
    ]) or any(keyword in error_type for keyword in [
        "connection", "timeout", "socket", "urllib"
    ]):
        return NetworkError
    
    # API errors
    if any(keyword in error_message for keyword in [
        "api", "http", "status", "unauthorized", "forbidden", "rate limit",
        "quota", "service unavailable", "bad request"
    ]) or any(keyword in error_type for keyword in [
        "http", "api", "requests"
    ]):
        return APIError
    
    # File system errors
    if any(keyword in error_message for keyword in [
        "file", "directory", "path", "permission", "not found", "no such",
        "disk space", "corrupt", "invalid"
    ]) or any(keyword in error_type for keyword in [
        "file", "os", "io", "permission"
    ]):
        return FileSystemError
    
    # Validation errors
    if any(keyword in error_message for keyword in [
        "validation", "invalid", "required", "missing", "format", "type"
    ]) or any(keyword in error_type for keyword in [
        "value", "type", "validation"
    ]):
        return ValidationError
    
    # Configuration errors
    if any(keyword in error_message for keyword in [
        "config", "environment", "setting", "missing config"
    ]) or any(keyword in error_type for keyword in [
        "config", "environment"
    ]):
        return ConfigurationError
    
    # Resource errors
    if any(keyword in error_message for keyword in [
        "memory", "cpu", "resource", "out of memory", "overload"
    ]) or any(keyword in error_type for keyword in [
        "memory", "resource"
    ]):
        return ResourceError
    
    # Default to processing error for unknown types
    return ProcessingError


def extract_error_context(error: Exception, depth: int = 3) -> Dict[str, Any]:
    """Extract contextual information from an error."""
    context = {
        "error_type": type(error).__name__,
        "error_message": str(error),
        "traceback": traceback.format_exc(),
        "frame_info": []
    }
    
    # Extract frame information
    tb = traceback.extract_tb(error.__traceback__)
    for frame in tb[-depth:]:
        context["frame_info"].append({
            "filename": frame.filename,
            "line": frame.lineno,
            "function": frame.name,
            "line_content": frame.line
        })
    
    # Add additional context for specific error types
    if hasattr(error, 'context'):
        context.update(error.context)
    
    return context


def is_retryable_error(error: Exception) -> bool:
    """Determine if an error is retryable."""
    if isinstance(error, TraxError):
        return error.is_retryable
    
    # Check for common retryable error patterns
    error_message = str(error).lower()
    error_type = type(error).__name__.lower()
    
    # Network errors are generally retryable
    if any(keyword in error_message for keyword in [
        "timeout", "connection", "network", "temporary", "unavailable"
    ]):
        return True
    
    # Rate limiting is retryable
    if "rate limit" in error_message or "too many requests" in error_message:
        return True
    
    # Service unavailable is retryable
    if "service unavailable" in error_message or "temporary" in error_message:
        return True
    
    # File system errors are generally not retryable
    if any(keyword in error_message for keyword in [
        "permission", "not found", "no such file", "disk space"
    ]):
        return False
    
    # Validation errors are not retryable
    if any(keyword in error_message for keyword in [
        "validation", "invalid", "required", "missing"
    ]):
        return False
    
    # Default to not retryable for unknown errors
    return False


def get_error_severity(error: Exception) -> ErrorSeverity:
    """Get the severity level of an error."""
    if isinstance(error, TraxError):
        return error.severity
    
    error_message = str(error).lower()
    
    # Critical errors
    if any(keyword in error_message for keyword in [
        "authentication", "unauthorized", "disk space", "memory", "critical"
    ]):
        return ErrorSeverity.CRITICAL
    
    # High severity errors
    if any(keyword in error_message for keyword in [
        "connection", "file not found", "permission", "corrupt"
    ]):
        return ErrorSeverity.HIGH
    
    # Medium severity errors
    if any(keyword in error_message for keyword in [
        "timeout", "rate limit", "validation", "invalid"
    ]):
        return ErrorSeverity.MEDIUM
    
    # Default to medium severity
    return ErrorSeverity.MEDIUM


def get_error_category(error: Exception) -> ErrorCategory:
    """Get the category of an error."""
    if isinstance(error, TraxError):
        return error.category
    
    error_class = classify_error(error)
    
    if error_class == NetworkError:
        return ErrorCategory.NETWORK
    elif error_class == APIError:
        return ErrorCategory.API
    elif error_class == FileSystemError:
        return ErrorCategory.FILESYSTEM
    elif error_class == ValidationError:
        return ErrorCategory.VALIDATION
    elif error_class == ConfigurationError:
        return ErrorCategory.CONFIGURATION
    elif error_class == ResourceError:
        return ErrorCategory.RESOURCE
    else:
        return ErrorCategory.PROCESSING


def wrap_error(
    error: Exception,
    message: Optional[str] = None,
    error_code: Optional[ErrorCode] = None,
    context: Optional[Dict[str, Any]] = None
) -> TraxError:
    """Wrap an exception in the appropriate Trax error type."""
    if isinstance(error, TraxError):
        return error
    
    error_class = classify_error(error)
    wrapped_message = message or str(error)
    
    return error_class(
        message=wrapped_message,
        error_code=error_code,
        context=context,
        original_error=error
    )


def create_error_from_code(
    error_code: ErrorCode,
    message: Optional[str] = None,
    context: Optional[Dict[str, Any]] = None,
    original_error: Optional[Exception] = None
) -> TraxError:
    """Create a Trax error from an error code."""
    message = message or error_code.description
    
    # Map error code categories to error classes
    category_to_class = {
        ErrorCategory.NETWORK: NetworkError,
        ErrorCategory.API: APIError,
        ErrorCategory.FILESYSTEM: FileSystemError,
        ErrorCategory.VALIDATION: ValidationError,
        ErrorCategory.PROCESSING: ProcessingError,
        ErrorCategory.CONFIGURATION: ConfigurationError,
        ErrorCategory.RESOURCE: ResourceError,
    }
    
    error_class = category_to_class.get(error_code.category, TraxError)
    
    return error_class(
        message=message,
        error_code=error_code,
        context=context,
        original_error=original_error
    )


def get_actionable_message(error: Exception) -> str:
    """Get an actionable message for an error."""
    if isinstance(error, TraxError) and error.error_code:
        return error.error_code.actionable_message
    
    # Provide generic actionable messages based on error type
    error_class = classify_error(error)
    
    if error_class == NetworkError:
        return "Check your internet connection and try again"
    elif error_class == APIError:
        return "Please check your API configuration and try again"
    elif error_class == FileSystemError:
        return "Please check the file path and permissions"
    elif error_class == ValidationError:
        return "Please check your input and try again"
    elif error_class == ConfigurationError:
        return "Please check your configuration settings"
    elif error_class == ResourceError:
        return "Please try again later when system resources are available"
    else:
        return "An unexpected error occurred. Please try again"


def error_handler(
    error_types: Optional[List[Type[Exception]]] = None,
    default_error_code: Optional[ErrorCode] = None,
    log_error: bool = True
):
    """Decorator for handling errors in functions."""
    def decorator(func: Callable) -> Callable:
        def wrapper(*args, **kwargs):
            try:
                return func(*args, **kwargs)
            except Exception as e:
                if error_types and not any(isinstance(e, t) for t in error_types):
                    raise
                
                wrapped_error = wrap_error(e, error_code=default_error_code)
                
                if log_error:
                    from ..logging import get_logger
                    logger = get_logger(func.__module__)
                    logger.error(
                        f"Error in {func.__name__}: {wrapped_error}",
                        extra=wrapped_error.to_dict()
                    )
                
                raise wrapped_error
        
        return wrapper
    return decorator


def async_error_handler(
    error_types: Optional[List[Type[Exception]]] = None,
    default_error_code: Optional[ErrorCode] = None,
    log_error: bool = True
):
    """Decorator for handling errors in async functions."""
    def decorator(func: Callable) -> Callable:
        async def wrapper(*args, **kwargs):
            try:
                return await func(*args, **kwargs)
            except Exception as e:
                if error_types and not any(isinstance(e, t) for t in error_types):
                    raise
                
                wrapped_error = wrap_error(e, error_code=default_error_code)
                
                if log_error:
                    from ..logging import get_logger
                    logger = get_logger(func.__module__)
                    logger.error(
                        f"Error in {func.__name__}: {wrapped_error}",
                        extra=wrapped_error.to_dict()
                    )
                
                raise wrapped_error
        
        return wrapper
    return decorator
