# ws_package/exceptions/custom_exceptions.py
"""
Custom exception classes for Workstatus application.
Provides specific exception types with proper error context and recovery information.
"""

import asyncio
from typing import Optional, Dict, Any
from ws_package.log.writer import LogWriter

logger = LogWriter.file(__name__)


class WorkstatusBaseException(Exception):
    """Base exception class for all Workstatus-specific exceptions."""
    
    def __init__(self, message: str, error_code: str = None, context: Dict[str, Any] = None, 
                 recoverable: bool = True, retry_after: Optional[int] = None):
        super().__init__(message)
        self.message = message
        self.error_code = error_code or self.__class__.__name__
        self.context = context or {}
        self.recoverable = recoverable
        self.retry_after = retry_after
        
        # Log the exception with context
        logger.error("%s: %s", self.error_code, message, extra={"context": self.context})


class ConfigurationError(WorkstatusBaseException):
    """Raised when configuration-related errors occur."""
    pass


class AuthenticationError(WorkstatusBaseException):
    """Raised when authentication fails."""
    def __init__(self, message: str, context: Dict[str, Any] = None):
        super().__init__(message, "AUTH_ERROR", context, recoverable=True, retry_after=60)


class AuthorizationError(WorkstatusBaseException):
    """Raised when authorization fails."""
    def __init__(self, message: str, context: Dict[str, Any] = None):
        super().__init__(message, "AUTHZ_ERROR", context, recoverable=False)


class NetworkError(WorkstatusBaseException):
    """Raised when network-related errors occur."""
    def __init__(self, message: str, context: Dict[str, Any] = None, retry_after: int = 30):
        super().__init__(message, "NETWORK_ERROR", context, recoverable=True, retry_after=retry_after)


class APIError(WorkstatusBaseException):
    """Raised when API-related errors occur."""
    def __init__(self, message: str, status_code: int = None, context: Dict[str, Any] = None, 
                 retry_after: int = 60):
        self.status_code = status_code
        super().__init__(message, f"API_ERROR_{status_code}", context, 
                        recoverable=self._is_recoverable(status_code), retry_after=retry_after)
    
    def _is_recoverable(self, status_code: int) -> bool:
        """Determine if the error is recoverable based on status code."""
        if status_code is None:
            return True
        # 4xx client errors are generally not recoverable, 5xx server errors are
        return status_code >= 500


class DatabaseError(WorkstatusBaseException):
    """Raised when database-related errors occur."""
    def __init__(self, message: str, context: Dict[str, Any] = None):
        super().__init__(message, "DB_ERROR", context, recoverable=True, retry_after=10)


class FileSystemError(WorkstatusBaseException):
    """Raised when file system operations fail."""
    def __init__(self, message: str, context: Dict[str, Any] = None):
        super().__init__(message, "FS_ERROR", context, recoverable=True, retry_after=5)


class EncryptionError(WorkstatusBaseException):
    """Raised when encryption/decryption operations fail."""
    def __init__(self, message: str, context: Dict[str, Any] = None):
        super().__init__(message, "ENCRYPT_ERROR", context, recoverable=False)


class ValidationError(WorkstatusBaseException):
    """Raised when data validation fails."""
    def __init__(self, message: str, field: str = None, context: Dict[str, Any] = None):
        self.field = field
        super().__init__(message, "VALIDATION_ERROR", context, recoverable=False)


class TimeoutError(WorkstatusBaseException):
    """Raised when operations timeout."""
    def __init__(self, message: str, context: Dict[str, Any] = None, retry_after: int = 30):
        super().__init__(message, "TIMEOUT_ERROR", context, recoverable=True, retry_after=retry_after)


class ResourceError(WorkstatusBaseException):
    """Raised when resource-related errors occur (memory, disk space, etc.)."""
    def __init__(self, message: str, resource_type: str = None, context: Dict[str, Any] = None):
        self.resource_type = resource_type
        super().__init__(message, "RESOURCE_ERROR", context, recoverable=True, retry_after=60)


class ScreenshotError(WorkstatusBaseException):
    """Raised when screenshot-related operations fail."""
    def __init__(self, message: str, context: Dict[str, Any] = None):
        super().__init__(message, "SCREENSHOT_ERROR", context, recoverable=True, retry_after=10)


class ActivityMonitoringError(WorkstatusBaseException):
    """Raised when activity monitoring operations fail."""
    def __init__(self, message: str, context: Dict[str, Any] = None):
        super().__init__(message, "ACTIVITY_ERROR", context, recoverable=True, retry_after=5)


def handle_exception(func):
    """
    Decorator to handle exceptions and convert them to appropriate Workstatus exceptions.
    """
    def wrapper(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except FileNotFoundError as e:
            raise FileSystemError(f"File not found: {e}", {"file_path": str(e)})
        except PermissionError as e:
            raise FileSystemError(f"Permission denied: {e}", {"file_path": str(e)})
        except OSError as e:
            raise FileSystemError(f"OS error: {e}", {"error_code": e.errno})
        except ValueError as e:
            raise ValidationError(f"Invalid value: {e}")
        except KeyError as e:
            raise ValidationError(f"Missing required key: {e}")
        except TypeError as e:
            raise ValidationError(f"Type error: {e}")
        except ConnectionError as e:
            raise NetworkError(f"Connection error: {e}")
        except TimeoutError as e:
            raise TimeoutError(f"Operation timed out: {e}")
        except MemoryError as e:
            raise ResourceError(f"Memory error: {e}", "memory")
        except Exception as e:
            # Log unexpected exceptions with full context
            logger.exception("Unexpected error in %s: %s", func.__name__, e)
            raise WorkstatusBaseException(f"Unexpected error: {e}", context={"function": func.__name__})
    
    return wrapper


def handle_async_exception(func):
    """
    Decorator to handle exceptions in async functions and convert them to appropriate Workstatus exceptions.
    """
    async def wrapper(*args, **kwargs):
        try:
            return await func(*args, **kwargs)
        except FileNotFoundError as e:
            raise FileSystemError(f"File not found: {e}", {"file_path": str(e)})
        except PermissionError as e:
            raise FileSystemError(f"Permission denied: {e}", {"file_path": str(e)})
        except OSError as e:
            raise FileSystemError(f"OS error: {e}", {"error_code": e.errno})
        except ValueError as e:
            raise ValidationError(f"Invalid value: {e}")
        except KeyError as e:
            raise ValidationError(f"Missing required key: {e}")
        except TypeError as e:
            raise ValidationError(f"Type error: {e}")
        except ConnectionError as e:
            raise NetworkError(f"Connection error: {e}")
        except asyncio.TimeoutError as e:
            raise TimeoutError(f"Async operation timed out: {e}")
        except MemoryError as e:
            raise ResourceError(f"Memory error: {e}", "memory")
        except Exception as e:
            # Log unexpected exceptions with full context
            logger.exception("Unexpected error in %s: %s", func.__name__, e)
            raise WorkstatusBaseException(f"Unexpected error: {e}", context={"function": func.__name__})
    
    return wrapper
