"""适配器错误脱敏单测（CR-M2-2：上抛消息不得内嵌厂商 HTTP 响应体）。

厂商异常的 `str(exc)` 常携带完整响应体（可能含请求内容/内部细节）。适配器把瞬时
故障包装成 `TransientProviderError` 时，消息应只含**异常类名 + 状态码**，绝不含原始
响应体（防泄漏，§9.3）。完整细节仍可留在服务端日志。
"""

from __future__ import annotations

import uuid
from types import SimpleNamespace
from typing import Any, cast

import pytest
from openai import AsyncOpenAI
from ww_llm_gateway.adapters.base import provider_error_summary
from ww_llm_gateway.adapters.openai_compat import OpenAICompatAdapter
from ww_llm_gateway.errors import TransientProviderError
from ww_llm_gateway.types import LlmRequest, Scope

_SECRET = "SECRET_BODY_MARKER_xyz"


def _req(**kw: Any) -> LlmRequest:
    kw.setdefault("tier", "writer")
    kw.setdefault("input", "x")
    return LlmRequest(scope=Scope(user_id=uuid.UUID(int=1)), **kw)


class _LeakyRateLimit(Exception):
    """底层厂商异常：`str()` 内嵌响应体，且带 429 状态码（触发瞬时判定）。"""

    def __init__(self) -> None:
        super().__init__(f'HTTP 429 {{"error": "{_SECRET}"}}')
        self.status_code = 429


def test_provider_error_summary_omits_body_keeps_class_and_status() -> None:
    summary = provider_error_summary(_LeakyRateLimit())

    assert _SECRET not in summary
    assert "_LeakyRateLimit" in summary
    assert "429" in summary


def test_provider_error_summary_class_only_when_no_status() -> None:
    class Boom(Exception):
        def __init__(self) -> None:
            super().__init__(_SECRET)

    summary = provider_error_summary(Boom())

    assert _SECRET not in summary
    assert summary == "Boom"


class _RaisingCompletions:
    def __init__(self, *, stream: bool) -> None:
        self._stream = stream

    async def create(self, **kw: Any) -> Any:
        raise _LeakyRateLimit()


def _adapter(*, stream: bool) -> OpenAICompatAdapter:
    client = SimpleNamespace(chat=SimpleNamespace(completions=_RaisingCompletions(stream=stream)))
    return OpenAICompatAdapter("deepseek", cast(AsyncOpenAI, client))


async def test_complete_wraps_transient_without_leaking_body() -> None:
    adapter = _adapter(stream=False)

    with pytest.raises(TransientProviderError) as exc_info:
        await adapter.complete(_req(), "deepseek-chat")

    msg = exc_info.value.message
    assert _SECRET not in msg
    assert "_LeakyRateLimit" in msg
    assert "429" in msg


async def test_stream_wraps_transient_without_leaking_body() -> None:
    adapter = _adapter(stream=True)

    with pytest.raises(TransientProviderError) as exc_info:
        async for _ in adapter.stream(_req(), "deepseek-chat"):
            pass

    msg = exc_info.value.message
    assert _SECRET not in msg
    assert "_LeakyRateLimit" in msg
    assert "429" in msg
