"""Integration tests for MCP server.

These tests require a running FastAPI server to fully test the integration.
Some tests use mocks to avoid this dependency.
"""

from __future__ import annotations

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

import pytest

from gfp_mcp.client import FastAPIClient
from gfp_mcp.config import MCPConfig
from gfp_mcp.registry import ServerInfo
from gfp_mcp.server import create_server


def test_create_server() -> None:
    """Test creating an MCP server instance."""
    server = create_server()
    assert server is not None
    assert server.name == "gdsfactoryplus"


def test_create_server_with_custom_url() -> None:
    """Test creating an MCP server with custom API URL."""
    custom_url = "http://localhost:9999"
    server = create_server(api_url=custom_url)
    assert server is not None


@pytest.mark.anyio
async def test_fastapi_client_lifecycle() -> None:
    """Test FastAPI client start and close."""
    client = FastAPIClient()
    assert client._client is None  # noqa: SLF001

    await client.start()
    assert client._client is not None  # noqa: SLF001

    await client.close()
    assert client._client is None  # noqa: SLF001


@pytest.mark.anyio
async def test_fastapi_client_context_manager() -> None:
    """Test FastAPI client as context manager."""
    async with FastAPIClient() as client:
        assert client._client is not None  # noqa: SLF001
    assert client._client is None  # noqa: SLF001


@pytest.mark.anyio
@patch("httpx.AsyncClient")
async def test_fastapi_client_get_request(mock_client_class: Any) -> None:
    """Test GET request with mocked httpx client."""
    mock_response = MagicMock()
    mock_response.json.return_value = {"result": "success"}
    mock_response.raise_for_status = MagicMock()

    mock_client = AsyncMock()
    mock_client.request = AsyncMock(return_value=mock_response)
    mock_client_class.return_value = mock_client

    client = FastAPIClient(base_url="http://localhost:9999")
    await client.start()
    result = await client.get("/api/test", params={"param": "value"})

    assert result == {"result": "success"}
    mock_client.request.assert_called_once()
    await client.close()


@pytest.mark.anyio
@patch("httpx.AsyncClient")
async def test_fastapi_client_post_request(mock_client_class: Any) -> None:
    """Test POST request with mocked httpx client."""
    mock_response = MagicMock()
    mock_response.json.return_value = {"status": "created"}
    mock_response.raise_for_status = MagicMock()

    mock_client = AsyncMock()
    mock_client.request = AsyncMock(return_value=mock_response)
    mock_client_class.return_value = mock_client

    client = FastAPIClient(base_url="http://localhost:9999")
    await client.start()
    result = await client.post("/api/test", json_data={"key": "value"})

    assert result == {"status": "created"}
    await client.close()


@pytest.mark.anyio
@patch("httpx.AsyncClient")
async def test_fastapi_client_retry_on_failure(mock_client_class: Any) -> None:
    """Test retry logic on request failure."""
    import httpx

    mock_response = MagicMock()
    mock_response.json.return_value = {"result": "success"}
    mock_response.raise_for_status = MagicMock()

    mock_client = AsyncMock()
    call_count = 0

    async def mock_request(*_args: Any, **_kwargs: Any) -> MagicMock:
        nonlocal call_count
        call_count += 1
        if call_count < 3:
            msg = "Connection failed"
            raise httpx.RequestError(msg, request=MagicMock())
        return mock_response

    mock_client.request = AsyncMock(side_effect=mock_request)
    mock_client_class.return_value = mock_client

    client = FastAPIClient(base_url="http://localhost:9999")
    await client.start()
    result = await client.get("/api/test")

    assert result == {"result": "success"}
    assert call_count == 3  # Should have retried twice
    await client.close()


def test_config_defaults() -> None:
    """Test configuration defaults."""
    assert MCPConfig.API_URL is None  # No default port
    assert MCPConfig.TIMEOUT == 300
    assert MCPConfig.MAX_RETRIES == 3


def test_config_get_api_url() -> None:
    """Test getting API URL from config."""
    url = MCPConfig.get_api_url()
    assert url == MCPConfig.API_URL

    override = "http://example.com:8080"
    url = MCPConfig.get_api_url(override)
    assert url == override


@pytest.mark.anyio
@patch("gfp_mcp.server.FastAPIClient")
async def test_server_list_tools(mock_client_class: Any) -> None:
    """Test listing tools through MCP server."""
    server = create_server()

    assert server is not None
    assert hasattr(server, "request_handlers")
    assert mock_client_class is not None  # Use the mock parameter


def test_request_no_base_url_no_servers() -> None:
    """Test request fails gracefully when no base URL and no servers."""
    with patch.dict(os.environ, {}, clear=True):
        client = FastAPIClient()
        client._registry = MagicMock()  # noqa: SLF001
        client._registry.list_servers.return_value = []

        with pytest.raises(
            ValueError, match="No project specified and no GDSFactory\\+ servers"
        ):
            client._resolve_base_url(project=None)  # noqa: SLF001


def test_request_auto_select_first_server() -> None:
    """Test request automatically uses first server when no project specified."""
    with patch.dict(os.environ, {}, clear=True):
        client = FastAPIClient()

        server_info = ServerInfo(
            port=8888,
            pid=12345,
            project_path="/path/to/project",
            project_name="test_project",
            pdk="sky130",
        )
        client._registry = MagicMock()  # noqa: SLF001
        client._registry.list_servers.return_value = [server_info]

        url = client._resolve_base_url(project=None)  # noqa: SLF001

        assert url == "http://localhost:8888"


def test_request_project_not_found_with_available_servers() -> None:
    """Test request fails with helpful message when project not found but servers exist."""
    with patch.dict(os.environ, {}, clear=True):
        client = FastAPIClient()

        server_info = ServerInfo(
            port=8888,
            pid=12345,
            project_path="/path/to/project",
            project_name="test_project",
            pdk="sky130",
        )
        client._registry = MagicMock()  # noqa: SLF001
        client._registry.list_servers.return_value = [server_info]
        client._registry.get_server_by_project.return_value = None  # noqa: SLF001

        with pytest.raises(ValueError, match="Project 'missing' not found in registry"):
            client._resolve_base_url(project="missing")  # noqa: SLF001


def test_request_project_not_found_no_servers() -> None:
    """Test request fails with helpful message when project not found and no servers."""
    with patch.dict(os.environ, {}, clear=True):
        client = FastAPIClient()

        client._registry = MagicMock()  # noqa: SLF001
        client._registry.list_servers.return_value = []
        client._registry.get_server_by_project.return_value = None  # noqa: SLF001

        with pytest.raises(ValueError, match="No GDSFactory\\+ servers are running"):
            client._resolve_base_url(project="missing")  # noqa: SLF001


@pytest.mark.anyio
async def test_simulate_component_fallback_on_404() -> None:
    """Test that simulate_component falls back to old API when new endpoint returns 404."""
    import httpx
    from mcp.types import CallToolRequest

    from gfp_mcp.server import create_server

    server = create_server()
    client = server._http_client  # type: ignore[attr-defined]

    call_count = 0
    calls_made: list[dict[str, Any]] = []

    async def mock_request(
        method: str,
        path: str,
        params: Any = None,
        json_data: Any = None,
        data: Any = None,
        project: str | None = None,
    ) -> dict[str, Any]:
        nonlocal call_count
        call_count += 1
        calls_made.append({"method": method, "path": path, "json_data": json_data})

        if "/api/sax/" in path:
            mock_response = MagicMock()
            mock_response.status_code = 404
            raise httpx.HTTPStatusError(
                "Not Found",
                request=MagicMock(),
                response=mock_response,
            )
        return {"s_params": {"o1@0,o2@0": [0.5, 0.5]}}

    client.request = AsyncMock(side_effect=mock_request)

    handler = server.request_handlers.get(CallToolRequest)
    assert handler is not None

    result = await handler(
        CallToolRequest(
            method="tools/call",
            params={"name": "simulate_component", "arguments": {"name": "mzi"}},
        )
    )

    assert call_count == 2
    assert calls_made[0]["path"] == "/api/sax/mzi/simulate"
    assert calls_made[1]["path"] == "/api/simulate"
    assert calls_made[1]["json_data"]["name"] == "mzi"
    assert result is not None


@pytest.mark.anyio
async def test_simulate_component_no_fallback_on_success() -> None:
    """Test that simulate_component does NOT fall back when new endpoint succeeds."""
    from mcp.types import CallToolRequest

    from gfp_mcp.server import create_server

    server = create_server()
    client = server._http_client  # type: ignore[attr-defined]

    call_count = 0
    calls_made: list[dict[str, Any]] = []

    async def mock_request(
        method: str,
        path: str,
        params: Any = None,
        json_data: Any = None,
        data: Any = None,
        project: str | None = None,
    ) -> dict[str, Any]:
        nonlocal call_count
        call_count += 1
        calls_made.append({"method": method, "path": path, "json_data": json_data})
        return {"s_params": {"o1@0,o2@0": [0.5, 0.5]}}

    client.request = AsyncMock(side_effect=mock_request)

    handler = server.request_handlers.get(CallToolRequest)
    assert handler is not None

    result = await handler(
        CallToolRequest(
            method="tools/call",
            params={"name": "simulate_component", "arguments": {"name": "mzi"}},
        )
    )

    assert call_count == 1
    assert calls_made[0]["path"] == "/api/sax/mzi/simulate"
    assert result is not None


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