"""
Tests for MCP tool discovery and integration.

This test suite ensures that:
1. MCP tools are properly discovered from configured servers
2. Tool naming follows the server__toolname convention
3. Tools are properly integrated into the reply engine
4. The new OpenAI-standard tool calling format works correctly
"""

import pytest
from jarvis.tools.registry import discover_mcp_tools, generate_tools_description, generate_tools_json_schema, run_tool_with_retries, ToolExecutionResult


class DummyCfg:
    def __init__(self):
        self.mcps = {}
        self.voice_debug = False


class DummyDB:
    pass


@pytest.mark.unit
def test_discover_mcp_tools_empty_config():
    """Test that empty MCP config returns empty tools dict."""
    result, errors = discover_mcp_tools({})
    assert result == {}
    assert errors == {}


@pytest.mark.unit
def test_discover_mcp_tools_with_fake_server(monkeypatch):
    """Test discovery of tools from a fake MCP server."""
    # Mock the MCPClient
    class FakeClient:
        def __init__(self, config):
            self.config = config

        def list_tools(self, server_name):
            if server_name == "test-server":
                return [
                    {"name": "read", "description": "Read a file"},
                    {"name": "write", "description": "Write to a file"},
                    {"name": "list", "description": "List directory contents"},
                ]
            return []

    import jarvis.tools.registry as registry_mod
    monkeypatch.setattr(registry_mod, "MCPClient", FakeClient)

    mcps_config = {
        "test-server": {
            "command": "fake-cmd",
            "args": ["--test"]
        }
    }

    result, errors = discover_mcp_tools(mcps_config)

    # Should create tools with server__toolname format
    expected_tools = {
        "test-server__read",
        "test-server__write",
        "test-server__list"
    }

    assert set(result.keys()) == expected_tools

    # Check tool spec properties
    read_tool = result["test-server__read"]
    assert read_tool.name == "test-server__read"
    assert "Read a file" in read_tool.description


@pytest.mark.unit
def test_discover_mcp_tools_handles_server_errors(monkeypatch):
    """Test that discovery continues even if one server fails."""
    class FakeClient:
        def __init__(self, config):
            self.config = config

        def list_tools(self, server_name):
            if server_name == "good-server":
                return [{"name": "tool1", "description": "Good tool"}]
            elif server_name == "bad-server":
                raise Exception("Server failed")
            return []

    import jarvis.tools.registry as registry_mod
    monkeypatch.setattr(registry_mod, "MCPClient", FakeClient)

    mcps_config = {
        "good-server": {"command": "good"},
        "bad-server": {"command": "bad"}
    }

    result, errors = discover_mcp_tools(mcps_config)

    # Should still get tools from the good server
    assert "good-server__tool1" in result
    assert len(result) == 1

    # Should report the error for the bad server
    assert "bad-server" in errors
    assert "Server failed" in errors["bad-server"]


@pytest.mark.unit
def test_discover_mcp_tools_returns_empty_errors_on_success(monkeypatch):
    """Test that successful discovery returns empty errors dict."""
    class FakeClient:
        def __init__(self, config):
            self.config = config

        def list_tools(self, server_name):
            return [{"name": "tool1", "description": "A tool"}]

    import jarvis.tools.registry as registry_mod
    monkeypatch.setattr(registry_mod, "MCPClient", FakeClient)

    mcps_config = {"server": {"command": "cmd"}}
    result, errors = discover_mcp_tools(mcps_config)

    assert len(result) == 1
    assert errors == {}


@pytest.mark.unit
def test_generate_tools_description_includes_mcp_tools():
    """Test that MCP tools are included in the tools description."""
    from jarvis.tools.registry import ToolSpec

    mcp_tools = {
            "server__read": ToolSpec(
                name="server__read",
                description="Read a file from the server",
                inputSchema={
                    "type": "object",
                    "properties": {
                        "path": {
                            "type": "string",
                            "description": "File path to read"
                        }
                    },
                    "required": ["path"]
                }
            )
        }

    allowed_tools = ["server__read", "screenshot"]
    description = generate_tools_description(allowed_tools, mcp_tools)

    assert "server__read" in description
    assert "Read a file from the server" in description
    assert "screenshot" in description  # Should still include builtin tools


@pytest.mark.unit
def test_mcp_tool_execution_new_format(monkeypatch):
    """Test execution of MCP tools using the new server__toolname format."""
    db = DummyDB()
    cfg = DummyCfg()
    cfg.mcps = {"test-server": {"command": "fake", "args": []}}

    class FakeClient:
        def __init__(self, config):
            self.config = config

        def invoke_tool(self, server_name, tool_name, arguments):
            assert server_name == "test-server"
            assert tool_name == "read"
            assert arguments == {"path": "/test/file.txt"}
            return {"text": "file contents", "isError": False}

    import jarvis.tools.registry as registry_mod
    monkeypatch.setattr(registry_mod, "MCPClient", FakeClient)

    result = run_tool_with_retries(
        db=db,
        cfg=cfg,
        tool_name="test-server__read",
        tool_args={"path": "/test/file.txt"},
        system_prompt="",
        original_prompt="",
        redacted_text="",
        max_retries=0
    )

    assert result.success is True
    assert result.reply_text == "file contents"
    assert result.error_message is None


@pytest.mark.unit
def test_mcp_tool_execution_error_handling(monkeypatch):
    """Test that MCP tool errors are properly handled."""
    db = DummyDB()
    cfg = DummyCfg()
    cfg.mcps = {"test-server": {"command": "fake", "args": []}}

    class FakeClient:
        def __init__(self, config):
            self.config = config

        def invoke_tool(self, server_name, tool_name, arguments):
            return {"text": "Permission denied", "isError": True}

    import jarvis.tools.registry as registry_mod
    monkeypatch.setattr(registry_mod, "MCPClient", FakeClient)

    result = run_tool_with_retries(
        db=db,
        cfg=cfg,
        tool_name="test-server__read",
        tool_args={"path": "/forbidden/file.txt"},
        system_prompt="",
        original_prompt="",
        redacted_text="",
        max_retries=0
    )

    assert result.success is False
    assert result.error_message == "Permission denied"


@pytest.mark.unit
def test_mcp_tool_invalid_server_name():
    """Test that invalid server names in tool names are handled."""
    db = DummyDB()
    cfg = DummyCfg()
    cfg.mcps = {"valid-server": {"command": "fake", "args": []}}

    result = run_tool_with_retries(
        db=db,
        cfg=cfg,
        tool_name="invalid-server__read",
        tool_args={"path": "/test/file.txt"},
        system_prompt="",
        original_prompt="",
        redacted_text="",
        max_retries=0
    )

    # Should fail gracefully since server not configured
    assert result.success is False
    assert result.error_message is not None
    assert "invalid-server" in result.error_message.lower()


@pytest.mark.unit
def test_mcp_tool_exception_handling(monkeypatch):
    """Test that exceptions during MCP tool execution are caught."""
    db = DummyDB()
    cfg = DummyCfg()
    cfg.mcps = {"test-server": {"command": "fake", "args": []}}

    class FakeClient:
        def __init__(self, config):
            self.config = config

        def invoke_tool(self, server_name, tool_name, arguments):
            raise Exception("Connection failed")

    import jarvis.tools.registry as registry_mod
    monkeypatch.setattr(registry_mod, "MCPClient", FakeClient)

    result = run_tool_with_retries(
        db=db,
        cfg=cfg,
        tool_name="test-server__read",
        tool_args={"path": "/test/file.txt"},
        system_prompt="",
        original_prompt="",
        redacted_text="",
        max_retries=0
    )

    assert result.success is False
    assert "Connection failed" in result.error_message


@pytest.mark.unit
def test_generate_tools_json_schema_returns_openai_format():
    """Test that generate_tools_json_schema returns OpenAI-compatible format for native tool calling."""
    from jarvis.tools.registry import ToolSpec

    mcp_tools = {
        "server__read": ToolSpec(
            name="server__read",
            description="Read a file from the server",
            inputSchema={
                "type": "object",
                "properties": {
                    "path": {
                        "type": "string",
                        "description": "File path to read"
                    }
                },
                "required": ["path"]
            }
        )
    }

    allowed_tools = ["server__read", "screenshot"]
    tools_schema = generate_tools_json_schema(allowed_tools, mcp_tools)

    # Should return a list
    assert isinstance(tools_schema, list)
    assert len(tools_schema) >= 2  # At least screenshot and server__read

    # Each tool should have the OpenAI format
    for tool in tools_schema:
        assert "type" in tool
        assert tool["type"] == "function"
        assert "function" in tool
        assert "name" in tool["function"]
        assert "description" in tool["function"]
        assert "parameters" in tool["function"]

    # Check that MCP tool is included
    tool_names = [t["function"]["name"] for t in tools_schema]
    assert "server__read" in tool_names
    assert "screenshot" in tool_names

    # Check MCP tool has correct schema
    server_read_tool = next(t for t in tools_schema if t["function"]["name"] == "server__read")
    assert server_read_tool["function"]["description"] == "Read a file from the server"
    assert "properties" in server_read_tool["function"]["parameters"]
    assert "path" in server_read_tool["function"]["parameters"]["properties"]


@pytest.mark.unit
def test_generate_tools_json_schema_handles_empty_input():
    """Test that generate_tools_json_schema handles empty or missing inputs gracefully."""
    # With no MCP tools
    tools_schema = generate_tools_json_schema(["screenshot"], None)
    assert isinstance(tools_schema, list)
    assert len(tools_schema) >= 1

    # With empty MCP tools dict
    tools_schema = generate_tools_json_schema(["screenshot"], {})
    assert isinstance(tools_schema, list)
    assert len(tools_schema) >= 1
