"""Exception hierarchy for Pinecone SDK."""

from __future__ import annotations

from typing import Any


class PineconeError(Exception):
    """Base exception for all Pinecone SDK errors."""

    def __init__(self, message: str):
        self.message = message
        super().__init__(message)

    def __repr__(self) -> str:
        """Return detailed representation for debugging."""
        return f"{self.__class__.__name__}(message={self.message!r})"


class APIConnectionError(PineconeError):
    """Failed to connect to Pinecone API.

    Raised when a network or connection error prevents the request from reaching
    the Pinecone API. This includes DNS failures, connection timeouts, and other
    network-level issues.
    """


class TimeoutError(APIConnectionError):
    """Request timed out.

    Raised when a request exceeds the configured timeout duration.
    """

    def __init__(self, timeout: float):
        super().__init__(f"Request timed out after {timeout}s")
        self.timeout = timeout

    def __repr__(self) -> str:
        """Return detailed representation for debugging."""
        return f"{self.__class__.__name__}(message={self.message!r}, timeout={self.timeout})"


class ConnectionError(APIConnectionError):
    """Connection failed.

    Raised when the underlying connection to the Pinecone API fails.
    Check your network connection and firewall settings.
    """

    def __init__(self, message: str, cause: Exception | None = None):
        super().__init__(message)
        self.cause = cause

    def __repr__(self) -> str:
        """Return detailed representation for debugging."""
        parts = [f"{self.__class__.__name__}(message={self.message!r}"]
        if self.cause:
            parts.append(f", cause={self.cause!r}")
        parts.append(")")
        return "".join(parts)


class APIError(PineconeError):
    """Server returned an error response.

    Base class for all HTTP error responses from the Pinecone API.
    Contains structured information about the error including status code,
    request ID, error code, and the original request details.

    Attributes:
        message: Human-readable error message
        status_code: HTTP status code (e.g., 400, 401, 404)
        response_body: Raw response body from the server
        request_id: Unique request ID for support/debugging
        error_code: Specific error code from API (if available)
        url: URL that was requested
        method: HTTP method used (GET, POST, etc.)
    """

    def __init__(
        self,
        message: str,
        status_code: int,
        response_body: str | None = None,
        *,
        request_id: str | None = None,
        error_code: str | None = None,
        url: str | None = None,
        method: str | None = None,
    ):
        super().__init__(message)
        self.status_code = status_code
        self.response_body = response_body
        self.request_id = request_id
        self.error_code = error_code
        self.url = url
        self.method = method

    def __repr__(self) -> str:
        """Return detailed representation for debugging."""
        parts = [f"{self.__class__.__name__}("]
        parts.append(f"message={self.message!r}")
        parts.append(f", status_code={self.status_code}")
        if self.request_id:
            parts.append(f", request_id={self.request_id!r}")
        if self.error_code:
            parts.append(f", error_code={self.error_code!r}")
        if self.url:
            parts.append(f", url={self.url!r}")
        if self.method:
            parts.append(f", method={self.method!r}")
        parts.append(")")
        return "".join(parts)


class BadRequestError(APIError):
    """400 Bad Request.

    Raised when the request is malformed or contains invalid parameters.
    Check the error message for details about which parameter is invalid.
    """

    def __init__(
        self,
        message: str | None = None,
        response_body: str | None = None,
        **kwargs: Any,
    ) -> None:
        if message is None:
            message = "Bad request. Check your request parameters."
        super().__init__(message, 400, response_body, **kwargs)


class UnauthorizedError(APIError):
    """401 Unauthorized - invalid API key.

    Raised when authentication fails, typically due to an invalid or expired API key.
    Get your API key from https://app.pinecone.io
    """

    def __init__(self, message: str | None = None, **kwargs: Any) -> None:
        if message is None:
            message = (
                "Invalid API key. Verify your API key is correct and active. "
                "Get your API key from https://app.pinecone.io"
            )
        super().__init__(message, 401, **kwargs)


class ForbiddenError(APIError):
    """403 Forbidden.

    Raised when the API key is valid but lacks permission for the requested operation.
    Check your plan limits and permissions at https://app.pinecone.io
    """

    def __init__(self, message: str | None = None, **kwargs: Any) -> None:
        if message is None:
            message = (
                "Permission denied. Your API key lacks permission for this operation. "
                "Check your plan limits at https://app.pinecone.io"
            )
        super().__init__(message, 403, **kwargs)


class NotFoundError(APIError):
    """404 Not Found.

    Raised when the requested resource (index, model, etc.) does not exist.
    Use the appropriate list method to see available resources.
    """

    def __init__(self, message: str | None = None, **kwargs: Any) -> None:
        if message is None:
            message = "Resource not found. Use list methods to see available resources."
        super().__init__(message, 404, **kwargs)


class ConflictError(APIError):
    """409 Conflict.

    Raised when the resource already exists or conflicts with existing state.
    For index creation, this typically means an index with the name already exists.
    """

    def __init__(self, message: str | None = None, **kwargs: Any) -> None:
        if message is None:
            message = "Resource already exists or conflicts with existing state."
        super().__init__(message, 409, **kwargs)


class RateLimitError(APIError):
    """429 Rate Limit Exceeded.

    Raised when you exceed the rate limit for your plan.
    Implement exponential backoff or upgrade your plan at https://app.pinecone.io

    Attributes:
        retry_after: Number of seconds to wait before retrying (if provided by server)
    """

    def __init__(
        self, retry_after: int | None = None, message: str | None = None, **kwargs: Any
    ) -> None:
        if message is None:
            message = "Rate limit exceeded"

        # Always append retry_after to message if available
        if retry_after and f"{retry_after}" not in message:
            message += f". Retry after {retry_after}s"
        elif not retry_after and message == "Rate limit exceeded":
            message += ". Implement exponential backoff or upgrade your plan."

        super().__init__(message, 429, **kwargs)
        self.retry_after = retry_after

    def __repr__(self) -> str:
        """Return detailed representation for debugging."""
        parts = [f"{self.__class__.__name__}("]
        parts.append(f"message={self.message!r}")
        parts.append(f", status_code={self.status_code}")
        if self.retry_after is not None:
            parts.append(f", retry_after={self.retry_after}")
        if self.request_id:
            parts.append(f", request_id={self.request_id!r}")
        if self.error_code:
            parts.append(f", error_code={self.error_code!r}")
        parts.append(")")
        return "".join(parts)


class ServerError(APIError):
    """500+ Server Error.

    Raised when the Pinecone API encounters an internal error.
    These errors are typically transient - retry with exponential backoff.
    If the issue persists, contact support with the request_id.
    """

    def __init__(self, message: str | None = None, status_code: int = 500, **kwargs: Any) -> None:
        if message is None:
            message = (
                f"Server error (HTTP {status_code}). This is typically transient - "
                "retry with exponential backoff. If the issue persists, contact support."
            )
        super().__init__(message, status_code, **kwargs)


class ConfigurationError(PineconeError):
    """Invalid configuration.

    Raised when SDK configuration is invalid or incomplete.
    Check your configuration parameters and environment variables.
    """


class InvalidAPIKeyError(ConfigurationError):
    """API key is missing or malformed."""

    def __init__(self) -> None:
        super().__init__("API key is required. Set PINECONE_API_KEY or pass api_key parameter.")


class InvalidConfigError(ConfigurationError):
    """Configuration value is invalid.

    Raised when a configuration parameter has an invalid value.

    Attributes:
        field: Name of the invalid configuration field
        value: The invalid value that was provided
    """

    def __init__(self, field: str, value: object, reason: str):
        super().__init__(f"Invalid {field}={value!r}: {reason}")
        self.field = field
        self.value = value

    def __repr__(self) -> str:
        """Return detailed representation for debugging."""
        return (
            f"{self.__class__.__name__}(message={self.message!r}, "
            f"field={self.field!r}, value={self.value!r})"
        )
