"""Tests for MLD client exception hierarchy."""

import pytest

from mld_sdk.client._exceptions import (
    AuthenticationError,
    ConflictError,
    MLDAPIError,
    MLDConnectionError,
    MLDPermissionError,
    MLDValidationError,
    NotFoundError,
    RateLimitError,
    ServerError,
    raise_for_status,
)


class TestExceptionHierarchy:
    def test_all_exceptions_inherit_from_mld_api_error(self):
        for cls in (
            AuthenticationError,
            MLDPermissionError,
            NotFoundError,
            ConflictError,
            MLDValidationError,
            RateLimitError,
            ServerError,
            MLDConnectionError,
        ):
            assert issubclass(cls, MLDAPIError)

    def test_base_error_attributes(self):
        err = MLDAPIError("oops", status_code=418, body={"detail": "teapot"})
        assert err.message == "oops"
        assert err.status_code == 418
        assert err.body == {"detail": "teapot"}
        assert str(err) == "[418] oops"

    def test_base_error_no_status(self):
        err = MLDAPIError("network fail")
        assert str(err) == "network fail"
        assert err.status_code is None

    def test_authentication_error_defaults(self):
        err = AuthenticationError()
        assert err.status_code == 401
        assert "Authentication" in err.message

    def test_connection_error_no_status(self):
        err = MLDConnectionError("timeout")
        assert err.status_code is None


class TestRaiseForStatus:
    def test_2xx_does_not_raise(self):
        raise_for_status(200)
        raise_for_status(201)
        raise_for_status(204)

    @pytest.mark.parametrize(
        "status,exc_cls",
        [
            (401, AuthenticationError),
            (403, MLDPermissionError),
            (404, NotFoundError),
            (409, ConflictError),
            (422, MLDValidationError),
            (429, RateLimitError),
        ],
    )
    def test_mapped_status_codes(self, status: int, exc_cls: type):
        with pytest.raises(exc_cls) as exc_info:
            raise_for_status(status, {"detail": "test"})
        assert exc_info.value.status_code == status
        assert exc_info.value.message == "test"

    def test_5xx_raises_server_error(self):
        with pytest.raises(ServerError) as exc_info:
            raise_for_status(503, {"detail": "unavailable"})
        assert exc_info.value.status_code == 503

    def test_unknown_status_raises_base(self):
        with pytest.raises(MLDAPIError) as exc_info:
            raise_for_status(418)
        assert exc_info.value.status_code == 418
        assert "418" in str(exc_info.value)

    def test_body_detail_used_as_message(self):
        with pytest.raises(NotFoundError) as exc_info:
            raise_for_status(404, {"detail": "Experiment 99 not found"})
        assert exc_info.value.message == "Experiment 99 not found"

    def test_body_message_field_fallback(self):
        with pytest.raises(NotFoundError) as exc_info:
            raise_for_status(404, {"message": "not here"})
        assert exc_info.value.message == "not here"
