# File: tests/unit/infrastructure/json/mcp/test_tool_registry.py
# Purpose: Unit tests for ToolRegistry class
# Context: Test tool registration, retrieval, and schema management

"""Unit tests for ToolRegistry component.

Test Coverage:
- Tool registration (basic, duplicate prevention, validation)
- Tool retrieval (single tool, nonexistent tool)
- Tool listing and discovery
- Schema management and retrieval
- Edge cases and error handling
"""

import pytest

from noveler.infrastructure.json.mcp.servers.tool_registry import (
    ToolDefinition,
    ToolRegistry,
)
from noveler.infrastructure.logging.unified_logger import get_logger


class TestToolRegistry:
    """Test suite for ToolRegistry class."""

    @pytest.fixture
    def registry(self) -> ToolRegistry:
        """Create a test registry instance.

        Returns:
            ToolRegistry configured for testing
        """
        return ToolRegistry(get_logger(__name__))

    @pytest.fixture
    def sample_tool(self) -> ToolDefinition:
        """Create a sample tool definition for testing.

        Returns:
            ToolDefinition with basic properties
        """
        def dummy_handler(text: str) -> str:
            """Dummy tool handler for testing."""
            return f"Processed: {text}"

        return ToolDefinition(
            name="test_tool",
            handler=dummy_handler,
            input_schema={
                "type": "object",
                "properties": {
                    "text": {"type": "string"}
                },
                "required": ["text"]
            },
            description="Test tool for unit testing"
        )

    def test_registry_initialization(self, registry: ToolRegistry) -> None:
        """Test that registry initializes with built-in tools.

        Purpose:
            Verify that registry loads default tool set on initialization.

        Expected:
            - Registry contains tools from _initialize_tools()
            - list_tools() returns non-empty list
        """
        tools = registry.list_tools()
        assert isinstance(tools, list)
        # Note: May be empty if _register_* methods are placeholders
        # In production, this would verify actual tool count

    def test_register_tool_basic(
        self, registry: ToolRegistry, sample_tool: ToolDefinition
    ) -> None:
        """Test basic tool registration.

        Purpose:
            Verify tool can be registered and retrieved.

        Expected:
            - Tool is stored in registry
            - get_tool() returns the handler
            - get_tool_definition() returns full definition
        """
        registry.register_tool(sample_tool)

        # Verify tool was registered
        assert registry.get_tool(sample_tool.name) is not None
        assert registry.get_tool(sample_tool.name) is sample_tool.handler

        # Verify full definition
        definition = registry.get_tool_definition(sample_tool.name)
        assert definition is not None
        assert definition.name == sample_tool.name
        assert definition.description == sample_tool.description

    def test_register_duplicate_raises_error(
        self, registry: ToolRegistry, sample_tool: ToolDefinition
    ) -> None:
        """Test that registering duplicate tool raises ValueError.

        Purpose:
            Ensure tool names are unique in registry.

        Expected:
            - First registration succeeds
            - Second registration with same name raises ValueError
        """
        registry.register_tool(sample_tool)

        with pytest.raises(ValueError, match="already registered"):
            registry.register_tool(sample_tool)

    def test_get_nonexistent_tool_returns_none(
        self, registry: ToolRegistry
    ) -> None:
        """Test retrieving nonexistent tool returns None.

        Purpose:
            Gracefully handle requests for unregistered tools.

        Expected:
            - get_tool() returns None for missing name
            - get_tool_definition() returns None for missing name
        """
        assert registry.get_tool("nonexistent_tool") is None
        assert registry.get_tool_definition("nonexistent_tool") is None

    def test_list_tools_returns_all_registered(
        self, registry: ToolRegistry
    ) -> None:
        """Test listing returns all registered tools.

        Purpose:
            Verify complete tool inventory can be retrieved.

        Expected:
            - list_tools() returns all ToolDefinition objects
            - Count matches number of registered tools
        """
        tools_before = len(registry.list_tools())

        # Register new tools
        tool1 = ToolDefinition(
            name="tool_1",
            handler=lambda: None,
            input_schema={},
            description="Tool 1"
        )
        tool2 = ToolDefinition(
            name="tool_2",
            handler=lambda: None,
            input_schema={},
            description="Tool 2"
        )

        registry.register_tool(tool1)
        registry.register_tool(tool2)

        tools_after = registry.list_tools()
        assert len(tools_after) >= tools_before + 2

    def test_get_tool_schemas(self, registry: ToolRegistry) -> None:
        """Test retrieving all tool schemas.

        Purpose:
            Verify schemas are available in MCP protocol format.

        Expected:
            - Returns dict mapping tool names to input schemas
            - All registered tools are included
        """
        schema_tool = ToolDefinition(
            name="schema_test_tool",
            handler=lambda: None,
            input_schema={
                "type": "object",
                "properties": {
                    "param1": {"type": "string"},
                    "param2": {"type": "integer"}
                },
                "required": ["param1"]
            },
            description="Tool with schema"
        )

        registry.register_tool(schema_tool)
        schemas = registry.get_tool_schemas()

        assert "schema_test_tool" in schemas
        assert schemas["schema_test_tool"] == schema_tool.input_schema
        assert schemas["schema_test_tool"]["properties"]["param1"]["type"] == "string"

    def test_tool_handler_execution(
        self, registry: ToolRegistry, sample_tool: ToolDefinition
    ) -> None:
        """Test that registered tool handler can be executed.

        Purpose:
            Verify handler is callable and produces expected results.

        Expected:
            - Retrieved handler is callable
            - Handler produces correct output
        """
        registry.register_tool(sample_tool)

        handler = registry.get_tool(sample_tool.name)
        assert callable(handler)

        result = handler(text="test input")
        assert result == "Processed: test input"

    def test_multiple_tool_registration_order(
        self, registry: ToolRegistry
    ) -> None:
        """Test registering multiple tools maintains independence.

        Purpose:
            Verify tool registration order doesn't affect retrieval.

        Expected:
            - All tools are accessible regardless of registration order
            - Each tool returns its own handler
        """
        tools = [
            ToolDefinition(
                name=f"tool_{i}",
                handler=lambda i=i: f"Tool {i}",
                input_schema={},
                description=f"Tool {i}"
            )
            for i in range(5)
        ]

        for tool in tools:
            registry.register_tool(tool)

        # Verify all tools are retrievable
        for i, tool in enumerate(tools):
            retrieved = registry.get_tool(tool.name)
            assert retrieved is not None
            assert retrieved(i) == f"Tool {i}"

    def test_tool_definition_immutability(
        self, registry: ToolRegistry, sample_tool: ToolDefinition
    ) -> None:
        """Test that registered tool definitions cannot be modified externally.

        Purpose:
            Ensure registry maintains data integrity.

        Expected:
            - Retrieved definition is the same object
            - Modifications to external reference don't affect registry
        """
        registry.register_tool(sample_tool)

        retrieved = registry.get_tool_definition(sample_tool.name)
        assert retrieved.name == sample_tool.name

        # Note: This test documents current behavior.
        # In production, consider making definitions immutable (dataclass frozen=True)

    def test_empty_tool_name_raises_error(
        self, registry: ToolRegistry
    ) -> None:
        """Test that empty tool name is rejected.

        Purpose:
            Validate tool name requirements.

        Expected:
            - Empty string tool name is prevented or handled
        """
        # This test documents expected behavior for edge cases
        invalid_tool = ToolDefinition(
            name="",  # Empty name
            handler=lambda: None,
            input_schema={},
            description="Invalid"
        )

        # Depending on implementation, this might raise or be allowed
        # This test is a placeholder for validation logic
        try:
            registry.register_tool(invalid_tool)
            # If no error, verify behavior is consistent
            retrieved = registry.get_tool("")
            # Empty names should either work consistently or be rejected
        except ValueError:
            # Expected if validation is implemented
            pass
