"""Tests for gfp_mcp.server module (Phase 5).

These tests verify the simplified MCP server implementation with
handler-based dispatch for tool calls.
"""

from __future__ import annotations

import json
from typing import Any
from unittest.mock import AsyncMock, MagicMock, patch

import pytest
from mcp.types import ImageContent, TextContent


class TestCreateServer:
    """Tests for create_server function."""

    def test_creates_server_instance(self) -> None:
        """Test that create_server returns a Server instance."""
        from gfp_mcp.server import create_server

        server = create_server()
        assert server is not None
        assert server.name == "gdsfactoryplus"

    def test_server_has_http_client(self) -> None:
        """Test that server has FastAPIClient attached."""
        from gfp_mcp.client import FastAPIClient
        from gfp_mcp.server import create_server

        server = create_server()
        client = server._http_client  # noqa: SLF001
        assert isinstance(client, FastAPIClient)

    def test_custom_api_url_passed_to_client(self) -> None:
        """Test that custom API URL is passed to client."""
        from gfp_mcp.server import create_server

        custom_url = "http://localhost:9999"
        server = create_server(api_url=custom_url)
        client = server._http_client  # noqa: SLF001
        assert client.base_url == custom_url


class TestCallToolHandler:
    """Tests for call_tool handler in the server."""

    @pytest.mark.anyio
    async def test_call_tool_unknown_tool(self) -> None:
        """Test that unknown tool returns error."""
        from gfp_mcp.server import create_server

        server = create_server()

        # Directly test the handler logic by calling it
        result = await self._call_tool(server, "nonexistent_tool", {})

        assert len(result) == 1
        assert isinstance(result[0], TextContent)
        response = json.loads(result[0].text)
        assert "error" in response
        assert "Unknown tool" in response["error"]

    @pytest.mark.anyio
    async def test_call_tool_list_projects(self) -> None:
        """Test that list_projects tool works through handler dispatch."""
        from gfp_mcp.server import create_server

        server = create_server()
        client = server._http_client  # noqa: SLF001

        # Mock the client's list_projects method
        with patch.object(client, "list_projects", return_value=[]):
            result = await self._call_tool(server, "list_projects", {})

            assert len(result) == 1
            assert isinstance(result[0], TextContent)
            response = json.loads(result[0].text)
            assert "projects" in response

    @pytest.mark.anyio
    async def test_call_tool_with_http_handler(self) -> None:
        """Test that HTTP-based tool works through handler dispatch."""
        from gfp_mcp.server import create_server

        server = create_server()
        client = server._http_client  # noqa: SLF001

        # Mock the client.request method
        with patch.object(
            client,
            "request",
            new_callable=AsyncMock,
            return_value=["cell1", "cell2"],
        ):
            result = await self._call_tool(server, "list_cells", {})

            assert len(result) == 1
            assert isinstance(result[0], TextContent)
            response = json.loads(result[0].text)
            assert "cells" in response

    @pytest.mark.anyio
    async def test_call_tool_handler_error(self) -> None:
        """Test that handler errors are caught and returned as error response."""
        from gfp_mcp.server import create_server

        server = create_server()
        client = server._http_client  # noqa: SLF001

        # Mock the client.request to raise an exception
        with patch.object(
            client,
            "request",
            new_callable=AsyncMock,
            side_effect=Exception("Connection failed"),
        ):
            result = await self._call_tool(server, "list_cells", {})

            assert len(result) == 1
            assert isinstance(result[0], TextContent)
            response = json.loads(result[0].text)
            assert "error" in response
            assert "Connection failed" in response["error"]

    async def _call_tool(
        self, server: Any, name: str, arguments: dict[str, Any]
    ) -> list[TextContent | ImageContent]:
        """Helper to call a tool on the server.

        This directly invokes the handler dispatch logic.
        """
        from gfp_mcp.tools import get_handler

        handler = get_handler(name)
        if handler is None:
            return [
                TextContent(
                    type="text", text=json.dumps({"error": f"Unknown tool: {name}"})
                )
            ]

        client = server._http_client  # noqa: SLF001
        return await handler.handle(arguments, client)


class TestServerNoDeprecatedMethods:
    """Tests to verify deprecated methods are removed."""

    def test_no_log_server_discovery(self) -> None:
        """Verify _log_server_discovery is not in gfp_mcp.server."""
        from gfp_mcp import server

        assert not hasattr(server, "_log_server_discovery")

    def test_no_render_built_cells(self) -> None:
        """Verify _render_built_cells is not in gfp_mcp.server."""
        from gfp_mcp import server

        assert not hasattr(server, "_render_built_cells")

    def test_no_mappings_import(self) -> None:
        """Verify server doesn't import from mappings module."""
        import sys

        import gfp_mcp.server as server_module

        # Check that mappings is not imported in the server module
        assert "gfp_mcp.mappings" not in sys.modules
        # Also verify no direct mappings functions are used
        assert not hasattr(server_module, "get_mapping")
        assert not hasattr(server_module, "transform_request")
        assert not hasattr(server_module, "transform_response")


class TestServerHandlerDispatch:
    """Tests for handler-based tool dispatch."""

    def test_server_uses_get_handler(self) -> None:
        """Verify server uses get_handler for tool dispatch."""
        from gfp_mcp import server

        # The server module should import get_handler from tools
        assert hasattr(server, "get_handler") or "get_handler" in dir(server)

    @pytest.mark.anyio
    async def test_all_tools_have_handlers(self) -> None:
        """Verify all tools from get_all_tools have handlers."""
        from gfp_mcp.tools import get_all_tools, get_handler

        for tool in get_all_tools():
            handler = get_handler(tool.name)
            assert handler is not None, f"No handler for tool: {tool.name}"
            assert handler.name == tool.name


class TestServerIntegration:
    """Integration tests for the server with mocked handlers."""

    @pytest.mark.anyio
    async def test_build_cells_with_visualization(self) -> None:
        """Test build_cells tool includes visualization."""
        from gfp_mcp.server import create_server

        server = create_server()
        client = server._http_client  # noqa: SLF001

        # Mock successful build response
        with patch.object(
            client,
            "request",
            new_callable=AsyncMock,
            return_value={"status": "success", "cells": ["test_cell"]},
        ):
            # Also mock the render function since klayout may not be available
            with patch(
                "gfp_mcp.tools.build.render_built_cells",
                new_callable=AsyncMock,
                return_value=[],
            ):
                from gfp_mcp.tools import get_handler

                handler = get_handler("build_cells")
                assert handler is not None

                result = await handler.handle(
                    {"names": ["test_cell"], "visualize": ["test_cell"]}, client
                )

                assert len(result) >= 1
                assert isinstance(result[0], TextContent)

    @pytest.mark.anyio
    async def test_get_project_info_through_handler(self) -> None:
        """Test get_project_info tool works through handler."""
        from gfp_mcp.server import create_server

        server = create_server()
        client = server._http_client  # noqa: SLF001

        # Mock the HTTP request for /info endpoint
        mock_response = {
            "project_name": "test_project",
            "pdk": "test_pdk",
            "port": 8787,
        }
        with patch.object(
            client, "request", new_callable=AsyncMock, return_value=mock_response
        ):
            from gfp_mcp.tools import get_handler

            handler = get_handler("get_project_info")
            assert handler is not None

            result = await handler.handle({"project": "test_project"}, client)

            assert len(result) == 1
            assert isinstance(result[0], TextContent)
            response = json.loads(result[0].text)
            assert response["project_name"] == "test_project"

    @pytest.mark.anyio
    async def test_simulate_component_fallback(self) -> None:
        """Test simulate_component handles 404 fallback."""
        import httpx

        from gfp_mcp.server import create_server

        server = create_server()
        client = server._http_client  # noqa: SLF001

        # Create a mock 404 response for primary endpoint
        mock_404_response = MagicMock()
        mock_404_response.status_code = 404

        # Create success response for fallback
        fallback_response = {"sax_result": "simulation_data"}

        # Configure mock to raise 404 on first call, succeed on second
        call_count = 0

        async def mock_request(*args: Any, **kwargs: Any) -> dict[str, str]:
            nonlocal call_count
            call_count += 1
            if call_count == 1:
                raise httpx.HTTPStatusError(
                    "Not Found", request=MagicMock(), response=mock_404_response
                )
            return fallback_response

        with patch.object(client, "request", side_effect=mock_request):
            from gfp_mcp.tools import get_handler

            handler = get_handler("simulate_component")
            assert handler is not None

            result = await handler.handle({"name": "test_component"}, client)

            assert len(result) == 1
            # Should have tried both endpoints
            assert call_count == 2


class TestRunServer:
    """Tests for run_server function."""

    def test_main_exists(self) -> None:
        """Test that main entry point exists."""
        from gfp_mcp.server import main

        assert callable(main)

    def test_run_server_exists(self) -> None:
        """Test that run_server function exists."""
        from gfp_mcp.server import run_server

        assert callable(run_server)

    def test_module_exports(self) -> None:
        """Test that module exports expected functions."""
        from gfp_mcp import server

        assert hasattr(server, "create_server")
        assert hasattr(server, "run_server")
        assert hasattr(server, "main")


if __name__ == "__main__":
    pytest.main([__file__, "-v"])
