"""Tests for exception handling."""

import pytest

from ticksupply.exceptions import (
    APIError,
    AuthenticationError,
    ConflictError,
    InvalidArgumentError,
    NetworkError,
    NotFoundError,
    PaymentRequiredError,
    PermissionDeniedError,
    RateLimitError,
    ServerError,
    TicksupplyError,
    raise_for_error_response,
)


class TestExceptionHierarchy:
    """Test exception class hierarchy."""

    def test_base_exception(self) -> None:
        """TicksupplyError is the base for all exceptions."""
        assert issubclass(APIError, TicksupplyError)
        assert issubclass(NetworkError, TicksupplyError)

    def test_api_error_subclasses(self) -> None:
        """All API errors inherit from APIError."""
        assert issubclass(AuthenticationError, APIError)
        assert issubclass(PermissionDeniedError, APIError)
        assert issubclass(NotFoundError, APIError)
        assert issubclass(ConflictError, APIError)
        assert issubclass(RateLimitError, APIError)
        assert issubclass(PaymentRequiredError, APIError)
        assert issubclass(InvalidArgumentError, APIError)
        assert issubclass(ServerError, APIError)


class TestAPIError:
    """Test APIError class."""

    def test_api_error_attributes(self) -> None:
        """APIError stores code, message, http_status, and trace_id."""
        error = APIError(
            code="not_found",
            message="Resource not found",
            http_status=404,
            trace_id="trace-123",
        )
        assert error.code == "not_found"
        assert error.message == "Resource not found"
        assert error.http_status == 404
        assert error.trace_id == "trace-123"

    def test_api_error_str(self) -> None:
        """APIError string includes code and message."""
        error = APIError(
            code="not_found",
            message="Resource not found",
            http_status=404,
        )
        assert "[not_found]" in str(error)
        assert "Resource not found" in str(error)

    def test_api_error_str_with_trace_id(self) -> None:
        """APIError string includes trace_id when present."""
        error = APIError(
            code="internal",
            message="Server error",
            http_status=500,
            trace_id="trace-456",
        )
        assert "trace-456" in str(error)


class TestRateLimitError:
    """Test RateLimitError class."""

    def test_retry_after(self) -> None:
        """RateLimitError stores retry_after."""
        error = RateLimitError(
            code="rate_limited",
            message="Too many requests",
            http_status=429,
            retry_after=60,
        )
        assert error.retry_after == 60

    def test_retry_after_in_str(self) -> None:
        """RateLimitError string includes retry_after."""
        error = RateLimitError(
            code="rate_limited",
            message="Too many requests",
            http_status=429,
            retry_after=30,
        )
        assert "30s" in str(error)


class TestNetworkError:
    """Test NetworkError class."""

    def test_network_error_with_cause(self) -> None:
        """NetworkError stores cause exception."""
        cause = ConnectionError("Connection refused")
        error = NetworkError("Request failed", cause=cause)
        assert error.cause is cause
        assert "Request failed" in str(error)


class TestRaiseForErrorResponse:
    """Test raise_for_error_response function."""

    def test_raises_not_found(self) -> None:
        """Raises NotFoundError for not_found code."""
        with pytest.raises(NotFoundError) as exc_info:
            raise_for_error_response(404, "not_found", "Resource not found")
        assert exc_info.value.http_status == 404

    def test_raises_authentication_error(self) -> None:
        """Raises AuthenticationError for unauthenticated code."""
        with pytest.raises(AuthenticationError) as exc_info:
            raise_for_error_response(401, "unauthenticated", "Invalid API key")
        assert exc_info.value.http_status == 401

    def test_raises_rate_limit_error(self) -> None:
        """Raises RateLimitError for rate_limited code."""
        with pytest.raises(RateLimitError) as exc_info:
            raise_for_error_response(429, "rate_limited", "Too many requests", retry_after=60)
        assert exc_info.value.retry_after == 60

    def test_raises_generic_api_error(self) -> None:
        """Raises APIError for unknown codes."""
        with pytest.raises(APIError) as exc_info:
            raise_for_error_response(400, "unknown_code", "Unknown error")
        assert exc_info.value.code == "unknown_code"
