"""Tests for error handling and HTTP status → exception mapping."""

import httpx
import pytest

from goodmem.errors import (
    APIError,
    AuthenticationError,
    BadRequestError,
    ConflictError,
    InternalServerError,
    NotFoundError,
    PermissionDeniedError,
    RateLimitError,
    UnprocessableEntityError,
    raise_for_status,
)


def _make_response(status_code: int, body: str = "error body") -> httpx.Response:
    return httpx.Response(status_code=status_code, text=body)


def test_200_no_error():
    raise_for_status(_make_response(200))


def test_201_no_error():
    raise_for_status(_make_response(201))


def test_204_no_error():
    raise_for_status(_make_response(204))


def test_400_raises_bad_request_error():
    with pytest.raises(BadRequestError) as exc_info:
        raise_for_status(_make_response(400, '{"detail": "bad request"}'))
    assert exc_info.value.status_code == 400
    assert exc_info.value.body == '{"detail": "bad request"}'


def test_401_raises_authentication_error():
    with pytest.raises(AuthenticationError) as exc_info:
        raise_for_status(_make_response(401))
    assert exc_info.value.status_code == 401
    assert "error body" in exc_info.value.body


def test_403_raises_permission_denied_error():
    with pytest.raises(PermissionDeniedError) as exc_info:
        raise_for_status(_make_response(403))
    assert exc_info.value.status_code == 403


def test_404_raises_not_found_error():
    with pytest.raises(NotFoundError) as exc_info:
        raise_for_status(_make_response(404))
    assert exc_info.value.status_code == 404


def test_409_raises_conflict_error():
    with pytest.raises(ConflictError) as exc_info:
        raise_for_status(_make_response(409))
    assert exc_info.value.status_code == 409


def test_422_raises_unprocessable_entity_error():
    with pytest.raises(UnprocessableEntityError) as exc_info:
        raise_for_status(_make_response(422))
    assert exc_info.value.status_code == 422


def test_429_raises_rate_limit_error():
    with pytest.raises(RateLimitError) as exc_info:
        raise_for_status(_make_response(429))
    assert exc_info.value.status_code == 429


def test_500_raises_internal_server_error():
    with pytest.raises(InternalServerError) as exc_info:
        raise_for_status(_make_response(500))
    assert exc_info.value.status_code == 500


def test_502_raises_internal_server_error():
    with pytest.raises(InternalServerError) as exc_info:
        raise_for_status(_make_response(502))
    assert exc_info.value.status_code == 502


def test_418_raises_generic_api_error():
    """Unknown 4xx maps to base APIError."""
    with pytest.raises(APIError) as exc_info:
        raise_for_status(_make_response(418, "I'm a teapot"))
    assert exc_info.value.status_code == 418
    assert "I'm a teapot" in exc_info.value.body


def test_error_body_preserved():
    with pytest.raises(BadRequestError) as exc_info:
        raise_for_status(_make_response(400, '{"detail": "bad request"}'))
    assert exc_info.value.body == '{"detail": "bad request"}'


def test_error_message_includes_status():
    with pytest.raises(BadRequestError) as exc_info:
        raise_for_status(_make_response(400, "bad"))
    assert "400" in str(exc_info.value)


def test_error_body_truncated_in_message():
    """Long bodies are truncated to 500 chars in the message."""
    long_body = "x" * 1000
    with pytest.raises(BadRequestError) as exc_info:
        raise_for_status(_make_response(400, long_body))
    # The full body is preserved in .body
    assert len(exc_info.value.body) == 1000
    # But the message is truncated
    assert len(str(exc_info.value)) <= 510  # "HTTP 400: " + 500


# ---------------------------------------------------------------------------
# Regression W1: APIError.response attribute populated by raise_for_status
# ---------------------------------------------------------------------------


def test_response_attribute_populated_on_400():
    """raise_for_status must populate exc.response with the httpx.Response."""
    resp = _make_response(400, "bad request")
    with pytest.raises(BadRequestError) as exc_info:
        raise_for_status(resp)
    assert exc_info.value.response is resp


def test_response_headers_accessible():
    """exc.response.headers must be accessible after raise_for_status."""
    resp = httpx.Response(
        status_code=404,
        text="not found",
        headers={"x-request-id": "req-123"},
    )
    with pytest.raises(NotFoundError) as exc_info:
        raise_for_status(resp)
    assert exc_info.value.response is not None
    assert exc_info.value.response.headers["x-request-id"] == "req-123"


def test_response_attribute_on_500():
    """5xx errors also carry the response attribute."""
    resp = _make_response(500, "server error")
    with pytest.raises(InternalServerError) as exc_info:
        raise_for_status(resp)
    assert exc_info.value.response is resp


def test_response_attribute_on_generic_4xx():
    """Unknown 4xx codes carry the response attribute."""
    resp = _make_response(418, "teapot")
    with pytest.raises(APIError) as exc_info:
        raise_for_status(resp)
    assert exc_info.value.response is resp


# ---------------------------------------------------------------------------
# Regression W2: Transport errors wrapped as GoodMemError in streaming
# ---------------------------------------------------------------------------


def test_stream_wraps_connect_error_as_goodmem_error():
    """httpx.ConnectError during Stream.__enter__ must be wrapped as GoodMemError."""
    from unittest.mock import MagicMock, patch
    from goodmem.streaming import Stream
    from goodmem.errors import GoodMemError
    from pydantic import BaseModel

    class DummyModel(BaseModel):
        data: str = ""

    # Create a mock http client whose .stream() raises ConnectError
    mock_http = MagicMock()
    mock_ctx = MagicMock()
    mock_ctx.__enter__ = MagicMock(side_effect=httpx.ConnectError("Connection refused"))
    mock_http.stream.return_value = mock_ctx

    stream = Stream(
        mock_http,
        method="POST",
        url="http://unreachable:9999/v1/test",
        model=DummyModel,
    )
    with pytest.raises(GoodMemError, match="Connection failed"):
        stream.__enter__()
