"""Tests for ErrorEnvelope exception-based construction and StatusCodeMapping."""

from __future__ import annotations

from dr_langfuse.errors.codes import ErrorCode, StatusCodeMapping
from dr_langfuse.errors.error_envelope import ErrorEnvelope


class _ApiErrorLike(RuntimeError):
    def __init__(self, status_code: int) -> None:
        super().__init__(f"api status {status_code}")
        self.status_code = status_code


class _ConnectErrorLike(RuntimeError):
    def __init__(self, message: str = "transport failure") -> None:
        super().__init__(message)
        self.request = object()


def test_auth_status_code_maps_to_auth() -> None:
    envelope = ErrorEnvelope(exception=_ApiErrorLike(401), operation="test")
    assert envelope.code == ErrorCode.AUTH
    assert envelope.retriable is False
    assert envelope.details.get("status_code") == 401


def test_unavailable_status_code_maps_to_unavailable() -> None:
    envelope = ErrorEnvelope(exception=_ApiErrorLike(503), operation="test")
    assert envelope.code == ErrorCode.UNAVAILABLE
    assert envelope.retriable is True


def test_timeout_status_code_maps_to_timeout() -> None:
    envelope = ErrorEnvelope(exception=_ApiErrorLike(408), operation="test")
    assert envelope.code == ErrorCode.TIMEOUT
    assert envelope.retriable is True


def test_malformed_request_status_code() -> None:
    envelope = ErrorEnvelope(exception=_ApiErrorLike(422), operation="test")
    assert envelope.code == ErrorCode.MALFORMED_REQUEST
    assert envelope.retriable is False


def test_transport_error_maps_to_unavailable() -> None:
    envelope = ErrorEnvelope(exception=_ConnectErrorLike(), operation="test")
    assert envelope.code == ErrorCode.UNAVAILABLE
    assert envelope.retriable is True


def test_transport_error_in_context_chain_maps_to_unavailable() -> None:
    root = RuntimeError("outer")
    root.__cause__ = RuntimeError("non-transport cause")
    root.__context__ = _ConnectErrorLike()
    envelope = ErrorEnvelope(exception=root, operation="test")
    assert envelope.code == ErrorCode.UNAVAILABLE
    assert envelope.retriable is True


def test_unknown_exception_maps_to_internal_error() -> None:
    envelope = ErrorEnvelope(exception=RuntimeError("unexpected"), operation="test")
    assert envelope.code == ErrorCode.INTERNAL_ERROR
    assert envelope.retriable is False


def test_unmapped_status_code_falls_back_to_internal_error() -> None:
    """Unmapped HTTP status codes fall back to INTERNAL_ERROR (not in auth/malformed/unavailable)."""
    envelope = ErrorEnvelope(exception=_ApiErrorLike(418), operation="test")
    assert envelope.code == ErrorCode.INTERNAL_ERROR
    assert envelope.retriable is False


def test_custom_status_code_mapping() -> None:
    custom = StatusCodeMapping(auth={999})
    envelope = ErrorEnvelope(
        exception=_ApiErrorLike(999), operation="test", status_codes=custom
    )
    assert envelope.code == ErrorCode.AUTH


def test_does_not_misclassify_author_text_as_auth() -> None:
    envelope = ErrorEnvelope(
        exception=RuntimeError("author field missing in response"), operation="test"
    )
    assert envelope.code == ErrorCode.INTERNAL_ERROR


def test_exception_fields_excluded_from_serialization() -> None:
    envelope = ErrorEnvelope(exception=_ApiErrorLike(401), operation="test")
    dumped = envelope.model_dump(mode="json")
    assert "exception" not in dumped
    assert "operation" not in dumped
    assert "status_codes" not in dumped


def test_explicit_code_overrides_auto_derived() -> None:
    envelope = ErrorEnvelope(
        exception=_ApiErrorLike(401),
        operation="test",
        code=ErrorCode.INTERNAL_ERROR,
        message="custom message",
    )
    assert envelope.code == ErrorCode.INTERNAL_ERROR
    assert envelope.message == "custom message"
