"""AgentLoop + GovernedLLMClient 集成测试。

验证 core/agent/ 通过 LLMProvider Protocol 与 adapters/llm.py 端到端协作:
- GovernedLLMClient 满足 LLMProvider Protocol
- AgentLoop 通过 GovernedLLMClient 完成 search + submit_answer 两步推理
- token 用量正确累加
- 遥测数据正确写入 SQLite
"""

from __future__ import annotations

import json
from pathlib import Path
from typing import Any
from unittest.mock import patch

import pytest

from adapters.breaker import CircuitBreaker
from adapters.llm import GovernedLLMClient
from adapters.telemetry import SQLiteTelemetryRecorder
from core.agent.loop import AgentLoop
from core.protocols import LLMProvider


class _StubDispatcher:
    """测试用工具调度器，支持 search_tree 和 submit_answer。"""

    async def dispatch(
        self, tool_name: str, args: dict[str, Any], *, context: dict[str, Any]
    ) -> str:
        if tool_name == "submit_answer":
            return "答案已提交"
        if tool_name == "search_tree":
            return "搜索结果: L2-3 节点"
        raise ValueError(f"未知工具: {tool_name}")


def _streaming_result(content: str, thinking: str = "") -> tuple:
    """构造 _call_streaming 的返回值。

    参数:
        content: 模型输出的文本内容。
        thinking: 模型思考过程文本。

    返回:
        (content, thinking, ttft_ms, max_inter_token_ms, usage_dict) 五元组。
    """
    return (
        content,
        thinking,
        50.0,   # ttft_ms
        10.0,   # max_inter_token_ms
        {"prompt_tokens": 10, "completion_tokens": 5},
    )


@pytest.mark.asyncio
async def test_agent_loop_with_governed_client(tmp_path: Path) -> None:
    """AgentLoop 通过 GovernedLLMClient 完成搜索+提交。"""
    telemetry = SQLiteTelemetryRecorder(db_path=tmp_path / "telemetry.db")
    client = GovernedLLMClient(
        model="test-model",
        base_url="https://api.test.com/v1",
        api_key="sk-test",
        provider="deepseek",
        thinking=True,
        breaker=CircuitBreaker(fail_threshold=5, cooldown_s=60.0),
        cache=None,
        telemetry=telemetry,
        timeout_s=30.0,
        ttft_timeout_s=10.0,
        inter_token_timeout_s=5.0,
        max_retries=1,
        retry_base_delay_s=0.01,
        retry_max_delay_s=0.05,
    )

    # 验证 GovernedLLMClient 满足 LLMProvider Protocol
    assert isinstance(client, LLMProvider)

    call_count = 0
    responses = [
        _streaming_result(
            json.dumps(
                {
                    "reflect": {},
                    "plan": {},
                    "action": {"tool": "search_tree", "args": {"query": "什么是AI"}},
                },
                ensure_ascii=False,
            ),
            thinking="让我思考一下",
        ),
        _streaming_result(
            json.dumps(
                {
                    "reflect": {},
                    "plan": {},
                    "action": {"tool": "submit_answer", "args": {"answer": "AI是..."}},
                },
                ensure_ascii=False,
            ),
        ),
    ]

    async def mock_call_streaming(messages: list[dict[str, Any]]) -> tuple:
        nonlocal call_count
        result = responses[call_count]
        call_count += 1
        return result

    loop = AgentLoop(llm=client, max_steps=10)

    with patch.object(client, "_call_streaming", side_effect=mock_call_streaming):
        result = await loop.run(
            system_prompt="你是一个搜索助手",
            user_prompt="什么是人工智能？",
            tool_dispatcher=_StubDispatcher(),
            session_id="test-session",
        )

    assert result.stop_reason == "finished"
    assert result.result == {"answer": "AI是..."}
    assert result.steps_used == 2
    assert result.steps[0].thought == "让我思考一下"
    assert result.steps[0].tool_call["tool"] == "search_tree"
    assert result.steps[1].tool_call["tool"] == "submit_answer"
    assert result.token_usage["prompt_tokens"] == 20  # 10 * 2
    assert result.token_usage["completion_tokens"] == 10  # 5 * 2
