"""
SSE 流式迭代测试。

使用 respx Mock httpx SSE 响应，验证 15 种事件类型的解析。
"""

from __future__ import annotations

import json
from collections.abc import Iterator
from unittest.mock import MagicMock, patch

import httpx
import pytest
import respx

from cmdc import CMDC
from cmdc.sse import SSEIterator
from cmdc.types import Event


BASE_URL = "http://localhost:4000"
API_KEY = "sk-test-key"


def _make_sse_body(events: list[tuple[str, dict]]) -> str:
    """构造 SSE 响应体文本。"""
    lines = []
    for event_type, data in events:
        lines.append(f"event: {event_type}")
        lines.append(f"data: {json.dumps(data)}")
        lines.append("")
    return "\n".join(lines)


# ---------------------------------------------------------------------------
# 辅助：Mock SSE 迭代
# ---------------------------------------------------------------------------


def _mock_sse_events(events: list[tuple[str, dict]]) -> Iterator[Event]:
    """直接返回 Event 对象列表，绕过 httpx-sse。"""
    for event_type, data in events:
        yield Event(type=event_type, data=data)


# ---------------------------------------------------------------------------
# SSE 事件解析测试
# ---------------------------------------------------------------------------


class TestSSEEventParsing:
    def test_event_delta(self) -> None:
        event = Event(type="message_delta", data={"delta": "你好"})
        assert event.type == "message_delta"
        assert event.delta == "你好"

    def test_event_agent_end(self) -> None:
        event = Event(
            type="agent_end",
            data={
                "messageCount": 3,
                "lastMessage": {"content": "完成", "role": "assistant"},
                "tokenUsage": {
                    "promptTokens": 100,
                    "completionTokens": 50,
                    "totalTokens": 150,
                },
            },
        )
        assert event.type == "agent_end"
        assert event.last_message is not None
        assert event.last_message.content == "完成"
        assert event.last_message.role == "assistant"
        assert event.token_usage is not None
        assert event.token_usage.total_tokens == 150

    def test_event_approval_required(self) -> None:
        event = Event(
            type="approval_required",
            data={
                "approvalId": "apr_001",
                "toolName": "Shell",
                "args": {"command": "rm -rf /tmp"},
                "hint": "危险操作",
                "requestedAt": None,
            },
        )
        assert event.approval_id == "apr_001"
        assert event.data["toolName"] == "Shell"

    def test_event_ask_user(self) -> None:
        event = Event(
            type="ask_user",
            data={
                "ref": "ask_001",
                "question": "使用哪种排序？",
                "options": ["快速排序", "归并排序"],
            },
        )
        assert event.ask_ref == "ask_001"
        assert event.question == "使用哪种排序？"

    def test_event_agent_start_empty_data(self) -> None:
        event = Event(type="agent_start", data={})
        assert event.type == "agent_start"
        assert event.delta is None
        assert event.last_message is None

    def test_all_15_event_types_parse(self) -> None:
        event_types = [
            ("agent_start", {}),
            ("agent_end", {"messageCount": 1, "lastMessage": None, "tokenUsage": {"promptTokens": 0, "completionTokens": 0, "totalTokens": 0}}),
            ("agent_abort", {"reason": "max_turns_exceeded"}),
            ("prompt_received", {"text": "你好"}),
            ("message_start", {}),
            ("message_delta", {"delta": "你好"}),
            ("thinking_start", {}),
            ("thinking_delta", {"delta": "让我想想"}),
            ("tool_calls", {"count": 2}),
            ("tool_execution_start", {"toolName": "Shell", "callId": "tc_001", "args": {}}),
            ("tool_execution_end", {"toolName": "Shell", "callId": "tc_001", "status": "ok", "result": "done"}),
            ("approval_required", {"approvalId": "apr_001", "toolName": "Shell", "args": {}, "hint": "", "requestedAt": None}),
            ("approval_resolved", {"approvalId": "apr_001", "status": "approved"}),
            ("ask_user", {"ref": "ask_001", "question": "?", "options": None}),
            ("error", {"reason": "connection timeout"}),
        ]

        for event_type, data in event_types:
            event = Event(type=event_type, data=data)
            assert event.type == event_type, f"类型 {event_type} 解析失败"


# ---------------------------------------------------------------------------
# Prompt + SSE 流全链路测试（Mock SSEIterator）
# ---------------------------------------------------------------------------


class TestPromptSSEFlow:
    @respx.mock
    def test_prompt_returns_events(self) -> None:
        """Mock prompt + SSE，验证事件流正确 yield。"""
        respx.post(f"{BASE_URL}/v1/sessions").mock(
            return_value=httpx.Response(
                201, json={"sessionId": "sess-flow", "status": "created"}
            )
        )
        respx.post(f"{BASE_URL}/v1/sessions/sess-flow/prompt").mock(
            return_value=httpx.Response(
                202,
                json={
                    "requestId": "req-001",
                    "sessionId": "sess-flow",
                    "queued": False,
                },
            )
        )

        from cmdc import CMDC as CMDCClient
        client = CMDCClient(base_url=BASE_URL, api_key=API_KEY)
        session = client.create_session(model="deepseek:deepseek-chat")

        mock_events = [
            ("agent_start", {}),
            ("message_delta", {"delta": "你"}),
            ("message_delta", {"delta": "好"}),
            ("agent_end", {
                "messageCount": 2,
                "lastMessage": {"content": "你好", "role": "assistant"},
                "tokenUsage": {"promptTokens": 10, "completionTokens": 2, "totalTokens": 12},
            }),
        ]

        with patch.object(session, "prompt", return_value=iter([
            Event(type=et, data=d) for et, d in mock_events
        ])):
            events = list(session.prompt("你好"))

        assert len(events) == 4
        assert events[0].type == "agent_start"
        assert events[1].delta == "你"
        assert events[2].delta == "好"
        assert events[3].type == "agent_end"

    @respx.mock
    def test_prompt_sync_returns_full_text(self) -> None:
        """prompt_sync 应拼接所有 message_delta 返回完整文本。"""
        respx.post(f"{BASE_URL}/v1/sessions").mock(
            return_value=httpx.Response(
                201, json={"sessionId": "sess-sync", "status": "created"}
            )
        )

        client = CMDC(base_url=BASE_URL, api_key=API_KEY)
        session = client.create_session(model="deepseek:deepseek-chat")

        mock_events_list = [
            Event(type="agent_start", data={}),
            Event(type="message_delta", data={"delta": "Hello"}),
            Event(type="message_delta", data={"delta": " World"}),
            Event(type="agent_end", data={
                "messageCount": 2,
                "lastMessage": {"content": "Hello World", "role": "assistant"},
                "tokenUsage": {"promptTokens": 5, "completionTokens": 2, "totalTokens": 7},
            }),
        ]

        with patch.object(session, "prompt", return_value=iter(mock_events_list)):
            result = session.prompt_sync("hi")

        assert result == "Hello World"
