"""
Error taxonomy for AMI SDK.

Custom exceptions that mirror API error responses with proper HTTP status mapping.
"""
from typing import Optional, Any, Dict
from httpx import Response


class AMIError(Exception):
    """
    Base exception for all AMI SDK errors.

    Attributes:
        message: Error message
        status_code: HTTP status code (if applicable)
        details: Additional error details
    """

    def __init__(
        self,
        message: str,
        status_code: Optional[int] = None,
        details: Optional[Dict[str, Any]] = None
    ):
        self.message = message
        self.status_code = status_code
        self.details = details or {}
        super().__init__(message)


# ============================================================================
# HTTP 4xx Client Errors
# ============================================================================

class AMIUnauthorized(AMIError):
    """401 Unauthorized - Invalid or missing API key."""

    def __init__(self, message: str = "Unauthorized", **kwargs):
        super().__init__(message, status_code=401, **kwargs)


class AMIForbidden(AMIError):
    """403 Forbidden - Valid auth but insufficient permissions."""

    def __init__(self, message: str = "Forbidden", **kwargs):
        super().__init__(message, status_code=403, **kwargs)


class AMINotFound(AMIError):
    """404 Not Found - Resource does not exist."""

    def __init__(self, message: str = "Not Found", **kwargs):
        super().__init__(message, status_code=404, **kwargs)


class AMIBadRequest(AMIError):
    """400 Bad Request - Invalid request (validation, mixed types, etc.)."""

    def __init__(self, message: str = "Bad Request", **kwargs):
        super().__init__(message, status_code=400, **kwargs)


class AMIConflict(AMIError):
    """409 Conflict - Resource conflict (EMU lifecycle, versioning)."""

    def __init__(self, message: str = "Conflict", **kwargs):
        super().__init__(message, status_code=409, **kwargs)


class AMIPreconditionFailed(AMIError):
    """412 Precondition Failed - Version pinning mismatch."""

    def __init__(self, message: str = "Precondition Failed", **kwargs):
        super().__init__(message, status_code=412, **kwargs)


class AMIUnprocessable(AMIError):
    """422 Unprocessable Entity - Invalid EMU predicate/policy/action."""

    def __init__(self, message: str = "Unprocessable Entity", **kwargs):
        super().__init__(message, status_code=422, **kwargs)


class AMIRateLimited(AMIError):
    """429 Too Many Requests - Rate limit exceeded."""

    def __init__(
        self,
        message: str = "Rate Limited",
        retry_after: Optional[int] = None,
        **kwargs
    ):
        super().__init__(message, status_code=429, **kwargs)
        self.retry_after = retry_after  # seconds


# ============================================================================
# HTTP 5xx Server Errors
# ============================================================================

class AMIServerError(AMIError):
    """5xx Server Error - Server-side failure."""

    def __init__(self, message: str = "Server Error", status_code: int = 500, **kwargs):
        super().__init__(message, status_code=status_code, **kwargs)


# ============================================================================
# Network Errors (not HTTP)
# ============================================================================

class AMINetworkError(AMIError):
    """Network-level error (connection refused, DNS failure, etc.)."""
    pass


class AMITimeoutError(AMIError):
    """Request timeout error."""
    pass


# ============================================================================
# Error Mapping
# ============================================================================

def map_http_error(response: Response) -> AMIError:
    """
    Map HTTP response to appropriate SDK exception.

    Args:
        response: httpx Response object

    Returns:
        Appropriate AMIError subclass
    """
    status_code = response.status_code

    # Extract error message from response
    try:
        body = response.json()
        message = body.get("detail", body.get("message", f"HTTP {status_code}"))
        details = {k: v for k, v in body.items() if k not in ("detail", "message")}
    except Exception:
        # Fallback if JSON parsing fails
        message = f"HTTP {status_code}: {response.text[:200]}" if response.text else f"HTTP {status_code}"
        details = {}

    # Map to specific exception
    error_map = {
        401: AMIUnauthorized,
        403: AMIForbidden,
        404: AMINotFound,
        400: AMIBadRequest,
        409: AMIConflict,
        412: AMIPreconditionFailed,
        422: AMIUnprocessable,
        429: AMIRateLimited,
    }

    # Handle 5xx errors
    if 500 <= status_code < 600:
        return AMIServerError(message, status_code=status_code, details=details)

    # Handle 429 with Retry-After header
    if status_code == 429:
        retry_after = None
        if "Retry-After" in response.headers:
            try:
                retry_after = int(response.headers["Retry-After"])
            except ValueError:
                pass
        return AMIRateLimited(message, retry_after=retry_after, details=details)

    # Use mapped error or fallback to generic AMIError
    if status_code in error_map:
        error_class = error_map[status_code]
        return error_class(message, details=details)
    else:
        # For unknown status codes, include status in message
        return AMIError(f"HTTP {status_code}: {message}", status_code=status_code, details=details)
