"""Tests for MCPServer - FastAPI application exposing tools via HTTP endpoints"""

import pytest
import httpx

from agentkit.mcp.server import MCPServer
from agentkit.tools.function_tool import FunctionTool
from agentkit.tools.registry import ToolRegistry


# ── Helper functions ──────────────────────────────────────


async def add_numbers(a: int, b: int) -> dict:
    return {"sum": a + b}


async def failing_tool() -> dict:
    raise RuntimeError("tool execution failed")


# ── Fixtures ──────────────────────────────────────────────


@pytest.fixture
def registry_with_tools():
    """ToolRegistry with a couple of registered tools."""
    registry = ToolRegistry()
    registry.register(
        FunctionTool(name="add", description="Add two numbers", func=add_numbers)
    )
    registry.register(
        FunctionTool(name="fail", description="Always fails", func=failing_tool)
    )
    return registry


@pytest.fixture
def empty_registry():
    """Empty ToolRegistry."""
    return ToolRegistry()


@pytest.fixture
def client_factory():
    """Factory that creates an httpx.AsyncClient for a given MCPServer."""

    def _factory(server: MCPServer) -> httpx.AsyncClient:
        app = server.get_app()
        transport = httpx.ASGITransport(app=app)
        return httpx.AsyncClient(transport=transport, base_url="http://test")

    return _factory


# ── Health endpoint ───────────────────────────────────────


class TestHealthEndpoint:
    async def test_health_returns_ok(self, client_factory):
        server = MCPServer()
        async with client_factory(server) as client:
            resp = await client.get("/health")
        assert resp.status_code == 200
        assert resp.json() == {"status": "ok"}


# ── List tools endpoint ──────────────────────────────────


class TestListTools:
    async def test_list_tools_empty_registry(self, client_factory, empty_registry):
        server = MCPServer(tool_registry=empty_registry)
        async with client_factory(server) as client:
            resp = await client.get("/tools/list")
        assert resp.status_code == 200
        body = resp.json()
        assert body == {"tools": []}

    async def test_list_tools_no_registry(self, client_factory):
        server = MCPServer()
        async with client_factory(server) as client:
            resp = await client.get("/tools/list")
        assert resp.status_code == 200
        body = resp.json()
        assert body == {"tools": []}

    async def test_list_tools_with_registered_tools(self, client_factory, registry_with_tools):
        server = MCPServer(tool_registry=registry_with_tools)
        async with client_factory(server) as client:
            resp = await client.get("/tools/list")
        assert resp.status_code == 200
        body = resp.json()
        tools = body["tools"]
        assert len(tools) == 2
        names = {t["name"] for t in tools}
        assert names == {"add", "fail"}
        # Verify tool shape
        for t in tools:
            assert "name" in t
            assert "description" in t
            assert "inputSchema" in t

    async def test_list_tools_includes_input_schema(self, client_factory, registry_with_tools):
        server = MCPServer(tool_registry=registry_with_tools)
        async with client_factory(server) as client:
            resp = await client.get("/tools/list")
        body = resp.json()
        add_tool = next(t for t in body["tools"] if t["name"] == "add")
        assert "properties" in add_tool["inputSchema"]


# ── Call tool endpoint ───────────────────────────────────


class TestCallTool:
    async def test_call_tool_success(self, client_factory, registry_with_tools):
        server = MCPServer(tool_registry=registry_with_tools)
        async with client_factory(server) as client:
            resp = await client.post("/tools/call", json={"name": "add", "arguments": {"a": 3, "b": 5}})
        assert resp.status_code == 200
        body = resp.json()
        assert "content" in body
        assert body["content"][0]["type"] == "text"
        assert "8" in body["content"][0]["text"]

    async def test_call_tool_missing_name(self, client_factory, registry_with_tools):
        server = MCPServer(tool_registry=registry_with_tools)
        async with client_factory(server) as client:
            resp = await client.post("/tools/call", json={"arguments": {"a": 1}})
        assert resp.status_code == 200
        body = resp.json()
        assert "error" in body

    async def test_call_tool_no_registry(self, client_factory):
        server = MCPServer()
        async with client_factory(server) as client:
            resp = await client.post("/tools/call", json={"name": "add", "arguments": {}})
        assert resp.status_code == 200
        body = resp.json()
        assert "error" in body

    async def test_call_tool_execution_error(self, client_factory, registry_with_tools):
        server = MCPServer(tool_registry=registry_with_tools)
        async with client_factory(server) as client:
            resp = await client.post("/tools/call", json={"name": "fail", "arguments": {}})
        assert resp.status_code == 200
        body = resp.json()
        assert body.get("isError") is True
        assert "content" in body
        assert "tool execution failed" in body["content"][0]["text"]

    async def test_call_tool_nonexistent_tool(self, client_factory, registry_with_tools):
        server = MCPServer(tool_registry=registry_with_tools)
        async with client_factory(server) as client:
            resp = await client.post("/tools/call", json={"name": "nonexistent", "arguments": {}})
        assert resp.status_code == 200
        body = resp.json()
        assert body.get("isError") is True


# ── Server construction ──────────────────────────────────


class TestMCPServerConstruction:
    def test_default_host_and_port(self):
        server = MCPServer()
        assert server._host == "0.0.0.0"
        assert server._port == 8080

    def test_custom_host_and_port(self):
        server = MCPServer(host="127.0.0.1", port=9090)
        assert server._host == "127.0.0.1"
        assert server._port == 9090

    def test_get_app_creates_app(self):
        server = MCPServer()
        app = server.get_app()
        assert app is not None
        # Second call returns same instance
        assert server.get_app() is app

    def test_get_app_lazy_creation(self):
        server = MCPServer()
        assert server._app is None
        server.get_app()
        assert server._app is not None
