"""ToolRegistry 单元测试"""

import pytest
from unittest.mock import AsyncMock, MagicMock

from app.agents.tools.manifest import (
    HookConfig,
    PermissionClass,
    SideEffectScope,
    ToolCategory,
    ToolManifest,
)
from app.agents.tools.registry import (
    ToolRegistry,
    get_tool_registry,
    reset_tool_registry,
)


@pytest.fixture
def registry():
    """创建空的 ToolRegistry 实例"""
    return ToolRegistry()


@pytest.fixture
def sample_manifest():
    """创建示例工具元数据"""
    return ToolManifest(
        name="test_tool",
        description="A test tool",
        category=ToolCategory.READ,
        parameters={
            "type": "object",
            "properties": {"input": {"type": "string"}},
            "required": ["input"],
        },
        return_schema={"type": "string"},
        permission_class=PermissionClass.READ,
        side_effect_scope=SideEffectScope.NONE,
        requires_confirmation=False,
        is_streaming=False,
        tags=["test", "sample"],
    )


@pytest.fixture
def sample_executor():
    """创建示例执行器"""

    def executor(input: str) -> str:
        return f"processed: {input}"

    return executor


class TestToolRegistryInit:
    """测试 ToolRegistry 初始化"""

    def test_empty_registry(self, registry):
        assert len(registry) == 0
        assert list(registry) == []

    def test_empty_contains_false(self, registry):
        assert "test_tool" not in registry


class TestToolRegistryRegister:
    """测试工具注册"""

    def test_register_single_tool(self, registry, sample_manifest, sample_executor):
        registry.register(sample_manifest, sample_executor)

        assert len(registry) == 1
        assert "test_tool" in registry

    def test_register_duplicate_raises(self, registry, sample_manifest, sample_executor):
        registry.register(sample_manifest, sample_executor)

        with pytest.raises(ValueError, match="Tool already registered"):
            registry.register(sample_manifest, sample_executor)

    def test_register_with_hooks(self, registry, sample_manifest, sample_executor):
        hooks = [
            HookConfig(name="audit", hook_type="pre_tool_use"),
            HookConfig(name="security", hook_type="post_tool_use"),
        ]
        registry.register(sample_manifest, sample_executor, hooks=hooks)

        tool_hooks = registry.get_hooks("test_tool")
        assert len(tool_hooks) == 2


class TestToolRegistryGet:
    """测试获取工具"""

    def test_get_existing_tool(self, registry, sample_manifest, sample_executor):
        registry.register(sample_manifest, sample_executor)

        manifest = registry.get("test_tool")
        assert manifest is not None
        assert manifest.name == "test_tool"

    def test_get_nonexistent_tool(self, registry):
        assert registry.get("nonexistent") is None

    def test_get_executor(self, registry, sample_manifest, sample_executor):
        registry.register(sample_manifest, sample_executor)

        executor = registry.get_executor("test_tool")
        assert executor is not None
        assert executor("hello") == "processed: hello"

    def test_get_nonexistent_executor(self, registry):
        assert registry.get_executor("nonexistent") is None


class TestToolRegistryList:
    """测试工具列表"""

    def test_list_all_empty(self, registry):
        assert registry.list_all() == []

    def test_list_all_with_tools(self, registry, sample_manifest, sample_executor):
        registry.register(sample_manifest, sample_executor)

        manifest2 = ToolManifest(
            name="test_tool2",
            description="Another test tool",
            category=ToolCategory.WRITE,
            parameters={"type": "object"},
            return_schema={"type": "string"},
            permission_class=PermissionClass.WRITE,
            side_effect_scope=SideEffectScope.LOCAL_STATE,
            requires_confirmation=True,
        )
        registry.register(manifest2, sample_executor)

        all_tools = registry.list_all()
        assert len(all_tools) == 2

    def test_list_by_category(self, registry, sample_manifest, sample_executor):
        registry.register(sample_manifest, sample_executor)

        manifest2 = ToolManifest(
            name="write_tool",
            description="A write tool",
            category=ToolCategory.WRITE,
            parameters={"type": "object"},
            return_schema={"type": "string"},
            permission_class=PermissionClass.WRITE,
            side_effect_scope=SideEffectScope.LOCAL_STATE,
            requires_confirmation=True,
        )
        registry.register(manifest2, sample_executor)

        read_tools = registry.list_by_category(ToolCategory.READ)
        write_tools = registry.list_by_category(ToolCategory.WRITE)

        assert len(read_tools) == 1
        assert len(write_tools) == 1

    def test_list_by_permission(self, registry, sample_manifest, sample_executor):
        registry.register(sample_manifest, sample_executor)

        read_tools = registry.list_by_permission(PermissionClass.READ)
        write_tools = registry.list_by_permission(PermissionClass.WRITE)

        assert len(read_tools) == 1
        assert len(write_tools) == 0


class TestToolRegistrySearch:
    """测试工具搜索"""

    def test_search_by_tag(self, registry, sample_manifest, sample_executor):
        registry.register(sample_manifest, sample_executor)

        results = registry.search_by_tag("test")
        assert len(results) == 1
        assert results[0].name == "test_tool"

        results = registry.search_by_tag("nonexistent")
        assert len(results) == 0

    def test_search_by_name(self, registry, sample_manifest, sample_executor):
        registry.register(sample_manifest, sample_executor)

        results = registry.search_by_name("test")
        assert len(results) == 1

        results = registry.search_by_name("tool")
        assert len(results) == 1

        results = registry.search_by_name("xyz")
        assert len(results) == 0


class TestToolRegistryUtility:
    """测试工具方法"""

    def test_requires_confirmation_true(self, registry, sample_manifest, sample_executor):
        sample_manifest.requires_confirmation = True
        registry.register(sample_manifest, sample_executor)

        assert registry.get_requires_confirmation("test_tool") is True

    def test_requires_confirmation_false(self, registry, sample_manifest, sample_executor):
        registry.register(sample_manifest, sample_executor)

        assert registry.get_requires_confirmation("test_tool") is False

    def test_requires_confirmation_nonexistent(self, registry):
        assert registry.get_requires_confirmation("nonexistent") is False

    def test_is_streaming_true(self, registry, sample_manifest, sample_executor):
        sample_manifest.is_streaming = True
        registry.register(sample_manifest, sample_executor)

        assert registry.get_is_streaming("test_tool") is True

    def test_is_streaming_false(self, registry, sample_manifest, sample_executor):
        registry.register(sample_manifest, sample_executor)

        assert registry.get_is_streaming("test_tool") is False


class TestToolRegistryUnregister:
    """测试工具注销"""

    def test_unregister_existing(self, registry, sample_manifest, sample_executor):
        registry.register(sample_manifest, sample_executor)

        assert registry.unregister("test_tool") is True
        assert len(registry) == 0
        assert "test_tool" not in registry

    def test_unregister_nonexistent(self, registry):
        assert registry.unregister("nonexistent") is False

    def test_unregister_clears_hooks(self, registry, sample_manifest, sample_executor):
        hooks = [HookConfig(name="test", hook_type="pre_tool_use")]
        registry.register(sample_manifest, sample_executor, hooks=hooks)

        registry.unregister("test_tool")

        assert registry.get_hooks("test_tool") == []


class TestToolRegistryClear:
    """测试清空注册表"""

    def test_clear(self, registry, sample_manifest, sample_executor):
        registry.register(sample_manifest, sample_executor)

        manifest2 = ToolManifest(
            name="tool2",
            description="Another tool",
            category=ToolCategory.READ,
            parameters={"type": "object"},
            return_schema={"type": "string"},
            permission_class=PermissionClass.READ,
            side_effect_scope=SideEffectScope.NONE,
            requires_confirmation=False,
        )
        registry.register(manifest2, sample_executor)

        registry.clear()

        assert len(registry) == 0


class TestGlobalRegistry:
    """测试全局注册表"""

    def test_get_tool_registry_returns_singleton(self):
        reset_tool_registry()

        reg1 = get_tool_registry()
        reg2 = get_tool_registry()

        assert reg1 is reg2

    def test_reset_tool_registry(self):
        reset_tool_registry()
        reg1 = get_tool_registry()

        reset_tool_registry()
        reg2 = get_tool_registry()

        # After reset, should get a new instance
        # (Note: this tests the reset function works)


class TestToolManifest:
    """测试 ToolManifest"""

    def test_to_dict(self, sample_manifest):
        d = sample_manifest.to_dict()

        assert d["name"] == "test_tool"
        assert d["description"] == "A test tool"
        assert d["category"] == "read"
        assert d["permission_class"] == "read"
        assert d["side_effect_scope"] == "none"
        assert d["requires_confirmation"] is False
        assert d["is_streaming"] is False
        assert d["tags"] == ["test", "sample"]


class TestHookConfig:
    """测试 HookConfig"""

    def test_matches_tool_with_no_filter(self):
        hook = HookConfig(name="test", hook_type="pre_tool_use")

        assert hook.matches_tool("any_tool") is True

    def test_matches_tool_with_filter(self):
        hook = HookConfig(name="test", hook_type="pre_tool_use", filter_names=["tool_a", "tool_b"])

        assert hook.matches_tool("tool_a") is True
        assert hook.matches_tool("tool_b") is True
        assert hook.matches_tool("tool_c") is False
