"""OpenAI 兼容适配器单测：用替身 client 验证消息翻译 + usage 映射（不联网）。"""

from __future__ import annotations

import uuid
from collections.abc import AsyncIterator
from types import SimpleNamespace
from typing import Any, cast

from openai import AsyncOpenAI
from ww_llm_gateway.adapters.openai_compat import OpenAICompatAdapter, _messages
from ww_llm_gateway.types import Block, LlmRequest, Scope


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


class _FakeStream:
    def __init__(self, chunks: list[Any]) -> None:
        self._chunks = chunks

    async def __aiter__(self) -> AsyncIterator[Any]:
        for c in self._chunks:
            yield c


class _FakeCompletions:
    def __init__(self, response: Any, stream_chunks: list[Any]) -> None:
        self._response = response
        self._stream_chunks = stream_chunks
        self.last_messages: Any = None

    async def create(self, **kw: Any) -> Any:
        self.last_messages = kw.get("messages")
        if kw.get("stream"):
            return _FakeStream(self._stream_chunks)
        return self._response


class _FakeClient:
    def __init__(self, completions: _FakeCompletions) -> None:
        self.chat = SimpleNamespace(completions=completions)


def test_messages_put_system_first_then_user() -> None:
    req = _req(input="正文", system=[Block(text="世界观硬规则", cache=True)])
    msgs = _messages(req)
    assert msgs[0]["role"] == "system"
    assert msgs[0]["content"] == "世界观硬规则"
    assert msgs[1]["role"] == "user"
    assert msgs[1]["content"] == "正文"


async def test_complete_maps_text_and_usage() -> None:
    response = SimpleNamespace(
        choices=[SimpleNamespace(message=SimpleNamespace(content="草稿"))],
        usage=SimpleNamespace(
            prompt_tokens=120,
            completion_tokens=80,
            prompt_tokens_details=SimpleNamespace(cached_tokens=30),
        ),
    )
    completions = _FakeCompletions(response, [])
    adapter = OpenAICompatAdapter("deepseek", cast(AsyncOpenAI, _FakeClient(completions)))

    result = await adapter.complete(_req(input="x"), "deepseek-chat")

    assert result.text == "草稿"
    assert result.usage.input_tokens == 120
    assert result.usage.output_tokens == 80
    assert result.usage.cache_read_tokens == 30


async def test_stream_yields_text_then_final_usage() -> None:
    chunks = [
        SimpleNamespace(choices=[SimpleNamespace(delta=SimpleNamespace(content="他"))], usage=None),
        SimpleNamespace(choices=[SimpleNamespace(delta=SimpleNamespace(content="说"))], usage=None),
        SimpleNamespace(
            choices=[],
            usage=SimpleNamespace(
                prompt_tokens=10,
                completion_tokens=2,
                prompt_tokens_details=None,
            ),
        ),
    ]
    completions = _FakeCompletions(None, chunks)
    adapter = OpenAICompatAdapter("deepseek", cast(AsyncOpenAI, _FakeClient(completions)))

    texts: list[str] = []
    usage_seen = None
    async for ch in adapter.stream(_req(input="x"), "deepseek-chat"):
        if ch.text:
            texts.append(ch.text)
        if ch.usage is not None:
            usage_seen = ch.usage

    assert "".join(texts) == "他说"
    assert usage_seen is not None
    assert usage_seen.output_tokens == 2
