# -*- coding: utf-8 -*-
"""
TencentBlueKing is pleased to support the open source community by making
蓝鲸智云 - AIDev (BlueKing - AIDev) available.
Copyright (C) 2025 THL A29 Limited,
a Tencent company. All rights reserved.
Licensed under the MIT License (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at http://opensource.org/licenses/MIT
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
either express or implied. See the License for the
specific language governing permissions and limitations under the License.
We undertake not to change the open source license (MIT license) applicable
to the current version of the project delivered to anyone in the future.
"""

from unittest.mock import AsyncMock, MagicMock, Mock, patch

import pytest
from aidev_agent.api.bk_aidev import BKAidevApi
from aidev_agent.config import settings
from aidev_agent.packages.langchain_core.tools.base import Tool, make_mcp_tools, make_structured_tool
from aidev_agent.services.pydantic_models import AgentOptions, ExecuteKwargs
from langchain_core.tools import StructuredTool

# ================== make_structured_tool Mock 测试 ==================


@pytest.fixture
def sample_weather_tool_data():
    """天气查询工具的示例数据"""
    return {
        "tool_code": "weather-query",
        "tool_name": "天气查询",
        "description": "查询中国国内天气情况",
        "method": "get",
        "url": "https://api.example.com/prod/bkaidev/scene/tool_proxy/tool_proxy/weather-query/call/",
        "property": {
            "body": [
                {
                    "name": "",
                    "type": "string",
                    "default": "",
                    "required": False,
                    "validate": {"rules": [], "enable": False},
                    "description": "",
                }
            ],
            "query": [
                {
                    "name": "id",
                    "type": "string",
                    "default": "88888888",
                    "required": True,
                    "validate": {"rules": [], "enable": False},
                    "description": "固定输入，无需更改",
                },
                {
                    "name": "key",
                    "type": "string",
                    "default": "88888888",
                    "required": True,
                    "validate": {"rules": [], "enable": False},
                    "description": "固定输入，无需更改",
                },
                {
                    "name": "sheng",
                    "type": "string",
                    "default": "",
                    "required": True,
                    "validate": {"rules": [], "enable": False},
                    "description": "省份名",
                },
                {
                    "name": "place",
                    "type": "string",
                    "default": "",
                    "required": True,
                    "validate": {"rules": [], "enable": False},
                    "description": "市区名",
                },
            ],
            "header": [
                {
                    "name": "",
                    "type": "string",
                    "default": "",
                    "required": False,
                    "validate": {"rules": [], "enable": False},
                    "description": "",
                }
            ],
        },
    }


@pytest.fixture
def sample_post_tool_data():
    """POST 请求工具的示例数据"""
    return {
        "tool_code": "create-user",
        "tool_name": "创建用户",
        "description": "创建新用户",
        "method": "post",
        "url": "https://api.example.com/users",
        "property": {
            "body": [
                {
                    "name": "username",
                    "type": "string",
                    "default": "",
                    "required": True,
                    "validate": {"rules": [], "enable": False},
                    "description": "用户名",
                },
                {
                    "name": "email",
                    "type": "string",
                    "default": "",
                    "required": True,
                    "validate": {"rules": [], "enable": False},
                    "description": "邮箱",
                },
                {
                    "name": "age",
                    "type": "integer",
                    "default": 18,
                    "required": False,
                    "validate": {"rules": [], "enable": False},
                    "description": "年龄",
                },
            ],
            "header": [
                {
                    "name": "Content-Type",
                    "type": "string",
                    "default": "application/json",
                    "required": True,
                    "validate": {"rules": [], "enable": False},
                    "description": "内容类型",
                }
            ],
            "query": [],
        },
    }


@pytest.fixture
def sample_path_param_tool_data():
    """包含路径参数的工具示例数据"""
    return {
        "tool_code": "get-user",
        "tool_name": "获取用户信息",
        "description": "根据用户ID获取用户信息",
        "method": "get",
        "url": "https://api.example.com/users/{user_id}",
        "property": {
            "path": [
                {
                    "name": "user_id",
                    "type": "string",
                    "default": "",
                    "required": True,
                    "validate": {"rules": [], "enable": False},
                    "description": "用户ID",
                }
            ],
            "query": [],
            "header": [],
        },
    }


def test_make_structured_tool_basic(sample_weather_tool_data):
    """测试基本的 make_structured_tool 功能"""
    tool = Tool.model_validate(sample_weather_tool_data)
    structured_tool = make_structured_tool(tool)

    # 验证返回的是 StructuredTool
    assert isinstance(structured_tool, StructuredTool)
    assert structured_tool.name == "weather-query"
    assert structured_tool.description == "查询中国国内天气情况"
    assert structured_tool.metadata == {"tool_name": "天气查询"}

    # 验证参数模型
    assert structured_tool.args_schema is not None
    fields = structured_tool.args_schema.model_fields
    assert "query__id" in fields
    assert "query__key" in fields
    assert "query__sheng" in fields
    assert "query__place" in fields


@patch("aidev_agent.packages.langchain_core.tools.base.requests.Session")
def test_make_structured_tool_get_request_success(mock_session_class, sample_weather_tool_data):
    """测试 GET 请求成功的场景"""
    # Mock 响应
    mock_response = Mock()
    mock_response.json.return_value = {"status": "success", "temperature": "25°C"}
    mock_response.headers.get.return_value = "application/json"
    mock_response.raise_for_status = Mock()

    mock_session = Mock()
    mock_session.request.return_value = mock_response
    mock_session_class.return_value = mock_session

    # 创建工具
    tool = Tool.model_validate(sample_weather_tool_data)
    structured_tool = make_structured_tool(tool)

    # 执行工具
    result = structured_tool.invoke({"query__sheng": "广东", "query__place": "深圳"})

    # 验证结果
    assert result == {"status": "success", "temperature": "25°C"}
    mock_session.request.assert_called_once()
    call_args = mock_session.request.call_args
    assert call_args[0][0] == "get"
    assert "sheng" in call_args[1]["params"]
    assert call_args[1]["params"]["sheng"] == "广东"
    assert call_args[1]["params"]["place"] == "深圳"


@patch("aidev_agent.packages.langchain_core.tools.base.requests.Session")
def test_make_structured_tool_post_request_success(mock_session_class, sample_post_tool_data):
    """测试 POST 请求成功的场景"""
    # Mock 响应
    mock_response = Mock()
    mock_response.json.return_value = {"id": 123, "username": "testuser"}
    mock_response.headers.get.return_value = "application/json"
    mock_response.raise_for_status = Mock()

    mock_session = Mock()
    mock_session.request.return_value = mock_response
    mock_session_class.return_value = mock_session

    # 创建工具
    tool = Tool.model_validate(sample_post_tool_data)
    structured_tool = make_structured_tool(tool)

    # 执行工具
    result = structured_tool.invoke({"body__username": "testuser", "body__email": "test@example.com"})

    # 验证结果
    assert result == {"id": 123, "username": "testuser"}
    mock_session.request.assert_called_once()
    call_args = mock_session.request.call_args
    assert call_args[0][0] == "post"
    assert "json" in call_args[1]
    assert call_args[1]["json"]["username"] == "testuser"
    assert call_args[1]["json"]["email"] == "test@example.com"


@patch("aidev_agent.packages.langchain_core.tools.base.requests.Session")
def test_make_structured_tool_http_error(mock_session_class, sample_weather_tool_data):
    """测试 HTTP 错误的场景"""
    # Mock 错误响应
    import requests

    mock_response = Mock()
    mock_response.content.decode.return_value = "API error occurred"
    mock_response.raise_for_status.side_effect = requests.HTTPError(response=mock_response)

    mock_session = Mock()
    mock_session.request.return_value = mock_response
    mock_session_class.return_value = mock_session

    # 创建工具
    tool = Tool.model_validate(sample_weather_tool_data)
    structured_tool = make_structured_tool(tool)

    # 执行工具
    result = structured_tool.invoke({"query__sheng": "广东", "query__place": "深圳"})

    # 验证错误信息
    assert "[HTTPError]" in result
    assert "API error occurred" in result


@patch("aidev_agent.packages.langchain_core.tools.base.requests.Session")
def test_make_structured_tool_timeout_error(mock_session_class, sample_weather_tool_data):
    """测试超时错误的场景"""
    mock_session = Mock()
    mock_session.request.side_effect = TimeoutError("Request timeout")
    mock_session_class.return_value = mock_session

    # 创建工具
    tool = Tool.model_validate(sample_weather_tool_data)
    structured_tool = make_structured_tool(tool)

    # 执行工具
    result = structured_tool.invoke({"query__sheng": "广东", "query__place": "深圳"})

    # 验证错误信息
    assert "Request ERROR" in result
    assert "timeout" in result.lower()


@patch("aidev_agent.packages.langchain_core.tools.base.requests.Session")
def test_make_structured_tool_path_params(mock_session_class, sample_path_param_tool_data):
    """测试路径参数的处理"""
    # Mock 响应
    mock_response = Mock()
    mock_response.json.return_value = {"id": "123", "name": "John"}
    mock_response.headers.get.return_value = "application/json"
    mock_response.raise_for_status = Mock()

    mock_session = Mock()
    mock_session.request.return_value = mock_response
    mock_session_class.return_value = mock_session

    # 创建工具
    tool = Tool.model_validate(sample_path_param_tool_data)
    structured_tool = make_structured_tool(tool)

    # 执行工具
    result = structured_tool.invoke({"path__user_id": "123"})

    # 验证结果
    assert result == {"id": "123", "name": "John"}
    mock_session.request.assert_called_once()
    call_args = mock_session.request.call_args
    # 验证 URL 中的路径参数被正确替换
    assert "users/123" in call_args[0][1]


@patch("aidev_agent.packages.langchain_core.tools.base.requests.Session")
def test_make_structured_tool_max_retry(mock_session_class, sample_weather_tool_data):
    """测试重复请求的限制"""
    # Mock 响应
    mock_response = Mock()
    mock_response.json.return_value = {"status": "success"}
    mock_response.headers.get.return_value = "application/json"
    mock_response.raise_for_status = Mock()

    mock_session = Mock()
    mock_session.request.return_value = mock_response
    mock_session_class.return_value = mock_session

    # 创建工具
    tool = Tool.model_validate(sample_weather_tool_data)
    structured_tool = make_structured_tool(tool)

    # 相同的请求调用多次
    params = {"query__sheng": "广东", "query__place": "深圳"}
    structured_tool.invoke(params)
    structured_tool.invoke(params)
    result = structured_tool.invoke(params)

    # 第三次应该返回提示信息
    assert "Same request call too much" in result


@patch("aidev_agent.packages.langchain_core.tools.base.requests.Session")
def test_make_structured_tool_non_json_response(mock_session_class, sample_weather_tool_data):
    """测试非 JSON 响应"""
    # Mock 文本响应
    mock_response = Mock()
    mock_response.text = "Plain text response"
    mock_response.headers.get.return_value = "text/plain"
    mock_response.raise_for_status = Mock()

    mock_session = Mock()
    mock_session.request.return_value = mock_response
    mock_session_class.return_value = mock_session

    # 创建工具
    tool = Tool.model_validate(sample_weather_tool_data)
    structured_tool = make_structured_tool(tool)

    # 执行工具
    result = structured_tool.invoke({"query__sheng": "广东", "query__place": "深圳"})

    # 验证返回文本
    assert result == "Plain text response"


@patch("aidev_agent.packages.langchain_core.tools.base.requests.Session")
def test_make_structured_tool_with_extra(mock_session_class, sample_weather_tool_data):
    """测试带有 extra 参数的工具"""
    # Mock 响应
    mock_response = Mock()
    mock_response.json.return_value = {"status": "success"}
    mock_response.headers.get.return_value = "application/json"
    mock_response.raise_for_status = Mock()

    mock_session = Mock()
    mock_session.request.return_value = mock_response
    mock_session_class.return_value = mock_session

    # 创建带 extra 的工具
    tool_data = sample_weather_tool_data.copy()
    tool = Tool.model_validate(tool_data)
    tool.extra = {"header": {"Authorization": "Bearer token123"}, "query": {"extra_param": "value"}}

    structured_tool = make_structured_tool(tool)

    # 执行工具
    structured_tool.invoke({"query__sheng": "广东", "query__place": "深圳"})

    # 验证 extra 参数被正确传递
    call_args = mock_session.request.call_args
    assert "Authorization" in call_args[1]["headers"]
    assert call_args[1]["headers"]["Authorization"] == "Bearer token123"
    assert "extra_param" in call_args[1]["params"]
    assert call_args[1]["params"]["extra_param"] == "value"


# ================== make_structured_tool 真实测试 ==================


@pytest.mark.skipif(
    not all([settings.LLM_GW_ENDPOINT, settings.APP_CODE, settings.SECRET_KEY]),
    reason="没有配置足够的环境变量,跳过该测试",
)
def test_make_structured_tool_real_execution():
    """测试真实的工具执行 - 使用 weather-query 工具"""
    # 获取真实的工具配置
    client = BKAidevApi.get_client()
    tool_code = "weather-query"

    # 使用 client 的 construct_tool 方法构造工具
    structured_tool = client.construct_tool(tool_code)

    # 验证工具创建成功
    assert isinstance(structured_tool, StructuredTool)
    assert structured_tool.name == tool_code
    print(f"\n工具名称: {structured_tool.name}")
    print(f"工具描述: {structured_tool.description}")
    print(f"工具元数据: {structured_tool.metadata}")

    # 执行真实的工具调用
    result = structured_tool.invoke({"query__sheng": "广东", "query__place": "深圳"})

    # 打印结果
    print(f"\n真实执行结果: {result}")

    # 基本验证
    assert result is not None


# ================== make_mcp_tools Mock 测试 ==================


@pytest.fixture
def sample_mcp_config():
    """MCP 配置示例"""
    return {"tencentcloud-doc-mcp": {"url": "http://portal-mcp-server.example.com/mcp", "transport": "streamable_http"}}


@pytest.fixture
def sample_mcp_config_with_auth():
    """带认证的 MCP 配置示例"""
    return {
        "authenticated-mcp": {
            "url": "http://secure-mcp-server.example.com/mcp",
            "transport": "streamable_http",
            "credential_type": "blueapps",
        }
    }


@pytest.fixture
def mock_agent_options():
    """Mock AgentOptions 对象"""
    return MagicMock(spec=AgentOptions)


@patch("aidev_agent.packages.langchain_core.tools.base.MultiServerMCPClient")
def test_make_mcp_tools_basic(mock_mcp_client_class, sample_mcp_config, mock_agent_options):
    """测试基本的 make_mcp_tools 功能"""
    # Mock 工具列表
    mock_tool = MagicMock(spec=StructuredTool)
    mock_tool.name = "test-mcp-tool"
    mock_tool.description = "Test MCP tool"
    mock_tool.coroutine = AsyncMock()
    mock_tool.metadata = {"mcp_name": "tencentcloud-doc-mcp"}  # 添加 mcp_name 到 metadata

    # Mock MCP 客户端
    mock_client = MagicMock()
    mock_client.get_tools = AsyncMock(return_value=[mock_tool])
    mock_mcp_client_class.return_value = mock_client

    # 调用 make_mcp_tools（使用真实的事件循环）
    tools = make_mcp_tools(sample_mcp_config, mock_agent_options)

    # 验证
    assert len(tools) == 1
    assert tools[0].name == "test-mcp-tool"
    assert tools[0].metadata["mcp_name"] == "tencentcloud-doc-mcp"
    mock_mcp_client_class.assert_called_once()


@patch("aidev_agent.packages.langchain_core.tools.base.MultiServerMCPClient")
def test_make_mcp_tools_with_blueapps_auth(mock_mcp_client_class, sample_mcp_config_with_auth, mock_agent_options):
    """测试带 blueapps 认证的 MCP 工具"""
    # Mock 工具
    mock_tool = MagicMock(spec=StructuredTool)
    mock_tool.coroutine = AsyncMock()
    mock_tool.metadata = {}  # 添加 metadata 属性

    # Mock MCP 客户端
    mock_client = MagicMock()
    mock_client.get_tools = AsyncMock(return_value=[mock_tool])
    mock_mcp_client_class.return_value = mock_client

    # 调用 make_mcp_tools（使用真实的事件循环）
    tools = make_mcp_tools(sample_mcp_config_with_auth, mock_agent_options)

    # 验证认证信息被添加到配置中
    # MultiServerMCPClient 在异步函数内部被调用，需要检查 mock 的调用参数
    call_args = mock_mcp_client_class.call_args
    assert call_args is not None, "MultiServerMCPClient 应该被调用"
    config_arg = call_args[0][0]  # 第一个位置参数是 server_config
    assert "authenticated-mcp" in config_arg
    assert "headers" in config_arg["authenticated-mcp"]
    assert "X-Bkapi-Authorization" in config_arg["authenticated-mcp"]["headers"]

    # 验证工具被包装
    assert len(tools) == 1


@patch("aidev_agent.packages.langchain_core.tools.base.MultiServerMCPClient")
def test_make_mcp_tools_error_handling(mock_mcp_client_class, sample_mcp_config, mock_agent_options):
    """测试 MCP 工具获取失败的场景"""
    # Mock 客户端抛出异常
    mock_client = MagicMock()
    mock_client.get_tools = AsyncMock(side_effect=Exception("Connection failed"))
    mock_mcp_client_class.return_value = mock_client

    # 调用应该抛出 AIDevException（使用真实的事件循环）
    from aidev_agent.exceptions import AIDevException

    with pytest.raises(AIDevException, match="获取MCP工具列表失败"):
        make_mcp_tools(sample_mcp_config, mock_agent_options)


@patch("aidev_agent.packages.langchain_core.tools.base.MultiServerMCPClient")
def test_make_mcp_tools_multiple_servers(mock_mcp_client_class, mock_agent_options):
    """测试多个 MCP 服务器"""
    multi_server_config = {
        "server1": {"url": "http://server1.com/mcp", "transport": "streamable_http"},
        "server2": {"url": "http://server2.com/mcp", "transport": "streamable_http"},
    }

    # Mock 工具
    mock_tool1 = MagicMock(spec=StructuredTool)
    mock_tool1.name = "tool1"
    mock_tool1.coroutine = AsyncMock()
    mock_tool1.metadata = {}  # 添加 metadata 属性

    mock_tool2 = MagicMock(spec=StructuredTool)
    mock_tool2.name = "tool2"
    mock_tool2.coroutine = AsyncMock()
    mock_tool2.metadata = {}  # 添加 metadata 属性

    # Mock 客户端 - 返回两个工具
    mock_client = MagicMock()
    mock_client.get_tools = AsyncMock(return_value=[mock_tool1, mock_tool2])
    mock_mcp_client_class.return_value = mock_client

    # 调用（使用真实的事件循环）
    tools = make_mcp_tools(multi_server_config, mock_agent_options)

    # 验证 - 应该有4个工具（两个服务器，每个返回两个工具）
    assert len(tools) == 4
    # MultiServerMCPClient 被调用两次（每个服务器一次）
    assert mock_mcp_client_class.call_count == 2


# ================== wrap_mcp_exception 测试 ==================
@pytest.mark.asyncio
async def test_mcp_exception_wrapper_success():
    """测试 wrap_mcp_exception 正常执行"""
    from aidev_agent.packages.langchain_core.tools.base import wrap_mcp_exception

    async def mock_coro(*args, **kwargs):
        return "success result"

    wrapper = wrap_mcp_exception(mock_coro)
    result = await wrapper()

    assert result == "success result"


@pytest.mark.asyncio
async def test_mcp_exception_wrapper_tool_exception():
    """测试 wrap_mcp_exception 处理 ToolException"""
    from aidev_agent.packages.langchain_core.tools.base import wrap_mcp_exception
    from langchain_core.tools.base import ToolException

    async def mock_coro(*args, **kwargs):
        raise ToolException("Tool error occurred")

    wrapper = wrap_mcp_exception(mock_coro)
    result = await wrapper()

    assert isinstance(result, tuple)
    assert "[ERROR]" in result[0]
    assert "MCP工具调用失败" in result[0]


@pytest.mark.asyncio
async def test_mcp_exception_wrapper_connection_error():
    """测试 wrap_mcp_exception 处理连接错误"""
    from aidev_agent.packages.langchain_core.tools.base import wrap_mcp_exception

    async def mock_coro(*args, **kwargs):
        raise ConnectionError("Connection refused")

    wrapper = wrap_mcp_exception(mock_coro)
    result = await wrapper()

    assert isinstance(result, tuple)
    assert "[ERROR]" in result[0]
    assert "连接异常" in result[0]


@pytest.mark.asyncio
async def test_mcp_exception_wrapper_timeout_error():
    """测试 wrap_mcp_exception 处理超时错误"""
    from aidev_agent.packages.langchain_core.tools.base import wrap_mcp_exception

    async def mock_coro(*args, **kwargs):
        raise TimeoutError("Request timeout")

    wrapper = wrap_mcp_exception(mock_coro)
    result = await wrapper()

    assert isinstance(result, tuple)
    assert "[ERROR]" in result[0]
    assert "超时异常" in result[0]


def test_make_structured_tool_with_inject_config_and_state():
    """测试 make_structured_tool 支持注入 context (config 和 state)"""

    import inspect

    # 创建一个测试工具
    tool = Tool(
        tool_code="test_tool",
        tool_name="测试工具",
        description="用于测试 context 注入的工具",
        method="POST",
        url="https://example.com/api/test",
        property={
            "header": [
                {
                    "name": "X-User-Id",
                    "type": "string",
                    "required": False,
                    "default": "{{ state.user_id }}",
                    "description": "用户ID (从 state 注入)",
                    "validate": {"enable": False, "rules": []},
                },
                {
                    "name": "X-Thread-Id",
                    "type": "string",
                    "required": False,
                    "default": "{{ config.configurable.thread_id }}",
                    "description": "线程ID (从 config 注入)",
                    "validate": {"enable": False, "rules": []},
                },
            ],
            "body": [
                {
                    "name": "message",
                    "type": "string",
                    "required": True,
                    "default": None,
                    "description": "消息内容",
                    "validate": {"enable": False, "rules": []},
                },
                {
                    "name": "tenant",
                    "type": "string",
                    "required": False,
                    "default": "{{ state.tenant }}",
                    "description": "租户 (从 state 注入)",
                    "validate": {"enable": False, "rules": []},
                },
            ],
        },
        extra=None,
    )

    # 构建工具 - 启用 context 注入
    structured_tool = make_structured_tool(
        tool=tool,
        inject_context=True,
    )

    # 验证工具的基本属性
    assert structured_tool.name == "test_tool"
    assert structured_tool.description == "用于测试 context 注入的工具"

    # 验证函数签名包含 state 和 config
    sig = inspect.signature(structured_tool.func)
    assert "state" in sig.parameters, "函数签名应该包含 state 参数"
    assert "config" in sig.parameters, "函数签名应该包含 config 参数"

    # 验证 args_schema 包含正常的业务字段
    schema_fields = structured_tool.args_schema.model_fields
    assert "body__message" in schema_fields
    assert "header__X-User-Id" in schema_fields

    print(f"✓ 工具创建成功，函数签名: {sig}")
    print(f"✓ args_schema 字段: {list(schema_fields.keys())}")


def test_make_structured_tool_inject_context():
    """测试 make_structured_tool 注入 context"""

    import inspect

    tool = Tool(
        tool_code="test_tool_state_only",
        tool_name="Context 注入工具",
        description="测试注入 context",
        method="GET",
        url="https://example.com/api/data",
        property={
            "query": [
                {
                    "name": "user_id",
                    "type": "string",
                    "required": False,
                    "default": "{{ state.user_id }}",
                    "description": "用户ID",
                    "validate": {"enable": False, "rules": []},
                }
            ]
        },
        extra=None,
    )

    structured_tool = make_structured_tool(
        tool=tool,
        inject_context=True,
    )

    sig = inspect.signature(structured_tool.func)
    assert "state" in sig.parameters, "函数签名应该包含 state 参数"
    assert "config" in sig.parameters, "函数签名应该包含 config 参数"

    schema_fields = structured_tool.args_schema.model_fields
    assert "query__user_id" in schema_fields

    print(f"✓ Context 注入成功，函数签名: {sig}")


def test_make_structured_tool_no_injection():
    """测试 make_structured_tool 不注入 (向后兼容)"""

    tool = Tool(
        tool_code="test_tool_no_injection",
        tool_name="无注入工具",
        description="测试不注入",
        method="GET",
        url="https://example.com/api/legacy",
        property={
            "query": [
                {
                    "name": "param1",
                    "type": "string",
                    "required": True,
                    "default": None,
                    "description": "参数1",
                    "validate": {"enable": False, "rules": []},
                }
            ]
        },
        extra=None,
    )

    structured_tool = make_structured_tool(
        tool=tool,
        inject_context=False,
    )

    schema_fields = structured_tool.args_schema.model_fields
    assert "state" not in schema_fields, "不应该包含 state 字段"
    assert "config" not in schema_fields, "不应该包含 config 字段"
    assert "query__param1" in schema_fields

    print(f"✓ 无注入测试成功 (向后兼容)，schema 字段: {list(schema_fields.keys())}")


@pytest.mark.asyncio
async def test_tool_invocation_with_state_and_config():
    """测试工具调用时 state 和 config 的渲染"""

    # Mock HTTP 请求
    with patch("requests.Session.request") as mock_request:
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.headers = {"content-type": "application/json"}
        mock_response.json.return_value = {"status": "success"}
        mock_request.return_value = mock_response

        tool = Tool(
            tool_code="test_render_tool",
            tool_name="渲染测试工具",
            description="测试变量渲染",
            method="POST",
            url="https://example.com/api/test",
            property={
                "header": [
                    {
                        "name": "X-User-Id",
                        "type": "string",
                        "required": False,
                        "default": "{{ state.user_id }}",
                        "description": "用户ID",
                        "validate": {"enable": False, "rules": []},
                    },
                    {
                        "name": "X-Thread-Id",
                        "type": "string",
                        "required": False,
                        "default": "{{ config.configurable.thread_id }}",
                        "description": "线程ID",
                        "validate": {"enable": False, "rules": []},
                    },
                ],
                "body": [
                    {
                        "name": "tenant",
                        "type": "string",
                        "required": False,
                        "default": "{{ state.tenant }}",
                        "description": "租户",
                        "validate": {"enable": False, "rules": []},
                    },
                    {
                        "name": "username",
                        "type": "string",
                        "required": False,
                        "default": "{{ bk_username }}",
                        "description": "租户",
                        "validate": {"enable": False, "rules": []},
                    },
                ],
            },
            extra=None,
        )

        structured_tool = make_structured_tool(
            tool=tool,
            inject_context=True,
        )

        # 模拟调用 - 注意:config 通过 RunnableConfig 参数传递,不是通过 input
        from langchain_core.runnables import RunnableConfig

        a = ExecuteKwargs()
        a.executor = "bk_username-X"
        structured_tool.invoke(
            {"state": {"user_id": "user_123", "tenant": "tenant_abc"}},
            config=RunnableConfig(configurable={"thread_id": "thread_456", "execute_kwargs": a}),
        )

        # 验证请求被正确调用
        assert mock_request.called
        call_kwargs = mock_request.call_args.kwargs

        # 验证 headers 和 body 中的模板被正确渲染
        print("✓ 工具调用成功")
        print(f"  - Headers: {call_kwargs.get('headers')}")
        print(f"  - Body: {call_kwargs.get('json')}")

        # 验证渲染结果
        headers = call_kwargs.get("headers", {})
        body = call_kwargs.get("json", {})

        assert headers.get("X-User-Id") == "user_123", "X-User-Id 应该从 state.user_id 渲染"
        assert headers.get("X-Thread-Id") == "thread_456", "X-Thread-Id 应该从 config 渲染"
        assert body.get("tenant") == "tenant_abc", "tenant 应该从 state.tenant 渲染"
        assert body.get("username") == "bk_username-X", "tenant 应该从 config 渲染"


@pytest.mark.asyncio
async def test_tool_with_langgraph_integration():
    """测试与 LangGraph 的集成 (使用 InjectedState)"""

    try:
        import inspect
        from typing import TypedDict

        from langchain_core.messages import AIMessage
        from langgraph.graph import END, START, StateGraph
        from langgraph.prebuilt import ToolNode
    except ImportError:
        pytest.skip("langgraph not installed")

    # Mock HTTP 请求
    with patch("requests.Session.request") as mock_request:
        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.headers = {"content-type": "application/json"}
        mock_response.json.return_value = {"result": "mocked"}
        mock_request.return_value = mock_response

        tool = Tool(
            tool_code="langgraph_test_tool",
            tool_name="LangGraph 集成工具",
            description="测试 LangGraph 集成",
            method="POST",
            url="https://example.com/api/langgraph",
            property={
                "header": [
                    {
                        "name": "X-User",
                        "type": "string",
                        "required": False,
                        "default": "{{ state.user }}",
                        "description": "用户",
                        "validate": {"enable": False, "rules": []},
                    }
                ],
                "body": [
                    {
                        "name": "session_id",
                        "type": "string",
                        "required": False,
                        "default": "{{ state.session_id }}",
                        "description": "会话ID",
                        "validate": {"enable": False, "rules": []},
                    }
                ],
            },
            extra=None,
        )

        structured_tool = make_structured_tool(
            tool=tool,
            inject_context=True,
        )

        # DEBUG: 检查工具函数签名
        print(f"Tool function: {structured_tool.func}")
        print(f"Function signature: {inspect.signature(structured_tool.func)}")
        print(f"Function annotations: {structured_tool.func.__annotations__}")

        # 构建 LangGraph
        class AgentState(TypedDict):
            messages: list
            user: str
            session_id: str

        workflow = StateGraph(AgentState)
        tool_node = ToolNode([structured_tool])
        workflow.add_node("tools", tool_node)
        workflow.add_edge(START, "tools")
        workflow.add_edge("tools", END)
        graph = workflow.compile()

        # 构造输入
        ai_message = AIMessage(
            content="", tool_calls=[{"name": "langgraph_test_tool", "args": {}, "id": "call_001", "type": "tool_call"}]
        )

        # 执行 Graph
        state = {"messages": [ai_message], "user": "alice", "session_id": "session_789"}

        await graph.ainvoke(state, config={"configurable": {"thread_id": "thread_123"}})

        # 验证工具被调用
        assert mock_request.called
        call_kwargs = mock_request.call_args.kwargs

        headers = call_kwargs.get("headers", {})
        body = call_kwargs.get("json", {})

        print("✓ LangGraph 集成测试成功")
        print(f"  - Headers: {headers}")
        print(f"  - Body: {body}")

        # 验证 state 被正确注入和渲染
        assert headers.get("X-User") == "alice", "X-User 应该从 state.user 渲染"
        assert body.get("session_id") == "session_789", "session_id 应该从 state.session_id 渲染"


# ONLY FOR DEBUG, NEED TO DELETE LATER
def test_get_mcp_tools_only():
    server_config = {
        "bk-itsm-prod-ticket": {
            "url": "https://bk-apigateway.apigw.o.woa.com/prod/api/v2/mcp-servers/bk-itsm-prod-ticket/application/mcp/",
            "transport": "streamable_http",
            "headers": {
                "X-Bkapi-Authorization": '{"access_token": "bWKN8FlNbROhBKxIE96g55i0l06oL7"}',
                "X-Bkapi-Timeout": "300",
            },
        }
    }
    _tools = make_mcp_tools(server_config, AgentOptions())
    assert _tools[0].metadata.get("mcp_name")
