from __future__ import annotations

from shipmail import (
    APIConnectionError,
    AuthenticationError,
    AuthorizationError,
    ConflictError,
    InternalServerError,
    NotFoundError,
    QuotaExceededError,
    RateLimitError,
    ShipMailError,
    ValidationError,
    WebhookVerificationError,
)
from shipmail._errors import map_error_from_response


class TestMapErrorFromResponse:
    def test_maps_authentication_error(self) -> None:
        body = {"error": {"type": "authentication_error", "message": "Invalid API key", "request_id": "req_1"}}
        err = map_error_from_response(401, body)
        assert isinstance(err, AuthenticationError)
        assert str(err) == "Invalid API key"
        assert err.status == 401
        assert err.type == "authentication_error"
        assert err.request_id == "req_1"
        assert err.retryable is False

    def test_maps_authorization_error(self) -> None:
        body = {"error": {"type": "authorization_error", "message": "Forbidden", "request_id": "req_2"}}
        err = map_error_from_response(403, body)
        assert isinstance(err, AuthorizationError)
        assert str(err) == "Forbidden"
        assert err.status == 403
        assert err.type == "authorization_error"
        assert err.retryable is False

    def test_maps_validation_error_with_details(self) -> None:
        body = {
            "error": {
                "type": "validation_error",
                "message": "Validation failed",
                "request_id": "req_3",
                "details": [
                    {"field": "name", "message": "Name is required"},
                    {"field": "address", "message": "Invalid email address"},
                ],
            }
        }
        err = map_error_from_response(422, body)
        assert isinstance(err, ValidationError)
        assert str(err) == "Validation failed"
        assert err.status == 422
        assert err.type == "validation_error"
        assert err.retryable is False
        assert err.details is not None
        assert len(err.details) == 2
        assert err.details[0]["field"] == "name"
        assert err.details[0]["message"] == "Name is required"
        assert err.details[1]["field"] == "address"
        assert err.details[1]["message"] == "Invalid email address"

    def test_maps_validation_error_without_details(self) -> None:
        body = {"error": {"type": "validation_error", "message": "Invalid input"}}
        err = map_error_from_response(422, body)
        assert isinstance(err, ValidationError)
        assert err.details is None

    def test_maps_not_found(self) -> None:
        body = {"error": {"type": "not_found", "message": "Domain not found", "request_id": "req_4"}}
        err = map_error_from_response(404, body)
        assert isinstance(err, NotFoundError)
        assert str(err) == "Domain not found"
        assert err.status == 404
        assert err.retryable is False

    def test_maps_rate_limit_error_with_retry_after(self) -> None:
        body = {
            "error": {
                "type": "rate_limit_error",
                "message": "Too many requests",
                "request_id": "req_5",
                "retry_after": 30,
            }
        }
        err = map_error_from_response(429, body)
        assert isinstance(err, RateLimitError)
        assert str(err) == "Too many requests"
        assert err.status == 429
        assert err.type == "rate_limit_error"
        assert err.retryable is True
        assert err.retry_after == 30

    def test_maps_rate_limit_error_without_retry_after(self) -> None:
        body = {"error": {"type": "rate_limit_error", "message": "Slow down"}}
        err = map_error_from_response(429, body)
        assert isinstance(err, RateLimitError)
        assert err.retry_after is None

    def test_maps_conflict(self) -> None:
        body = {"error": {"type": "conflict", "message": "Domain already exists", "request_id": "req_6"}}
        err = map_error_from_response(409, body)
        assert isinstance(err, ConflictError)
        assert str(err) == "Domain already exists"
        assert err.status == 409
        assert err.retryable is False

    def test_maps_internal_error(self) -> None:
        body = {"error": {"type": "internal_error", "message": "Something went wrong", "request_id": "req_7"}}
        err = map_error_from_response(500, body)
        assert isinstance(err, InternalServerError)
        assert str(err) == "Something went wrong"
        assert err.status == 500
        assert err.retryable is True

    def test_uses_request_id_parameter_over_body(self) -> None:
        body = {"error": {"type": "not_found", "message": "Not found", "request_id": "req_body"}}
        err = map_error_from_response(404, body, "req_param")
        assert err.request_id == "req_param"

    def test_falls_back_to_body_request_id_when_parameter_is_none(self) -> None:
        body = {"error": {"type": "not_found", "message": "Not found", "request_id": "req_body"}}
        err = map_error_from_response(404, body)
        assert err.request_id == "req_body"

    def test_returns_generic_error_for_unknown_type(self) -> None:
        body = {"error": {"type": "unknown_type", "message": "Unknown error"}}
        err = map_error_from_response(418, body)
        assert isinstance(err, ShipMailError)
        assert not isinstance(err, AuthenticationError)
        assert not isinstance(err, NotFoundError)
        assert str(err) == "Unknown error"
        assert err.status == 418
        assert err.retryable is False

    def test_generic_error_is_retryable_for_5xx(self) -> None:
        body = {"error": {"type": "unknown_type", "message": "Unknown server error"}}
        err = map_error_from_response(503, body)
        assert err.retryable is True


class TestErrorClasses:
    def test_shipmail_error_defaults_retryable_to_false(self) -> None:
        err = ShipMailError("test")
        assert err.retryable is False
        assert err.status is None
        assert err.type is None
        assert err.request_id is None

    def test_connection_error_is_retryable(self) -> None:
        err = APIConnectionError("Connection refused")
        assert err.retryable is True
        assert err.status is None

    def test_webhook_verification_error_is_not_retryable(self) -> None:
        err = WebhookVerificationError("Invalid signature")
        assert err.retryable is False

    def test_all_error_classes_extend_shipmail_error(self) -> None:
        assert isinstance(AuthenticationError("test"), ShipMailError)
        assert isinstance(AuthorizationError("test"), ShipMailError)
        assert isinstance(ValidationError("test"), ShipMailError)
        assert isinstance(NotFoundError("test"), ShipMailError)
        assert isinstance(RateLimitError("test"), ShipMailError)
        assert isinstance(ConflictError("test"), ShipMailError)
        assert isinstance(InternalServerError("test"), ShipMailError)
        assert isinstance(APIConnectionError("test"), ShipMailError)
        assert isinstance(WebhookVerificationError("test"), ShipMailError)
        assert isinstance(QuotaExceededError("test"), ShipMailError)

    def test_all_error_classes_extend_exception(self) -> None:
        assert isinstance(ShipMailError("test"), Exception)
        assert isinstance(AuthenticationError("test"), Exception)
        assert isinstance(APIConnectionError("test"), Exception)
        assert isinstance(WebhookVerificationError("test"), Exception)
        assert isinstance(NotFoundError("test"), Exception)
        assert isinstance(RateLimitError("test"), Exception)
        assert isinstance(InternalServerError("test"), Exception)
