"""Tests for proxym.mcp module — registry, router, client."""

from __future__ import annotations

import pytest

from proxym.context._context import ProjectContext
from proxym.mcp.registry import MCPRegistry, MCPServerSpec, MCPTransport
from proxym.mcp.router import MCPRouter
from proxym.mcp.client import MCPClient, MCPToolResult


# ── MCPServerSpec ─────────────────────────────────────────────

class TestMCPServerSpec:
    def test_defaults(self):
        spec = MCPServerSpec(name="test")
        assert spec.name == "test"
        assert spec.transport == MCPTransport.HTTP
        assert spec.enabled is True
        assert spec.priority == 10
        assert spec.tools == []

    def test_custom_values(self):
        spec = MCPServerSpec(
            name="github",
            url="http://localhost:3001",
            transport=MCPTransport.SSE,
            tools=["search_issues", "create_pr"],
            description="GitHub MCP",
            priority=5,
        )
        assert spec.transport == MCPTransport.SSE
        assert len(spec.tools) == 2
        assert spec.priority == 5


# ── MCPRegistry ──────────────────────────────────────────────

class TestMCPRegistry:
    def test_register_and_get(self):
        reg = MCPRegistry()
        spec = MCPServerSpec(name="fs", tools=["read", "write"])
        reg.register(spec)
        assert reg.get("fs") is spec

    def test_get_nonexistent(self):
        reg = MCPRegistry()
        assert reg.get("nope") is None

    def test_unregister(self):
        reg = MCPRegistry()
        reg.register(MCPServerSpec(name="fs"))
        assert reg.unregister("fs") is True
        assert reg.get("fs") is None
        assert reg.unregister("fs") is False

    def test_list_servers_enabled_only(self):
        reg = MCPRegistry()
        reg.register(MCPServerSpec(name="a", enabled=True))
        reg.register(MCPServerSpec(name="b", enabled=False))
        reg.register(MCPServerSpec(name="c", enabled=True))
        servers = reg.list_servers(enabled_only=True)
        assert len(servers) == 2
        names = {s.name for s in servers}
        assert "b" not in names

    def test_list_servers_all(self):
        reg = MCPRegistry()
        reg.register(MCPServerSpec(name="a", enabled=True))
        reg.register(MCPServerSpec(name="b", enabled=False))
        servers = reg.list_servers(enabled_only=False)
        assert len(servers) == 2

    def test_available_names(self):
        reg = MCPRegistry()
        reg.register(MCPServerSpec(name="a", enabled=True))
        reg.register(MCPServerSpec(name="b", enabled=False))
        assert reg.available_names() == {"a"}

    def test_servers_for_tool(self):
        reg = MCPRegistry()
        reg.register(MCPServerSpec(name="fs", tools=["read", "write"], priority=5))
        reg.register(MCPServerSpec(name="code2llm", tools=["read", "analyze"], priority=10))
        servers = reg.servers_for_tool("read")
        assert len(servers) == 2
        assert servers[0].name == "fs"  # lower priority = first

    def test_servers_for_tool_none(self):
        reg = MCPRegistry()
        reg.register(MCPServerSpec(name="fs", tools=["read"]))
        assert reg.servers_for_tool("deploy") == []

    def test_load_from_config(self):
        reg = MCPRegistry()
        config = [
            {"name": "github", "url": "http://localhost:3001", "tools": ["search"], "transport": "http"},
            {"name": "fs", "url": "http://localhost:3002", "tools": ["read", "write"], "priority": 5},
        ]
        reg.load_from_config(config)
        assert reg.get("github") is not None
        assert reg.get("fs").priority == 5
        assert len(reg.list_servers()) == 2

    def test_sorted_by_priority(self):
        reg = MCPRegistry()
        reg.register(MCPServerSpec(name="low", priority=20))
        reg.register(MCPServerSpec(name="high", priority=1))
        reg.register(MCPServerSpec(name="mid", priority=10))
        servers = reg.list_servers()
        assert [s.name for s in servers] == ["high", "mid", "low"]


# ── MCPRouter ────────────────────────────────────────────────

class TestMCPRouter:
    def _make_registry(self) -> MCPRegistry:
        reg = MCPRegistry()
        reg.register(MCPServerSpec(name="filesystem", tools=["read", "write"]))
        reg.register(MCPServerSpec(name="github", tools=["search_issues", "create_pr"]))
        reg.register(MCPServerSpec(name="code2llm", tools=["analyze"]))
        return reg

    def test_select_servers_python_project(self):
        router = MCPRouter(self._make_registry())
        ctx = ProjectContext(stack=["python", "fastapi"])
        servers = router.select_servers(ctx)
        names = {s.name for s in servers}
        assert "filesystem" in names
        assert "code2llm" in names

    def test_select_servers_empty_context(self):
        router = MCPRouter(self._make_registry())
        ctx = ProjectContext()
        servers = router.select_servers(ctx)
        names = {s.name for s in servers}
        assert "filesystem" in names

    def test_get_tools_for_servers(self):
        reg = self._make_registry()
        router = MCPRouter(reg)
        servers = [reg.get("filesystem"), reg.get("github")]
        tools = router.get_tools_for_servers(servers)
        assert len(tools) == 4  # 2 from filesystem + 2 from github
        assert all(t["type"] == "function" for t in tools)
        names = {t["function"]["name"] for t in tools}
        assert "filesystem__read" in names
        assert "github__search_issues" in names

    def test_resolve_tool_call(self):
        router = MCPRouter(self._make_registry())
        spec, tool = router.resolve_tool_call("github__search_issues")
        assert spec.name == "github"
        assert tool == "search_issues"

    def test_resolve_tool_call_no_separator(self):
        router = MCPRouter(self._make_registry())
        spec, tool = router.resolve_tool_call("filesystem")
        assert spec.name == "filesystem"
        assert tool == "filesystem"

    def test_resolve_tool_call_unknown(self):
        router = MCPRouter(self._make_registry())
        spec, tool = router.resolve_tool_call("unknown__thing")
        assert spec is None
        assert tool == "thing"


# ── MCPClient ────────────────────────────────────────────────

class TestMCPClient:
    def test_parse_response_dict_content(self):
        data = {
            "result": {
                "content": [{"type": "text", "text": "Hello world"}],
            }
        }
        result = MCPClient._parse_response(data, "test", "tool", 10.0)
        assert result.success is True
        assert result.content == "Hello world"
        assert result.elapsed_ms == 10.0

    def test_parse_response_string_content(self):
        data = {"result": {"content": "plain text"}}
        result = MCPClient._parse_response(data, "test", "tool", 5.0)
        assert result.content == "plain text"

    def test_parse_response_string_result(self):
        data = {"result": "direct string"}
        result = MCPClient._parse_response(data, "test", "tool", 1.0)
        assert result.content == "direct string"

    def test_parse_response_empty(self):
        data = {"result": {}}
        result = MCPClient._parse_response(data, "test", "tool", 0.0)
        assert result.success is True
        assert result.content == ""

    @pytest.mark.asyncio
    async def test_unsupported_transport(self):
        client = MCPClient()
        server = MCPServerSpec(name="stdio", transport=MCPTransport.STDIO)
        result = await client.invoke_tool(server, "test")
        assert result.success is False
        assert "Unsupported transport" in result.error

    @pytest.mark.asyncio
    async def test_connection_failure(self):
        client = MCPClient(timeout=1.0, max_retries=0)
        server = MCPServerSpec(
            name="dead", url="http://127.0.0.1:19999",
            transport=MCPTransport.HTTP,
        )
        result = await client.invoke_tool(server, "test")
        assert result.success is False


class TestMCPToolResult:
    def test_defaults(self):
        r = MCPToolResult(success=True, content="ok")
        assert r.success is True
        assert r.content == "ok"
        assert r.error == ""
        assert r.metadata == {}
