"""Shared types, error classes, and client configuration."""

from __future__ import annotations

from enum import Enum
from typing import Any, Callable, Dict, List, Optional


class SkytellsError(Exception):
    """
    Raised for all Skytells API and network errors.

    Attributes:
        error_id:    Machine-readable error identifier (e.g. ``"UNAUTHORIZED"``).
        details:     Human-readable detail string with additional context.
        http_status: HTTP status code from the response (0 for network errors).

    Example::

        try:
            prediction = client.run("flux-pro", input={"prompt": "test"})
        except SkytellsError as e:
            print(e.error_id)    # "UNAUTHORIZED"
            print(e.http_status) # 401
            print(e.details)
    """

    def __init__(
        self,
        message: str,
        error_id: str,
        details: str,
        http_status: int = 0,
    ) -> None:
        super().__init__(message)
        self.error_id = error_id
        self.details = details
        self.http_status = http_status

    def __repr__(self) -> str:
        return (
            f"SkytellsError(message={str(self)!r}, error_id={self.error_id!r}, "
            f"http_status={self.http_status!r})"
        )


class ApiErrorId(str, Enum):
    """Known error identifiers returned by the Skytells API."""

    UNAUTHORIZED = "UNAUTHORIZED"
    INVALID_PARAMETER = "INVALID_PARAMETER"
    INVALID_DATE_FORMAT = "INVALID_DATE_FORMAT"
    INVALID_DATE_RANGE = "INVALID_DATE_RANGE"
    MODEL_NOT_FOUND = "MODEL_NOT_FOUND"
    INTERNAL_ERROR = "INTERNAL_ERROR"
    INVALID_INPUT = "INVALID_INPUT"
    INSUFFICIENT_CREDITS = "INSUFFICIENT_CREDITS"
    ACCOUNT_SUSPENDED = "ACCOUNT_SUSPENDED"
    PAYMENT_REQUIRED = "PAYMENT_REQUIRED"
    SECURITY_VIOLATION = "SECURITY_VIOLATION"
    RATE_LIMIT_EXCEEDED = "RATE_LIMIT_EXCEEDED"
    INFRASTRUCTURE_ERROR = "INFRASTRUCTURE_ERROR"
    # Client-side errors raised by the SDK itself
    PREDICTION_FAILED = "PREDICTION_FAILED"
    WAIT_TIMEOUT = "WAIT_TIMEOUT"
    REQUEST_TIMEOUT = "REQUEST_TIMEOUT"
    NETWORK_ERROR = "NETWORK_ERROR"
    SERVER_ERROR = "SERVER_ERROR"
    INVALID_JSON = "INVALID_JSON"


class RetryOptions:
    """
    Configuration for automatic request retries.

    Args:
        retries:    Number of retry attempts (default: ``0``).
        retry_delay: Delay in milliseconds between retries (default: ``1000``).
        retry_on:   HTTP status codes that trigger a retry
                    (default: ``[429, 500, 502, 503, 504]``).
    """

    DEFAULT_RETRY_ON: List[int] = [429, 500, 502, 503, 504]

    def __init__(
        self,
        retries: int = 0,
        retry_delay: int = 1000,
        retry_on: Optional[List[int]] = None,
    ) -> None:
        self.retries = retries
        self.retry_delay = retry_delay
        self.retry_on: List[int] = retry_on if retry_on is not None else list(self.DEFAULT_RETRY_ON)

    @classmethod
    def from_dict(cls, d: Dict[str, Any]) -> "RetryOptions":
        return cls(
            retries=d.get("retries", 0),
            retry_delay=d.get("retry_delay", 1000),
            retry_on=d.get("retry_on"),
        )


class ClientOptions:
    """
    Configuration options for :class:`~skytells.SkytellsClient`.

    Args:
        base_url: Override the default API base URL.
        timeout:  Request timeout in milliseconds (default: ``60000``).
        headers:  Custom headers to include in every request.
        retry:    Retry configuration as a :class:`RetryOptions` instance or dict.
    """

    def __init__(
        self,
        base_url: Optional[str] = None,
        timeout: int = 60000,
        headers: Optional[Dict[str, str]] = None,
        retry: Optional[Any] = None,
    ) -> None:
        self.base_url = base_url
        self.timeout = timeout
        self.headers: Dict[str, str] = headers or {}
        if retry is None:
            self.retry = RetryOptions()
        elif isinstance(retry, dict):
            self.retry = RetryOptions.from_dict(retry)
        else:
            self.retry = retry


class Pagination:
    """Pagination metadata included in paginated API responses."""

    __slots__ = ("current_page", "per_page", "total", "last_page")

    def __init__(self, data: Dict[str, Any]) -> None:
        self.current_page: int = data.get("current_page", 1)
        self.per_page: int = data.get("per_page", 0)
        self.total: int = data.get("total", 0)
        self.last_page: int = data.get("last_page", 1)

    def to_dict(self) -> Dict[str, Any]:
        return {
            "current_page": self.current_page,
            "per_page": self.per_page,
            "total": self.total,
            "last_page": self.last_page,
        }

    def __repr__(self) -> str:
        return (
            f"Pagination(current_page={self.current_page}, total={self.total}, "
            f"last_page={self.last_page})"
        )


class PaginatedResponse:
    """
    A paginated API response wrapping a list of items.

    Attributes:
        data:       List of response items (raw dicts).
        pagination: :class:`Pagination` metadata.
    """

    def __init__(self, data: Dict[str, Any]) -> None:
        self.data: List[Dict[str, Any]] = data.get("data", [])
        self.pagination = Pagination(data.get("pagination", {}))

    def to_dict(self) -> Dict[str, Any]:
        return {
            "data": self.data,
            "pagination": self.pagination.to_dict(),
        }

    def __repr__(self) -> str:
        return f"PaginatedResponse(count={len(self.data)}, pagination={self.pagination!r})"


# Type alias for the on_progress callback
OnProgressCallback = Callable[[Dict[str, Any]], None]
