"""
Custom exceptions for EDS-NLP integration.

This module defines the exception hierarchy for EDS-NLP processing errors,
providing clear error types for different failure scenarios.

Validates: Requirements 7.1, 7.2
"""


class EDSNLPError(Exception):
    """
    Base exception for EDS-NLP integration errors.
    
    All EDS-NLP-specific exceptions inherit from this base class,
    allowing for catch-all error handling when needed.
    """
    
    def __init__(self, message: str, details: dict = None):
        """
        Initialize EDS-NLP error.
        
        Args:
            message: Human-readable error message
            details: Optional dictionary with additional error context
        """
        super().__init__(message)
        self.message = message
        self.details = details or {}
    
    def __str__(self) -> str:
        """String representation of the error."""
        if self.details:
            details_str = ", ".join(f"{k}={v}" for k, v in self.details.items())
            return f"{self.message} ({details_str})"
        return self.message
    
    def to_dict(self) -> dict:
        """
        Convert error to dictionary for logging/serialization.
        
        Returns:
            Dictionary with error type, message, and details
        """
        return {
            "error_type": self.__class__.__name__,
            "message": self.message,
            "details": self.details
        }


class PipelineInitializationError(EDSNLPError):
    """
    Raised when spaCy pipeline with EDS-NLP components cannot be loaded.
    
    This error indicates a critical failure in setting up the NLP pipeline,
    typically due to missing models, incompatible versions, or configuration
    issues. When this error occurs, the system should fall back to regex-based
    extraction.
    
    Examples:
        - spaCy model not installed
        - EDS-NLP components not available
        - Incompatible spaCy/EDS-NLP versions
        - Invalid pipeline configuration
    """
    
    def __init__(
        self,
        message: str,
        model_name: str = None,
        component_name: str = None,
        original_error: Exception = None
    ):
        """
        Initialize pipeline initialization error.
        
        Args:
            message: Human-readable error message
            model_name: Name of the spaCy model that failed to load
            component_name: Name of the EDS-NLP component that failed
            original_error: Original exception that caused the failure
        """
        details = {}
        if model_name:
            details["model_name"] = model_name
        if component_name:
            details["component_name"] = component_name
        if original_error:
            details["original_error"] = str(original_error)
            details["original_error_type"] = type(original_error).__name__
        
        super().__init__(message, details)
        self.model_name = model_name
        self.component_name = component_name
        self.original_error = original_error


class EDSNLPProcessingError(EDSNLPError):
    """
    Raised when document processing through EDS-NLP pipeline fails.
    
    This error indicates a failure during the processing of a specific document.
    The system should fall back to regex-based extraction for the affected
    document and continue processing other documents.
    
    Examples:
        - Document too long for spaCy
        - Malformed text causing pipeline crash
        - Timeout during processing
        - Memory error during processing
        - Unexpected exception in EDS-NLP component
    """
    
    def __init__(
        self,
        message: str,
        document_id: str = None,
        document_length: int = None,
        processing_stage: str = None,
        original_error: Exception = None
    ):
        """
        Initialize processing error.
        
        Args:
            message: Human-readable error message
            document_id: ID of the document that failed to process
            document_length: Length of the document in characters
            processing_stage: Stage where processing failed (e.g., "entity_extraction")
            original_error: Original exception that caused the failure
        """
        details = {}
        if document_id:
            details["document_id"] = document_id
        if document_length is not None:
            details["document_length"] = document_length
        if processing_stage:
            details["processing_stage"] = processing_stage
        if original_error:
            details["original_error"] = str(original_error)
            details["original_error_type"] = type(original_error).__name__
        
        super().__init__(message, details)
        self.document_id = document_id
        self.document_length = document_length
        self.processing_stage = processing_stage
        self.original_error = original_error


class NormalizationError(EDSNLPError):
    """
    Raised when clinical term normalization fails.
    
    This error indicates a failure in normalizing a clinical term. Unlike
    pipeline and processing errors, normalization errors are typically
    non-critical - the system should log a warning and use the original
    term without normalization.
    
    Examples:
        - Invalid characters in term
        - Abbreviation expansion failure
        - Unicode normalization error
        - Empty term after normalization
    """
    
    def __init__(
        self,
        message: str,
        original_term: str = None,
        normalization_step: str = None,
        original_error: Exception = None
    ):
        """
        Initialize normalization error.
        
        Args:
            message: Human-readable error message
            original_term: The term that failed to normalize
            normalization_step: Step where normalization failed
                              (e.g., "accent_removal", "abbreviation_expansion")
            original_error: Original exception that caused the failure
        """
        details = {}
        if original_term:
            details["original_term"] = original_term
        if normalization_step:
            details["normalization_step"] = normalization_step
        if original_error:
            details["original_error"] = str(original_error)
            details["original_error_type"] = type(original_error).__name__
        
        super().__init__(message, details)
        self.original_term = original_term
        self.normalization_step = normalization_step
        self.original_error = original_error


class EDSNLPTimeoutError(EDSNLPProcessingError):
    """
    Raised when document processing exceeds the configured timeout.
    
    This is a specialized processing error for timeout scenarios. The system
    should fall back to regex-based extraction for the affected document.
    """
    
    def __init__(
        self,
        message: str,
        document_id: str = None,
        timeout_seconds: float = None,
        elapsed_seconds: float = None
    ):
        """
        Initialize timeout error.
        
        Args:
            message: Human-readable error message
            document_id: ID of the document that timed out
            timeout_seconds: Configured timeout threshold
            elapsed_seconds: Actual time elapsed before timeout
        """
        details = {}
        if timeout_seconds is not None:
            details["timeout_seconds"] = timeout_seconds
        if elapsed_seconds is not None:
            details["elapsed_seconds"] = elapsed_seconds
        
        super().__init__(
            message=message,
            document_id=document_id,
            processing_stage="timeout"
        )
        self.timeout_seconds = timeout_seconds
        self.elapsed_seconds = elapsed_seconds


class EDSNLPConfigurationError(EDSNLPError):
    """
    Raised when EDS-NLP configuration is invalid.
    
    This error indicates a problem with the configuration provided to
    EDS-NLP components. It should be raised during initialization.
    
    Examples:
        - Invalid component name
        - Conflicting configuration options
        - Missing required configuration
        - Invalid configuration value types
    """
    
    def __init__(
        self,
        message: str,
        config_key: str = None,
        config_value: any = None,
        expected_type: str = None
    ):
        """
        Initialize configuration error.
        
        Args:
            message: Human-readable error message
            config_key: Configuration key that is invalid
            config_value: Invalid configuration value
            expected_type: Expected type for the configuration value
        """
        details = {}
        if config_key:
            details["config_key"] = config_key
        if config_value is not None:
            details["config_value"] = str(config_value)
        if expected_type:
            details["expected_type"] = expected_type
        
        super().__init__(message, details)
        self.config_key = config_key
        self.config_value = config_value
        self.expected_type = expected_type
