"""Exceptions."""

from json import JSONDecodeError

from httpx import HTTPError, HTTPStatusError, RequestError, codes
from msgspec import Struct
from msgspec.json import Decoder as JsonDecoder


class ConfigurationError(Exception):
    """Configuration error."""


class AuthenticationError(Exception):
    """Authentication error."""


class ValidationErrorDetail(Struct):
    """Validation Error."""

    loc: list[str | int]
    """Location."""
    msg: str
    """Message."""
    type: str
    """Type."""


class ValidationErrorMessage(Struct):
    """Validation Error Message."""

    detail: list[ValidationErrorDetail]


_validation_error_message_decoder = JsonDecoder(ValidationErrorMessage)


class ApiException(Exception):
    """Slices API Exception base class."""

    _httpx_error: HTTPError | None = None

    def __init__(
        self,
        *args: object,
        httpx_error: HTTPError | None = None,
    ) -> None:
        """Create an API exception."""
        super().__init__(*args)

        self._httpx_error = httpx_error

    @property
    def code(self) -> int | None:
        """Return the HTTP status code, if relevant, otherwise None."""
        return (
            self._httpx_error.response.status_code
            if self._httpx_error and isinstance(self._httpx_error, HTTPStatusError)
            else None
        )

    def __str__(self) -> str:
        """Return string representation of error."""

        if isinstance(self._httpx_error, HTTPStatusError):
            try:
                method = self._httpx_error.request.method
                url = str(self._httpx_error.request.url)
                at = " (for " + method + " " + url + ")"
            except:  # noqa E722
                at = ""
            try:
                response_json = self._httpx_error.response.json()
                response_txt = response_json.get("detail", "no details available.")
            except JSONDecodeError:
                response_txt = self._httpx_error.response.text
            return str(self.__class__.__name__) + at + ": " + str(response_txt)

        if self._httpx_error:
            return self.__class__.__name__ + ": " + self._httpx_error.__str__()

        return super().__str__()


class ApiResponseError(ApiException):
    """API Response Error."""

    _httpx_status_error: HTTPStatusError

    def __init__(self, *args: object, httpx_error: HTTPStatusError) -> None:
        """Create an API response error."""
        super().__init__(*args, httpx_error=httpx_error)
        self._httpx_status_error = httpx_error


class ApiConnectError(ApiException):
    """API Connection Error."""

    url: str

    def __init__(self, url: str) -> None:
        """Create an API Connection error."""
        super().__init__(f"Connection Error connection to {url}", httpx_error=None)
        self.url = url


class ValidationError(ApiResponseError):
    """Validation Error.

    The server rejected the request due to validation errors.
    """

    details: list[ValidationErrorDetail]
    """Details."""

    def __init__(
        self,
        details: list[ValidationErrorDetail],
        *,
        httpx_error: HTTPStatusError,
    ) -> None:
        """Create a validation error."""
        super().__init__("Validation error", httpx_error=httpx_error)
        self.details = details


class BadRequestException(ApiResponseError):
    """Bad Request Exception."""

    detail: str

    def __init__(self, *args: object, httpx_error: HTTPStatusError) -> None:
        """Create a bad request exception."""
        super().__init__(*args, httpx_error=httpx_error)

        if httpx_error:
            try:
                response_json = httpx_error.response.json()
                response_txt = response_json.get("detail", "no details available.")
            except JSONDecodeError:
                response_txt = httpx_error.response.text
            self.detail = response_txt


class NotFoundException(ApiResponseError):
    """Not Found Exception."""


class UnauthorizedException(ApiResponseError):
    """Unauthorized Exception."""


class ForbiddenException(ApiResponseError):
    """Forbidden Exception."""


class ConflictException(ApiResponseError):
    """Conflict Exception."""


class ServiceException(ApiResponseError):
    """Service Exception.

    The service encountered an internal error.
    """


def from_httpx_exception(err: HTTPError) -> ApiException:  # noqa: PLR0911
    """Convert an HTTPX exception to a Slices API exception."""

    if isinstance(err, RequestError):
        return ApiException("Request error", httpx_error=err)

    assert isinstance(err, HTTPStatusError)

    if err.response.status_code == codes.BAD_REQUEST.value:
        return BadRequestException(httpx_error=err)
    if err.response.status_code == codes.NOT_FOUND.value:
        return NotFoundException(httpx_error=err)
    if err.response.status_code == codes.UNAUTHORIZED.value:
        return UnauthorizedException(httpx_error=err)
    if err.response.status_code == codes.FORBIDDEN.value:
        return ForbiddenException(httpx_error=err)
    if err.response.status_code == codes.CONFLICT.value:
        return ConflictException(httpx_error=err)
    if err.response.status_code == codes.UNPROCESSABLE_ENTITY.value:
        return ValidationError(
            _validation_error_message_decoder.decode(err.response.content).detail,
            httpx_error=err,
        )

    if codes.is_server_error(err.response.status_code):
        return ServiceException(httpx_error=err)

    return ApiException(
        f"Unexpected HTTP status code: {err.response.status_code}",
        httpx_error=err,
    )
