"""
Custom exceptions for Toxo platform.

This module defines all custom exceptions used throughout the Toxo platform,
providing clear error categorization and helpful error messages.
"""

import time
import logging
from typing import Optional, Dict, Any, List
from enum import Enum


class ErrorSeverity(Enum):
    """Error severity levels."""
    LOW = "low"
    MEDIUM = "medium" 
    HIGH = "high"
    CRITICAL = "critical"


class ToxoError(Exception):
    """
    Base exception for all Toxo errors.
    
    Provides common functionality for error handling including:
    - Error categorization and severity
    - Structured error data
    - Retry capability indication
    - Error context tracking
    """
    
    def __init__(
        self,
        message: str,
        error_code: Optional[str] = None,
        severity: ErrorSeverity = ErrorSeverity.MEDIUM,
        context: Optional[Dict[str, Any]] = None,
        cause: Optional[Exception] = None,
        retryable: bool = False
    ):
        """
        Initialize Toxo error.
        
        Args:
            message: Human-readable error message
            error_code: Machine-readable error code
            severity: Error severity level
            context: Additional error context
            cause: Original exception that caused this error
            retryable: Whether this error can be retried
        """
        self.message = message
        self.error_code = error_code or self.__class__.__name__.upper()
        self.severity = severity
        self.context = context or {}
        self.cause = cause
        self.retryable = retryable
        self.timestamp = time.time()
        
        # Format message with error code
        formatted_message = f"[{self.error_code}] {message}"
        if cause:
            formatted_message += f" (caused by: {cause})"
            
        super().__init__(formatted_message)
    
    def to_dict(self) -> Dict[str, Any]:
        """Convert error to dictionary representation."""
        return {
            "error_code": self.error_code,
            "message": self.message,
            "severity": self.severity.value,
            "context": self.context,
            "retryable": self.retryable,
            "timestamp": self.timestamp,
            "cause": str(self.cause) if self.cause else None
        }


class ConfigError(ToxoError):
    """Error in configuration setup or validation."""
    
    def __init__(self, message: str, **kwargs):
        super().__init__(
            message,
            error_code="CONFIG_ERROR",
            severity=ErrorSeverity.HIGH,
            **kwargs
        )


class ValidationError(ToxoError):
    """Error in data validation."""
    
    def __init__(self, message: str, field: Optional[str] = None, **kwargs):
        context = kwargs.get('context', {})
        if field:
            context['field'] = field
        kwargs['context'] = context
        
        super().__init__(
            message,
            error_code="VALIDATION_ERROR",
            severity=ErrorSeverity.MEDIUM,
            **kwargs
        )


class APIError(ToxoError):
    """Error in API communication."""
    
    def __init__(
        self,
        message: str,
        status_code: Optional[int] = None,
        response_data: Optional[Dict[str, Any]] = None,
        **kwargs
    ):
        context = kwargs.get('context', {})
        if status_code:
            context['status_code'] = status_code
        if response_data:
            context['response_data'] = response_data
        kwargs['context'] = context
        
        # API errors are generally retryable unless they're client errors
        if not 'retryable' in kwargs:
            kwargs['retryable'] = status_code is None or status_code >= 500 or status_code == 429
        
        super().__init__(
            message,
            error_code="API_ERROR",
            severity=ErrorSeverity.HIGH if status_code and status_code >= 500 else ErrorSeverity.MEDIUM,
            **kwargs
        )


class RateLimitError(APIError):
    """Error due to API rate limiting."""
    
    def __init__(self, message: str, retry_after: Optional[int] = None, **kwargs):
        context = kwargs.get('context', {})
        if retry_after:
            context['retry_after'] = retry_after
        kwargs['context'] = context
        kwargs['retryable'] = True
        
        super().__init__(
            message,
            status_code=429,
            **kwargs
        )
        self.error_code = "RATE_LIMIT_ERROR"


class AuthenticationError(APIError):
    """Error in authentication."""
    
    def __init__(self, message: str, **kwargs):
        kwargs['retryable'] = False  # Auth errors are not retryable
        super().__init__(
            message,
            status_code=401,
            error_code="AUTHENTICATION_ERROR",
            **kwargs
        )


class MemoryError(ToxoError):
    """Error in memory management."""
    
    def __init__(self, message: str, memory_usage: Optional[int] = None, **kwargs):
        context = kwargs.get('context', {})
        if memory_usage:
            context['memory_usage'] = memory_usage
        kwargs['context'] = context
        
        super().__init__(
            message,
            error_code="MEMORY_ERROR",
            severity=ErrorSeverity.HIGH,
            retryable=True,  # Memory errors might be retryable after cleanup
            **kwargs
        )


class TrainingError(ToxoError):
    """Error during model training."""
    
    def __init__(
        self,
        message: str,
        epoch: Optional[int] = None,
        batch: Optional[int] = None,
        **kwargs
    ):
        context = kwargs.get('context', {})
        if epoch is not None:
            context['epoch'] = epoch
        if batch is not None:
            context['batch'] = batch
        kwargs['context'] = context
        
        super().__init__(
            message,
            error_code="TRAINING_ERROR",
            severity=ErrorSeverity.HIGH,
            **kwargs
        )


class LearningError(ToxoError):
    """Error during learning or adaptation processes."""
    
    def __init__(
        self,
        message: str,
        learning_type: Optional[str] = None,
        feedback_id: Optional[str] = None,
        **kwargs
    ):
        context = kwargs.get('context', {})
        if learning_type:
            context['learning_type'] = learning_type
        if feedback_id:
            context['feedback_id'] = feedback_id
        kwargs['context'] = context
        
        super().__init__(
            message,
            error_code="LEARNING_ERROR",
            severity=ErrorSeverity.MEDIUM,
            **kwargs
        )


class DocumentProcessingError(ToxoError):
    """Error in document processing operations."""
    
    def __init__(
        self,
        message: str,
        file_path: Optional[str] = None,
        file_type: Optional[str] = None,
        **kwargs
    ):
        context = kwargs.get('context', {})
        if file_path:
            context['file_path'] = file_path
        if file_type:
            context['file_type'] = file_type
        kwargs['context'] = context
        
        super().__init__(
            message,
            error_code="DOCUMENT_PROCESSING_ERROR",
            severity=ErrorSeverity.MEDIUM,
            retryable=True,  # Document processing errors might be retryable
            **kwargs
        )


# Alias for backward compatibility
ProcessingError = DocumentProcessingError


class CoordinationError(ToxoError):
    """Error in agent coordination operations."""
    
    def __init__(self, message: str, agent_id: Optional[str] = None, **kwargs):
        context = kwargs.get('context', {})
        if agent_id:
            context['agent_id'] = agent_id
        kwargs['context'] = context
        
        super().__init__(
            message,
            error_code="COORDINATION_ERROR",
            severity=ErrorSeverity.HIGH,
            retryable=True,
            **kwargs
        )


class AgentError(ToxoError):
    """Error in agent operations."""
    
    def __init__(self, message: str, agent_id: Optional[str] = None, agent_type: Optional[str] = None, **kwargs):
        context = kwargs.get('context', {})
        if agent_id:
            context['agent_id'] = agent_id
        if agent_type:
            context['agent_type'] = agent_type
        kwargs['context'] = context
        
        super().__init__(
            message,
            error_code="AGENT_ERROR",
            severity=ErrorSeverity.MEDIUM,
            retryable=True,
            **kwargs
        )


class NetworkError(ToxoError):
    """Error in network communication."""
    
    def __init__(self, message: str, **kwargs):
        super().__init__(
            message,
            error_code="NETWORK_ERROR",
            severity=ErrorSeverity.HIGH,
            retryable=True,
            **kwargs
        )


class TimeoutError(ToxoError):
    """Error due to operation timeout."""
    
    def __init__(self, message: str, timeout_duration: Optional[float] = None, **kwargs):
        context = kwargs.get('context', {})
        if timeout_duration:
            context['timeout_duration'] = timeout_duration
        kwargs['context'] = context
        
        super().__init__(
            message,
            error_code="TIMEOUT_ERROR",
            severity=ErrorSeverity.MEDIUM,
            retryable=True,
            **kwargs
        )


class TransactionError(ToxoError):
    """Error in transaction-like operations."""
    
    def __init__(
        self,
        message: str,
        transaction_id: Optional[str] = None,
        rollback_available: bool = True,
        **kwargs
    ):
        context = kwargs.get('context', {})
        if transaction_id:
            context['transaction_id'] = transaction_id
        context['rollback_available'] = rollback_available
        kwargs['context'] = context
        
        super().__init__(
            message,
            error_code="TRANSACTION_ERROR",
            severity=ErrorSeverity.HIGH,
            **kwargs
        )


class DataCorruptionError(ToxoError):
    """Error due to data corruption."""
    
    def __init__(self, message: str, **kwargs):
        super().__init__(
            message,
            error_code="DATA_CORRUPTION_ERROR",
            severity=ErrorSeverity.CRITICAL,
            retryable=False,
            **kwargs
        )


class PerformanceWarning(ToxoError):
    """Warning for performance issues."""
    
    def __init__(self, message: str, **kwargs):
        super().__init__(
            message,
            error_code="PERFORMANCE_WARNING",
            severity=ErrorSeverity.LOW,
            retryable=False,
            **kwargs
        )


class ModelError(ToxoError):
    """Error in model operations."""
    
    def __init__(
        self, 
        message: str, 
        model_name: Optional[str] = None,
        model_version: Optional[str] = None,
        **kwargs
    ):
        context = kwargs.get('context', {})
        if model_name:
            context['model_name'] = model_name
        if model_version:
            context['model_version'] = model_version
        kwargs['context'] = context
        
        super().__init__(
            message,
            error_code="MODEL_ERROR",
            severity=ErrorSeverity.HIGH,
            **kwargs
        )


class InferenceError(ToxoError):
    """Error during inference/query processing."""
    
    def __init__(
        self, 
        message: str, 
        query: Optional[str] = None,
        response_time: Optional[float] = None,
        **kwargs
    ):
        context = kwargs.get('context', {})
        if query:
            # Truncate long queries for logging
            context['query'] = query[:200] + "..." if len(query) > 200 else query
        if response_time is not None:
            context['response_time'] = response_time
        kwargs['context'] = context
        
        super().__init__(
            message,
            error_code="INFERENCE_ERROR",
            severity=ErrorSeverity.MEDIUM,
            retryable=True,
            **kwargs
        )


class LayerError(ToxoError):
    """Raised when there's an error with layer operations."""
    pass


class PromptError(ToxoError):
    """Error in prompt processing."""
    pass


class SecurityError(ToxoError):
    """Error in security operations."""
    
    def __init__(self, message: str, security_context: Optional[str] = None, **kwargs):
        context = kwargs.get('context', {})
        if security_context:
            context['security_context'] = security_context
        kwargs['context'] = context
        kwargs['retryable'] = False  # Security errors are generally not retryable
        
        super().__init__(
            message,
            error_code="SECURITY_ERROR",
            severity=ErrorSeverity.HIGH,
            **kwargs
        )


class PackageError(ToxoError):
    """Error in package management."""
    pass


# Utility functions for error handling
def is_retryable_error(error: Exception) -> bool:
    """Check if an error is retryable."""
    if isinstance(error, ToxoError):
        return error.retryable
    
    # Check for common retryable error patterns
    if isinstance(error, (ConnectionError, TimeoutError)):
        return True
    
    return False


def get_error_severity(error: Exception) -> ErrorSeverity:
    """Get the severity of an error."""
    if isinstance(error, ToxoError):
        return error.severity
    
    # Default severity for non-Toxo errors
    return ErrorSeverity.MEDIUM


def format_error_context(error: Exception) -> Dict[str, Any]:
    """Format error context for logging."""
    if isinstance(error, ToxoError):
        return error.to_dict()
    
    return {
        "error_type": type(error).__name__,
        "message": str(error),
        "retryable": is_retryable_error(error),
        "severity": get_error_severity(error).value,
        "timestamp": time.time()
    } 