# app/exceptions/base.py
"""
Base exception classes for the application.

This module provides classes and functions for:
- Base exception class with consistent error formatting
- Common exception types for different error categories
- Standardized error response structure
"""

from typing import Any


class OrionException(Exception):
    """Base exception class for all custom exceptions."""

    def __init__(
        self,
        message: str,
        error_code: str,
        status_code: int = 400,
        details: dict[str, Any] | None = None,
    ):
        self.message = message
        self.error_code = error_code
        self.status_code = status_code
        self.details = details or {}
        super().__init__(self.message)

    def to_dict(self) -> dict[str, Any]:
        """Convert exception to dictionary for JSON response."""
        result = {
            "error_code": self.error_code,
            "message": self.message,
            "status_code": self.status_code,
        }
        if self.details:
            result["details"] = self.details
        return result


class ValidationException(OrionException):
    """Raised when request validation fails."""

    def __init__(
        self,
        message: str,
        field: str | None = None,
        details: dict[str, Any] | None = None,
    ):
        validation_details = details or {}
        if field:
            validation_details["field"] = field

        super().__init__(
            message=message,
            error_code="VALIDATION_ERROR",
            status_code=422,
            details=validation_details,
        )


class AuthenticationException(OrionException):
    """Raised when authentication fails."""

    def __init__(
        self,
        message: str = "Authentication failed",
        error_code: str = "AUTHENTICATION_FAILED",
        details: dict[str, Any] | None = None,
    ):
        super().__init__(
            message=message,
            error_code=error_code,
            status_code=401,
            details=details,
        )


class AuthorizationException(OrionException):
    """Raised when user lacks permission for an operation."""

    def __init__(
        self,
        message: str = "Access denied",
        error_code: str = "AUTHORIZATION_FAILED",
        details: dict[str, Any] | None = None,
    ):
        super().__init__(
            message=message,
            error_code=error_code,
            status_code=403,
            details=details,
        )


class ResourceNotFoundException(OrionException):
    """Raised when a requested resource is not found."""

    def __init__(
        self,
        resource_type: str,
        identifier: str,
        message: str | None = None,
        error_code: str | None = None,
    ):
        if not message:
            message = f"{resource_type} with identifier '{identifier}' not found"
        if not error_code:
            error_code = f"{resource_type.upper()}_NOT_FOUND"

        super().__init__(
            message=message,
            error_code=error_code,
            status_code=404,
            details={
                "resource_type": resource_type,
                "identifier": identifier,
            },
        )


class ConflictException(OrionException):
    """Raised when a resource conflict occurs."""

    def __init__(
        self,
        message: str,
        error_code: str = "RESOURCE_CONFLICT",
        details: dict[str, Any] | None = None,
    ):
        super().__init__(
            message=message,
            error_code=error_code,
            status_code=409,
            details=details,
        )


class BusinessLogicException(OrionException):
    """Raised when business logic rules are violated."""

    def __init__(
        self,
        message: str,
        error_code: str,
        details: dict[str, Any] | None = None,
    ):
        super().__init__(
            message=message,
            error_code=error_code,
            status_code=400,
            details=details,
        )


class ExternalServiceException(OrionException):
    """Raised when an external service fails."""

    def __init__(
        self,
        message: str,
        service_name: str,
        error_code: str = "EXTERNAL_SERVICE_ERROR",
        details: dict[str, Any] | None = None,
    ):
        service_details = details or {}
        service_details["service_name"] = service_name

        super().__init__(
            message=message,
            error_code=error_code,
            status_code=502,
            details=service_details,
        )


class RateLimitException(OrionException):
    """Raised when rate limit is exceeded."""

    def __init__(
        self,
        message: str = "Rate limit exceeded",
        retry_after: int | None = None,
        details: dict[str, Any] | None = None,
    ):
        rate_limit_details = details or {}
        if retry_after:
            rate_limit_details["retry_after"] = retry_after

        super().__init__(
            message=message,
            error_code="RATE_LIMIT_EXCEEDED",
            status_code=429,
            details=rate_limit_details,
        )


class ServiceUnavailableException(OrionException):
    """Raised when service is unavailable."""

    def __init__(self, message: str = "Service temporarily unavailable"):
        super().__init__(
            message=message,
            error_code="SERVICE_UNAVAILABLE",
            status_code=503,
        )


# Note: Domain-specific exceptions like StoreNotFoundException, UserNotFoundException, etc.
# are defined in their respective domain modules (store.py, admin.py, etc.)
# to keep domain-specific logic separate from base exceptions.
