from __future__ import annotations

from dataclasses import dataclass
from typing import Any, Dict, Optional, Type


@dataclass(slots=True)
class MetaErrorContext:
    """Normalized shape of a Meta Graph API error payload."""

    message: str
    type: Optional[str] = None
    code: Optional[int] = None
    error_subcode: Optional[int] = None
    fbtrace_id: Optional[str] = None
    is_transient: bool | None = None
    error_user_title: Optional[str] = None
    error_user_msg: Optional[str] = None
    raw: Dict[str, Any] | None = None


class MetaApiError(Exception):
    """Base exception for Meta Graph API errors."""

    def __init__(self, context: MetaErrorContext, status_code: int) -> None:
        super().__init__(context.message)
        self.context = context
        self.status_code = status_code

    def to_dict(self) -> Dict[str, Any]:
        return {
            "message": self.context.message,
            "type": self.context.type,
            "code": self.context.code,
            "error_subcode": self.context.error_subcode,
            "fbtrace_id": self.context.fbtrace_id,
            "status_code": self.status_code,
        }


class MetaApiRateLimitError(MetaApiError):
    """Raised when the API indicates rate limiting or transient errors."""


class MetaApiAuthError(MetaApiError):
    """Raised when authentication is invalid or expired."""


class MetaApiPermissionError(MetaApiError):
    """Raised when the token lacks permissions for the requested resource."""


_ERROR_CODE_TO_EXCEPTION: Dict[int, Type[MetaApiError]] = {
    1: MetaApiRateLimitError,
    2: MetaApiRateLimitError,
    4: MetaApiRateLimitError,
    17: MetaApiRateLimitError,
    32: MetaApiRateLimitError,
    613: MetaApiRateLimitError,
    102: MetaApiAuthError,
    190: MetaApiAuthError,
    200: MetaApiPermissionError,
    294: MetaApiPermissionError,
    298: MetaApiPermissionError,
}


def build_error_context(payload: Dict[str, Any]) -> MetaErrorContext:
    error_obj = payload.get("error", payload)
    return MetaErrorContext(
        message=error_obj.get("message", "Unknown Meta API error"),
        type=error_obj.get("type"),
        code=error_obj.get("code"),
        error_subcode=error_obj.get("error_subcode"),
        fbtrace_id=error_obj.get("fbtrace_id"),
        is_transient=error_obj.get("is_transient"),
        error_user_title=error_obj.get("error_user_title"),
        error_user_msg=error_obj.get("error_user_msg"),
        raw=payload,
    )


def map_meta_error(status_code: int, payload: Dict[str, Any]) -> MetaApiError:
    """Map an error payload into a typed exception."""

    context = build_error_context(payload)
    exc_cls = _ERROR_CODE_TO_EXCEPTION.get(context.code, MetaApiError)
    if context.is_transient and exc_cls is MetaApiError:
        exc_cls = MetaApiRateLimitError
    return exc_cls(context=context, status_code=status_code)


# Enhanced MCP Server Exception Hierarchy

class MCPError(Exception):
    """Base exception for all MCP server errors.
    
    Attributes:
        code: Error code for programmatic handling
        message: Human-readable error message
        details: Additional error context
    """
    
    def __init__(
        self, 
        message: str, 
        code: Optional[str] = None,
        details: Optional[Dict[str, Any]] = None
    ):
        super().__init__(message)
        self.message = message
        self.code = code or self.__class__.__name__
        self.details = details or {}
    
    def to_dict(self) -> Dict[str, Any]:
        """Convert error to dictionary format.
        
        Returns:
            Dictionary representation of the error
        """
        return {
            "code": self.code,
            "message": self.message,
            "type": self.__class__.__name__,
            "details": self.details,
        }


class ValidationError(MCPError):
    """Raised when input validation fails.
    
    Used for schema validation errors, invalid parameters, etc.
    """
    
    def __init__(self, message: str, field: Optional[str] = None, **details):
        super().__init__(message, code="VALIDATION_ERROR", details=details)
        if field:
            self.details["field"] = field


class AuthenticationError(MCPError):
    """Raised when authentication fails.
    
    Used for invalid tokens, expired credentials, etc.
    """
    
    def __init__(self, message: str = "Authentication failed", **details):
        super().__init__(message, code="AUTHENTICATION_ERROR", details=details)


class AuthorizationError(MCPError):
    """Raised when authorization fails.
    
    Used when authenticated but lacking permissions.
    """
    
    def __init__(self, message: str = "Authorization failed", resource: Optional[str] = None, **details):
        super().__init__(message, code="AUTHORIZATION_ERROR", details=details)
        if resource:
            self.details["resource"] = resource


class RateLimitError(MCPError):
    """Raised when rate limits are exceeded.
    
    Includes retry-after information when available.
    """
    
    def __init__(self, message: str = "Rate limit exceeded", retry_after: Optional[int] = None, **details):
        super().__init__(message, code="RATE_LIMIT_ERROR", details=details)
        if retry_after:
            self.details["retry_after"] = retry_after


class ConfigurationError(MCPError):
    """Raised when configuration is invalid or missing.
    
    Used during startup or when loading configuration.
    """
    
    def __init__(self, message: str, setting: Optional[str] = None, **details):
        super().__init__(message, code="CONFIGURATION_ERROR", details=details)
        if setting:
            self.details["setting"] = setting


class ResourceNotFoundError(MCPError):
    """Raised when a requested resource is not found.
    
    Used for missing campaigns, ad sets, ads, etc.
    """
    
    def __init__(self, message: str, resource_type: Optional[str] = None, resource_id: Optional[str] = None, **details):
        super().__init__(message, code="RESOURCE_NOT_FOUND", details=details)
        if resource_type:
            self.details["resource_type"] = resource_type
        if resource_id:
            self.details["resource_id"] = resource_id


class TransportError(MCPError):
    """Raised when transport layer encounters an error.
    
    Used for connection errors, protocol errors, etc.
    """
    
    def __init__(self, message: str, transport_type: Optional[str] = None, **details):
        super().__init__(message, code="TRANSPORT_ERROR", details=details)
        if transport_type:
            self.details["transport_type"] = transport_type


class EngineError(MCPError):
    """Raised when an engine encounters an error.
    
    Base class for engine-specific errors.
    """
    
    def __init__(self, message: str, engine: Optional[str] = None, operation: Optional[str] = None, **details):
        super().__init__(message, code="ENGINE_ERROR", details=details)
        if engine:
            self.details["engine"] = engine
        if operation:
            self.details["operation"] = operation


class CacheError(MCPError):
    """Raised when cache operations fail.
    
    Used for Redis connection errors, serialization errors, etc.
    """
    
    def __init__(self, message: str, operation: Optional[str] = None, **details):
        super().__init__(message, code="CACHE_ERROR", details=details)
        if operation:
            self.details["operation"] = operation


class CircuitBreakerError(MCPError):
    """Raised when circuit breaker is open.
    
    Indicates that a service is temporarily unavailable.
    """
    
    def __init__(self, message: str = "Circuit breaker is open", service: Optional[str] = None, **details):
        super().__init__(message, code="CIRCUIT_BREAKER_OPEN", details=details)
        if service:
            self.details["service"] = service


class TimeoutError(MCPError):
    """Raised when an operation times out.
    
    Used for request timeouts, operation timeouts, etc.
    """
    
    def __init__(self, message: str = "Operation timed out", timeout: Optional[float] = None, **details):
        super().__init__(message, code="TIMEOUT_ERROR", details=details)
        if timeout:
            self.details["timeout"] = timeout