# Copyright (c) 2025 Itential, Inc
# GNU General Public License v3.0+ (see LICENSE or https://www.gnu.org/licenses/gpl-3.0.txt)

"""
Itential MCP Exception Hierarchy

This module defines a comprehensive exception hierarchy for the Itential MCP server,
providing structured error handling with clear categorization and appropriate
HTTP status code mappings for REST API contexts.
"""

from typing import Optional, Dict, Any


class ItentialMcpException(Exception):
    """
    Base exception class for all Itential MCP errors.

    This is the root exception class that all errors generated by the
    Itential MCP server should derive from. It provides consistent
    error handling and optional HTTP status code mapping.

    Attributes:
        message: Human-readable error message
        details: Optional dictionary containing additional error context
        http_status: HTTP status code for API error responses
    """

    http_status: int = 500  # Internal Server Error by default

    def __init__(
        self,
        message: str = "",
        *args,
        details: Optional[Dict[str, Any]] = None,
        http_status: Optional[int] = None,
    ):
        # Handle backward compatibility with multiple positional arguments
        if args:
            # For legacy compatibility, pass all args to Exception.__init__
            super().__init__(message, *args)
            # But use first argument as our message
            self.message = (
                str(message)
                if message
                else (self.__class__.__doc__ or "An error occurred")
            )
        else:
            super().__init__(message)
            self.message = message or self.__class__.__doc__ or "An error occurred"

        self.details = details or {}
        if http_status is not None:
            self.http_status = http_status

    def __str__(self) -> str:
        """Return the error message for string representation."""
        return self.message

    def to_dict(self) -> Dict[str, Any]:
        """Convert exception to dictionary for API responses."""
        return {
            "error": self.__class__.__name__,
            "message": self.message,
            "details": self.details,
            "http_status": self.http_status,
        }


# === Client/Request Errors (4xx) ===


class ClientException(ItentialMcpException):
    """Base class for client-side errors (4xx HTTP status codes)."""

    http_status = 400  # Bad Request


class ValidationException(ClientException):
    """
    Raised when input validation fails.

    Used for parameter validation, schema violations, and
    other input-related errors.
    """

    http_status = 400  # Bad Request


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

    Used for invalid credentials, expired tokens, and
    other authentication-related errors.
    """

    http_status = 401  # Unauthorized


class AuthorizationException(ClientException):
    """
    Raised when authorization fails.

    Used when a user lacks sufficient permissions for
    the requested operation.
    """

    http_status = 403  # Forbidden


class NotFoundError(ClientException):
    """
    Raised when a specific resource could not be found.

    This exception indicates that a requested resource
    (device, workflow, adapter, etc.) does not exist
    on the server.
    """

    http_status = 404  # Not Found


class ConflictException(ClientException):
    """
    Raised when a request conflicts with the current state.

    Used for duplicate resource creation attempts and
    other state conflict scenarios.
    """

    http_status = 409  # Conflict


class AlreadyExistsError(ConflictException):
    """
    Raised when a resource with the same identifier already exists.

    This exception indicates that a resource cannot be created
    because one with the same identifier already exists.
    """

    pass  # Inherits http_status = 409


class InvalidStateError(ConflictException):
    """
    Raised when a resource is in an unexpected or invalid state.

    This exception indicates that a resource is in a state
    that prevents the requested operation from being performed.
    """

    pass  # Inherits http_status = 409


class RateLimitException(ClientException):
    """
    Raised when rate limits are exceeded.

    Used to indicate that too many requests have been made
    in a given time period.
    """

    http_status = 429  # Too Many Requests


# === Server/System Errors (5xx) ===


class ServerException(ItentialMcpException):
    """Base class for server-side errors (5xx HTTP status codes)."""

    http_status = 500  # Internal Server Error


class ConfigurationException(ServerException):
    """
    Raised when there are configuration-related errors.

    Used for missing or invalid configuration settings
    that prevent normal operation.
    """

    pass  # Inherits http_status = 500


class ConnectionException(ServerException):
    """
    Raised when external service connections fail.

    Used for database connections, API connections,
    and other external service communication failures.
    """

    http_status = 502  # Bad Gateway


class TimeoutExceededError(ServerException):
    """
    Raised when a timeout value has been exceeded.

    This exception indicates that an operation took longer
    than the configured timeout value to complete.
    """

    http_status = 504  # Gateway Timeout


class ServiceUnavailableException(ServerException):
    """
    Raised when a required service is temporarily unavailable.

    Used for circuit breaker patterns and temporary
    service outages.
    """

    http_status = 503  # Service Unavailable


# === Business Logic Errors ===


class BusinessLogicException(ItentialMcpException):
    """Base class for business logic errors."""

    http_status = 422  # Unprocessable Entity


class WorkflowException(BusinessLogicException):
    """
    Raised when workflow execution encounters an error.

    Used for workflow-specific errors such as invalid
    workflow states, missing dependencies, etc.
    """

    pass


class DeviceException(BusinessLogicException):
    """
    Raised when device operations encounter errors.

    Used for device-specific errors such as unreachable
    devices, invalid device states, etc.
    """

    pass


class ComplianceException(BusinessLogicException):
    """
    Raised when compliance operations encounter errors.

    Used for compliance-specific errors such as policy
    violations, invalid compliance rules, etc.
    """

    pass


# === Legacy Compatibility Aliases ===

# Maintain backward compatibility with existing error names
ItentialMcpError = ItentialMcpException  # Legacy alias


# === Exception Utility Functions ===


def get_exception_for_http_status(status_code: int) -> type[ItentialMcpException]:
    """
    Get the appropriate exception class for an HTTP status code.

    Args:
        status_code: HTTP status code

    Returns:
        Exception class that maps to the given status code
    """
    status_mapping = {
        400: ValidationException,
        401: AuthenticationException,
        403: AuthorizationException,
        404: NotFoundError,
        409: ConflictException,
        422: BusinessLogicException,
        429: RateLimitException,
        500: ServerException,
        502: ConnectionException,
        503: ServiceUnavailableException,
        504: TimeoutExceededError,
    }

    return status_mapping.get(status_code, ItentialMcpException)


def create_exception_from_response(
    status_code: int, message: str, details: Optional[Dict[str, Any]] = None
) -> ItentialMcpException:
    """
    Create an appropriate exception from an HTTP response.

    Args:
        status_code: HTTP status code
        message: Error message
        details: Additional error details

    Returns:
        Exception instance appropriate for the status code
    """
    exception_class = get_exception_for_http_status(status_code)
    return exception_class(message=message, details=details, http_status=status_code)


# === Exception Categories for Filtering ===

CLIENT_EXCEPTIONS = (
    ClientException,
    ValidationException,
    AuthenticationException,
    AuthorizationException,
    NotFoundError,
    ConflictException,
    AlreadyExistsError,
    InvalidStateError,
    RateLimitException,
)

SERVER_EXCEPTIONS = (
    ServerException,
    ConfigurationException,
    ConnectionException,
    TimeoutExceededError,
    ServiceUnavailableException,
)

BUSINESS_EXCEPTIONS = (
    BusinessLogicException,
    WorkflowException,
    DeviceException,
    ComplianceException,
)

ALL_EXCEPTIONS = CLIENT_EXCEPTIONS + SERVER_EXCEPTIONS + BUSINESS_EXCEPTIONS


def is_client_error(exception: Exception) -> bool:
    """Check if an exception is a client-side error."""
    return isinstance(exception, CLIENT_EXCEPTIONS)


def is_server_error(exception: Exception) -> bool:
    """Check if an exception is a server-side error."""
    return isinstance(exception, SERVER_EXCEPTIONS)


def is_business_error(exception: Exception) -> bool:
    """Check if an exception is a business logic error."""
    return isinstance(exception, BUSINESS_EXCEPTIONS)
