"""
Unit tests for factory.py

注意：这些测试需要 mock LLM 响应，不实际调用模型。
"""

import pytest
from unittest.mock import AsyncMock, MagicMock, patch
from langchain_core.language_models.chat_models import BaseChatModel
from langchain_core.messages import AIMessage, HumanMessage, ToolMessage

from .factory import create_opencode_style_agent, create_initial_state


class TestCreateOpencodeStyleAgent:
    """测试 create_opencode_style_agent 函数"""

    @pytest.mark.skip(reason="需要网络访问，在 CI 环境中跳过")
    def test_create_agent_with_string_model(self):
        """测试使用字符串模型创建 agent"""
        with patch("langchain.chat_models.init_chat_model") as mock_init:
            mock_model = MagicMock()
            mock_init.return_value = mock_model

            agent = create_opencode_style_agent("gpt-4")

            assert agent is not None
            mock_init.assert_called_once_with("gpt-4")

    def test_create_agent_with_base_chat_model(self):
        """测试使用 BaseChatModel 实例创建 agent"""
        mock_model = MagicMock(spec=BaseChatModel)
        mock_model.bind_tools = MagicMock(return_value=mock_model)

        agent = create_opencode_style_agent(mock_model)

        assert agent is not None

    def test_create_agent_with_tools(self):
        """测试带工具创建 agent"""
        mock_model = MagicMock(spec=BaseChatModel)
        mock_model.bind_tools = MagicMock(return_value=mock_model)

        def test_tool(query: str) -> str:
            """Test tool"""
            return f"Result: {query}"

        agent = create_opencode_style_agent(mock_model, tools=[test_tool])

        assert agent is not None

    def test_create_agent_with_system_prompt_string(self):
        """测试使用字符串系统提示词"""
        mock_model = MagicMock(spec=BaseChatModel)
        mock_model.bind_tools = MagicMock(return_value=mock_model)

        agent = create_opencode_style_agent(
            mock_model,
            system_prompt="You are a helpful assistant",
        )

        assert agent is not None

    def test_create_agent_with_system_prompt_message(self):
        """测试使用 SystemMessage 系统提示词"""
        from langchain_core.messages import SystemMessage as SysMsg

        mock_model = MagicMock(spec=BaseChatModel)
        mock_model.bind_tools = MagicMock(return_value=mock_model)

        agent = create_opencode_style_agent(
            mock_model,
            system_prompt=SysMsg(content="You are a helpful assistant"),
        )

        assert agent is not None

    def test_create_agent_with_custom_max_steps(self):
        """测试自定义 max_steps"""
        mock_model = MagicMock(spec=BaseChatModel)
        mock_model.bind_tools = MagicMock(return_value=mock_model)

        agent = create_opencode_style_agent(mock_model, max_steps=100)

        assert agent is not None

    def test_create_agent_default_max_steps(self):
        """测试默认 max_steps 是 50"""
        mock_model = MagicMock(spec=BaseChatModel)
        mock_model.bind_tools = MagicMock(return_value=mock_model)

        agent = create_opencode_style_agent(mock_model)

        assert agent is not None
        # 验证 recursion_limit 配置
        config = agent.config
        assert config.get("recursion_limit") == 2 * 50 + 100  # 200

    def test_create_agent_recursion_limit_formula(self):
        """测试 recursion_limit 计算公式"""
        mock_model = MagicMock(spec=BaseChatModel)
        mock_model.bind_tools = MagicMock(return_value=mock_model)

        for max_steps in [10, 50, 100]:
            agent = create_opencode_style_agent(mock_model, max_steps=max_steps)
            expected_limit = 2 * max_steps + 100
            assert agent.config.get("recursion_limit") == expected_limit

    def test_create_agent_with_checkpointer(self):
        """测试带 checkpointer 创建"""
        from langgraph.checkpoint.memory import MemorySaver

        mock_model = MagicMock(spec=BaseChatModel)
        mock_model.bind_tools = MagicMock(return_value=mock_model)

        agent = create_opencode_style_agent(
            mock_model,
            checkpointer=MemorySaver(),
        )

        assert agent is not None

    def test_create_agent_with_debug_mode(self):
        """测试调试模式"""
        mock_model = MagicMock(spec=BaseChatModel)
        mock_model.bind_tools = MagicMock(return_value=mock_model)

        agent = create_opencode_style_agent(mock_model, debug=True)

        assert agent is not None

    def test_create_agent_with_name(self):
        """测试带名称创建"""
        mock_model = MagicMock(spec=BaseChatModel)
        mock_model.bind_tools = MagicMock(return_value=mock_model)

        agent = create_opencode_style_agent(mock_model, name="test_agent")

        assert agent is not None


class TestAgentGraphStructure:
    """测试 Agent 图结构"""

    def test_graph_has_model_node(self):
        """测试图包含 model 节点"""
        mock_model = MagicMock(spec=BaseChatModel)
        mock_model.bind_tools = MagicMock(return_value=mock_model)

        agent = create_opencode_style_agent(mock_model)

        # 检查节点 - nodes 是字典，键是节点名称
        nodes = agent.get_graph().nodes
        assert "model" in nodes

    def test_graph_has_tools_node_when_tools_provided(self):
        """测试有工具时图包含 tools 节点"""
        mock_model = MagicMock(spec=BaseChatModel)
        mock_model.bind_tools = MagicMock(return_value=mock_model)

        def test_tool(query: str) -> str:
            """Test tool"""
            return f"Result: {query}"

        agent = create_opencode_style_agent(mock_model, tools=[test_tool])

        nodes = agent.get_graph().nodes
        assert "tools" in nodes

    def test_graph_no_tools_node_when_no_tools(self):
        """测试无工具时图不包含 tools 节点"""
        mock_model = MagicMock(spec=BaseChatModel)
        mock_model.bind_tools = MagicMock(return_value=mock_model)

        agent = create_opencode_style_agent(mock_model, tools=[])

        nodes = agent.get_graph().nodes
        assert "tools" not in nodes


class TestAgentInvocation:
    """测试 Agent 调用（使用 mock）

    注意：由于 factory.py 的复杂性，mock LLM 调用比较困难。
    这些测试主要验证图的构建和配置，而不是实际的 LLM 调用。
    """

    @pytest.mark.skip(reason="需要完整的 LLM mock，在集成测试中验证")
    @pytest.mark.asyncio
    async def test_agent_stops_on_finish_reason_stop(self):
        """测试 finish_reason=stop 时停止"""
        pass

    @pytest.mark.skip(reason="需要完整的 LLM mock，在集成测试中验证")
    @pytest.mark.asyncio
    async def test_agent_continues_on_finish_reason_tool_calls(self):
        """测试 finish_reason=tool-calls 时继续"""
        pass

    @pytest.mark.skip(reason="需要完整的 LLM mock，在集成测试中验证")
    @pytest.mark.asyncio
    async def test_agent_stops_on_max_steps(self):
        """测试达到 max_steps 时停止"""
        pass


class TestBackwardCompatibility:
    """测试向后兼容性"""

    def test_create_agent_alias(self):
        """测试 create_agent 别名"""
        from .factory import create_agent

        mock_model = MagicMock(spec=BaseChatModel)
        mock_model.bind_tools = MagicMock(return_value=mock_model)

        agent = create_agent(mock_model)

        assert agent is not None


if __name__ == "__main__":
    pytest.main([__file__, "-v"])
