"""Tests for ServersResource."""

from __future__ import annotations

from typing import Any
from unittest.mock import AsyncMock
from uuid import UUID, uuid4

import pytest

from mcpgateway_sdk.models.server import Server
from mcpgateway_sdk.resources.servers import ServersResource


@pytest.fixture
def http() -> AsyncMock:
    """Mock HTTPClient."""
    return AsyncMock()


@pytest.fixture
def servers(http: AsyncMock) -> ServersResource:
    """ServersResource with mocked HTTP."""
    return ServersResource(http)


def _server_data(**overrides: Any) -> dict[str, Any]:
    data: dict[str, Any] = {
        "id": str(uuid4()),
        "name": "test-server",
        "display_name": "Test Server",
        "description": "A test MCP server",
        "icon_url": None,
        "type": "stdio",
        "status": "running",
        "config": {"command": "python", "args": ["-m", "server"]},
        "source": "manual",
        "source_doc_url": None,
        "credential_mode": "none",
        "exposure_mode": "all",
        "portal_visible": False,
        "portal_category": None,
        "portal_tags": [],
        "tool_count": 3,
        "last_synced_at": None,
        "last_error": None,
        "oauth_config": None,
        "created_at": "2026-01-01T00:00:00Z",
        "updated_at": "2026-01-01T00:00:00Z",
        "mcp_endpoint_url": None,
    }
    data.update(overrides)
    return data


# ---------------------------------------------------------------------------
# _endpoints dict
# ---------------------------------------------------------------------------


class TestEndpointsDict:
    """Tests for _endpoints class variable."""

    def test_endpoints_has_list(self) -> None:
        assert ServersResource._endpoints["list"] == ("GET", "/api/v1/servers")

    def test_endpoints_has_get(self) -> None:
        assert ServersResource._endpoints["get"] == (
            "GET",
            "/api/v1/servers/{server_id}",
        )

    def test_endpoints_has_create(self) -> None:
        assert ServersResource._endpoints["create"] == ("POST", "/api/v1/servers")

    def test_endpoints_has_update(self) -> None:
        assert ServersResource._endpoints["update"] == (
            "PUT",
            "/api/v1/servers/{server_id}",
        )

    def test_endpoints_has_delete(self) -> None:
        assert ServersResource._endpoints["delete"] == (
            "DELETE",
            "/api/v1/servers/{server_id}",
        )

    def test_endpoints_has_start(self) -> None:
        assert ServersResource._endpoints["start"] == (
            "POST",
            "/api/v1/servers/{server_id}/start",
        )

    def test_endpoints_has_stop(self) -> None:
        assert ServersResource._endpoints["stop"] == (
            "POST",
            "/api/v1/servers/{server_id}/stop",
        )

    def test_endpoints_has_health(self) -> None:
        assert ServersResource._endpoints["health"] == (
            "POST",
            "/api/v1/servers/{server_id}/health",
        )

    def test_endpoints_has_health_all(self) -> None:
        assert ServersResource._endpoints["health_all"] == (
            "POST",
            "/api/v1/servers/health/all",
        )

    def test_endpoints_has_bulk_action(self) -> None:
        assert ServersResource._endpoints["bulk_action"] == (
            "POST",
            "/api/v1/servers/bulk-action",
        )

    def test_endpoints_has_list_tools(self) -> None:
        assert ServersResource._endpoints["list_tools"] == (
            "GET",
            "/api/v1/servers/{server_id}/tools",
        )

    def test_endpoints_has_update_tool_overrides(self) -> None:
        assert ServersResource._endpoints["update_tool_overrides"] == (
            "PATCH",
            "/api/v1/servers/{server_id}/tools/{tool_id}/overrides",
        )

    def test_endpoints_has_reset_tool_overrides(self) -> None:
        assert ServersResource._endpoints["reset_tool_overrides"] == (
            "DELETE",
            "/api/v1/servers/{server_id}/tools/{tool_id}/overrides",
        )

    def test_endpoints_has_correct_tool_schema(self) -> None:
        assert ServersResource._endpoints["correct_tool_schema"] == (
            "POST",
            "/api/v1/servers/{server_id}/tools/{tool_id}/correct-schema",
        )

    def test_endpoints_has_get_tool_selection(self) -> None:
        assert ServersResource._endpoints["get_tool_selection"] == (
            "GET",
            "/api/v1/servers/{server_id}/tool-selection",
        )

    def test_endpoints_has_update_tool_selection(self) -> None:
        assert ServersResource._endpoints["update_tool_selection"] == (
            "PATCH",
            "/api/v1/servers/{server_id}/tool-selection",
        )

    def test_endpoints_has_sync_tools(self) -> None:
        assert ServersResource._endpoints["sync_tools"] == (
            "POST",
            "/api/v1/servers/{server_id}/sync",
        )

    def test_endpoints_has_create_tool(self) -> None:
        assert ServersResource._endpoints["create_tool"] == (
            "POST",
            "/api/v1/servers/{server_id}/tools",
        )

    def test_endpoints_has_update_tool(self) -> None:
        assert ServersResource._endpoints["update_tool"] == (
            "PATCH",
            "/api/v1/servers/{server_id}/tools/{tool_id}",
        )

    def test_endpoints_has_delete_tool(self) -> None:
        assert ServersResource._endpoints["delete_tool"] == (
            "DELETE",
            "/api/v1/servers/{server_id}/tools/{tool_id}",
        )

    def test_endpoints_has_list_credentials(self) -> None:
        assert ServersResource._endpoints["list_credentials"] == (
            "GET",
            "/api/v1/servers/{server_id}/credentials",
        )

    def test_endpoints_has_create_credential(self) -> None:
        assert ServersResource._endpoints["create_credential"] == (
            "POST",
            "/api/v1/servers/{server_id}/credentials",
        )

    def test_endpoints_has_delete_credential(self) -> None:
        assert ServersResource._endpoints["delete_credential"] == (
            "DELETE",
            "/api/v1/servers/{server_id}/credentials/{credential_id}",
        )

    def test_endpoints_has_get_oauth_config(self) -> None:
        assert ServersResource._endpoints["get_oauth_config"] == (
            "GET",
            "/api/v1/servers/{server_id}/oauth-config",
        )

    def test_endpoints_has_configure_oauth(self) -> None:
        assert ServersResource._endpoints["configure_oauth"] == (
            "POST",
            "/api/v1/servers/{server_id}/oauth-config",
        )

    def test_endpoints_has_delete_oauth_config(self) -> None:
        assert ServersResource._endpoints["delete_oauth_config"] == (
            "DELETE",
            "/api/v1/servers/{server_id}/oauth-config",
        )

    def test_endpoints_has_get_connection_options(self) -> None:
        assert ServersResource._endpoints["get_connection_options"] == (
            "GET",
            "/api/v1/servers/{server_id}/connection-options",
        )

    def test_endpoints_has_catalog_search(self) -> None:
        assert ServersResource._endpoints["catalog_search"] == (
            "GET",
            "/api/v1/catalog/search",
        )

    def test_endpoints_has_catalog_get(self) -> None:
        assert ServersResource._endpoints["catalog_get"] == (
            "GET",
            "/api/v1/catalog/{registry}/{registry_id}",
        )

    def test_endpoints_has_import_from_catalog(self) -> None:
        assert ServersResource._endpoints["import_from_catalog"] == (
            "POST",
            "/api/v1/servers/catalog-imports",
        )

    def test_endpoints_has_exactly_thirty_entries(self) -> None:
        assert len(ServersResource._endpoints) == 30


# ---------------------------------------------------------------------------
# CRUD: list
# ---------------------------------------------------------------------------


class TestList:
    """Tests for ServersResource.list."""

    async def test_sends_get_with_default_params(
        self, servers: ServersResource, http: AsyncMock
    ) -> None:
        # Arrange
        http.request.return_value = {"items": []}

        # Act
        await servers.list()

        # Assert
        http.request.assert_awaited_once_with(
            "GET",
            "/api/v1/servers",
            params={"page": 1, "page_size": 100},
        )

    async def test_sends_get_with_all_filters(
        self, servers: ServersResource, http: AsyncMock
    ) -> None:
        # Arrange
        http.request.return_value = {"items": []}

        # Act
        await servers.list(
            search="test",
            status="running",
            type="stdio",
            page=2,
            page_size=25,
        )

        # Assert
        http.request.assert_awaited_once_with(
            "GET",
            "/api/v1/servers",
            params={
                "search": "test",
                "status": "running",
                "type": "stdio",
                "page": 2,
                "page_size": 25,
            },
        )

    async def test_returns_list_of_servers(self, servers: ServersResource, http: AsyncMock) -> None:
        # Arrange
        http.request.return_value = {
            "items": [
                _server_data(name="server-a"),
                _server_data(name="server-b"),
            ]
        }

        # Act
        result = await servers.list()

        # Assert
        assert len(result) == 2
        assert all(isinstance(s, Server) for s in result)
        assert result[0].name == "server-a"
        assert result[1].name == "server-b"

    async def test_extracts_items_from_paginated_response(
        self, servers: ServersResource, http: AsyncMock
    ) -> None:
        # Arrange
        http.request.return_value = {
            "items": [_server_data()],
            "total": 50,
            "page": 1,
            "page_size": 20,
        }

        # Act
        result = await servers.list()

        # Assert
        assert len(result) == 1
        assert isinstance(result[0], Server)

    async def test_empty_list_returns_empty(
        self, servers: ServersResource, http: AsyncMock
    ) -> None:
        # Arrange
        http.request.return_value = {"items": []}

        # Act
        result = await servers.list()

        # Assert
        assert result == []


# ---------------------------------------------------------------------------
# CRUD: get
# ---------------------------------------------------------------------------


class TestGet:
    """Tests for ServersResource.get."""

    async def test_sends_get_with_server_id(
        self, servers: ServersResource, http: AsyncMock
    ) -> None:
        # Arrange
        server_id = uuid4()
        http.request.return_value = _server_data()

        # Act
        await servers.get(server_id)

        # Assert
        http.request.assert_awaited_once_with("GET", f"/api/v1/servers/{server_id}")

    async def test_accepts_string_id(self, servers: ServersResource, http: AsyncMock) -> None:
        # Arrange
        server_id = str(uuid4())
        http.request.return_value = _server_data()

        # Act
        await servers.get(server_id)

        # Assert
        http.request.assert_awaited_once_with("GET", f"/api/v1/servers/{server_id}")

    async def test_returns_server_model(self, servers: ServersResource, http: AsyncMock) -> None:
        # Arrange
        http.request.return_value = _server_data(name="my-server")

        # Act
        result = await servers.get(uuid4())

        # Assert
        assert isinstance(result, Server)
        assert result.name == "my-server"


# ---------------------------------------------------------------------------
# CRUD: create
# ---------------------------------------------------------------------------


class TestCreate:
    """Tests for ServersResource.create."""

    async def test_sends_post_with_fields(self, servers: ServersResource, http: AsyncMock) -> None:
        # Arrange
        http.request.return_value = _server_data()

        # Act
        await servers.create(name="new-server", type="stdio", config={"command": "python"})

        # Assert
        call_kwargs = http.request.call_args
        assert call_kwargs.args == ("POST", "/api/v1/servers")
        body = call_kwargs.kwargs["json"]
        assert body["name"] == "new-server"
        assert body["type"] == "stdio"
        assert body["config"] == {"command": "python"}

    async def test_returns_server_model(self, servers: ServersResource, http: AsyncMock) -> None:
        # Arrange
        http.request.return_value = _server_data(name="created-server")

        # Act
        result = await servers.create(name="created-server", type="stdio")

        # Assert
        assert isinstance(result, Server)
        assert result.name == "created-server"


# ---------------------------------------------------------------------------
# CRUD: update
# ---------------------------------------------------------------------------


class TestUpdate:
    """Tests for ServersResource.update."""

    async def test_sends_put_with_fields(self, servers: ServersResource, http: AsyncMock) -> None:
        # Arrange
        server_id = uuid4()
        http.request.return_value = _server_data()

        # Act
        await servers.update(server_id, name="updated-name", description="new desc")

        # Assert
        call_kwargs = http.request.call_args
        assert call_kwargs.args == ("PUT", f"/api/v1/servers/{server_id}")
        body = call_kwargs.kwargs["json"]
        assert body["name"] == "updated-name"
        assert body["description"] == "new desc"

    async def test_returns_server_model(self, servers: ServersResource, http: AsyncMock) -> None:
        # Arrange
        http.request.return_value = _server_data(name="updated-server")

        # Act
        result = await servers.update(uuid4(), name="updated-server")

        # Assert
        assert isinstance(result, Server)
        assert result.name == "updated-server"


# ---------------------------------------------------------------------------
# CRUD: delete
# ---------------------------------------------------------------------------


class TestDelete:
    """Tests for ServersResource.delete."""

    async def test_sends_delete_with_server_id(
        self, servers: ServersResource, http: AsyncMock
    ) -> None:
        # Arrange
        server_id = uuid4()
        http.request.return_value = None

        # Act
        await servers.delete(server_id)

        # Assert
        http.request.assert_awaited_once_with("DELETE", f"/api/v1/servers/{server_id}")

    async def test_returns_none(self, servers: ServersResource, http: AsyncMock) -> None:
        # Arrange
        http.request.return_value = None

        # Act
        result = await servers.delete(uuid4())

        # Assert
        assert result is None


# ---------------------------------------------------------------------------
# Lifecycle: start
# ---------------------------------------------------------------------------


class TestStart:
    """Tests for ServersResource.start."""

    async def test_sends_post_to_start_endpoint(
        self, servers: ServersResource, http: AsyncMock
    ) -> None:
        # Arrange
        server_id = uuid4()
        http.request.return_value = {"server_id": str(server_id), "current_status": "running"}

        # Act
        await servers.start(server_id)

        # Assert
        http.request.assert_awaited_once_with("POST", f"/api/v1/servers/{server_id}/start")

    async def test_returns_dict(self, servers: ServersResource, http: AsyncMock) -> None:
        # Arrange
        http.request.return_value = {
            "server_id": str(uuid4()),
            "server_name": "test-server",
            "current_status": "running",
            "tools_count": 3,
        }

        # Act
        result = await servers.start(uuid4())

        # Assert
        assert isinstance(result, dict)
        assert result["current_status"] == "running"
        assert result["tools_count"] == 3


# ---------------------------------------------------------------------------
# Lifecycle: stop
# ---------------------------------------------------------------------------


class TestStop:
    """Tests for ServersResource.stop."""

    async def test_sends_post_to_stop_endpoint(
        self, servers: ServersResource, http: AsyncMock
    ) -> None:
        # Arrange
        server_id = uuid4()
        http.request.return_value = {"server_id": str(server_id), "current_status": "stopped"}

        # Act
        await servers.stop(server_id)

        # Assert
        http.request.assert_awaited_once_with("POST", f"/api/v1/servers/{server_id}/stop")

    async def test_returns_dict(self, servers: ServersResource, http: AsyncMock) -> None:
        # Arrange
        http.request.return_value = {
            "server_id": str(uuid4()),
            "server_name": "test-server",
            "current_status": "stopped",
            "tools_count": 3,
        }

        # Act
        result = await servers.stop(uuid4())

        # Assert
        assert isinstance(result, dict)
        assert result["current_status"] == "stopped"
        assert result["tools_count"] == 3


# ---------------------------------------------------------------------------
# Lifecycle: health
# ---------------------------------------------------------------------------


class TestHealth:
    """Tests for ServersResource.health."""

    async def test_sends_post_to_health_endpoint(
        self, servers: ServersResource, http: AsyncMock
    ) -> None:
        # Arrange
        server_id = uuid4()
        http.request.return_value = {"status": "healthy"}

        # Act
        await servers.health(server_id)

        # Assert
        http.request.assert_awaited_once_with("POST", f"/api/v1/servers/{server_id}/health")

    async def test_returns_dict(self, servers: ServersResource, http: AsyncMock) -> None:
        # Arrange
        http.request.return_value = {"status": "healthy", "latency_ms": 12}

        # Act
        result = await servers.health(uuid4())

        # Assert
        assert isinstance(result, dict)
        assert result["status"] == "healthy"


# ---------------------------------------------------------------------------
# Lifecycle: health_all
# ---------------------------------------------------------------------------


class TestHealthAll:
    """Tests for ServersResource.health_all."""

    async def test_sends_post_to_health_all_endpoint(
        self, servers: ServersResource, http: AsyncMock
    ) -> None:
        # Arrange
        http.request.return_value = {"results": []}

        # Act
        await servers.health_all()

        # Assert
        http.request.assert_awaited_once_with("POST", "/api/v1/servers/health/all")

    async def test_returns_dict(self, servers: ServersResource, http: AsyncMock) -> None:
        # Arrange
        http.request.return_value = {"results": [{"server_id": str(uuid4()), "status": "healthy"}]}

        # Act
        result = await servers.health_all()

        # Assert
        assert isinstance(result, dict)
        assert "results" in result


# ---------------------------------------------------------------------------
# Lifecycle: bulk_action
# ---------------------------------------------------------------------------


class TestBulkAction:
    """Tests for ServersResource.bulk_action."""

    async def test_sends_post_with_action_and_server_ids(
        self, servers: ServersResource, http: AsyncMock
    ) -> None:
        # Arrange
        sid1 = uuid4()
        sid2 = uuid4()
        http.request.return_value = {"affected": 2}

        # Act
        await servers.bulk_action("start", [sid1, sid2])

        # Assert
        call_kwargs = http.request.call_args
        assert call_kwargs.args == ("POST", "/api/v1/servers/bulk-action")
        body = call_kwargs.kwargs["json"]
        assert body["action"] == "start"
        assert body["server_ids"] == [str(sid1), str(sid2)]

    async def test_converts_uuid_to_string(self, servers: ServersResource, http: AsyncMock) -> None:
        # Arrange
        sid = uuid4()
        http.request.return_value = {"affected": 1}

        # Act
        await servers.bulk_action("stop", [sid])

        # Assert
        body = http.request.call_args.kwargs["json"]
        assert body["server_ids"] == [str(sid)]

    async def test_accepts_string_ids(self, servers: ServersResource, http: AsyncMock) -> None:
        # Arrange
        sid = str(uuid4())
        http.request.return_value = {"affected": 1}

        # Act
        await servers.bulk_action("restart", [sid])

        # Assert
        body = http.request.call_args.kwargs["json"]
        assert body["server_ids"] == [sid]

    async def test_returns_dict(self, servers: ServersResource, http: AsyncMock) -> None:
        # Arrange
        http.request.return_value = {"affected": 3, "errors": []}

        # Act
        result = await servers.bulk_action("start", [uuid4(), uuid4(), uuid4()])

        # Assert
        assert isinstance(result, dict)
        assert result["affected"] == 3


# ---------------------------------------------------------------------------
# Tools on server: list_tools
# ---------------------------------------------------------------------------


class TestListTools:
    """Tests for ServersResource.list_tools."""

    async def test_sends_get_with_server_id(
        self, servers: ServersResource, http: AsyncMock
    ) -> None:
        # Arrange
        server_id = uuid4()
        http.request.return_value = {"items": []}

        # Act
        await servers.list_tools(server_id)

        # Assert
        http.request.assert_awaited_once_with("GET", f"/api/v1/servers/{server_id}/tools")

    async def test_returns_items_from_paginated(
        self, servers: ServersResource, http: AsyncMock
    ) -> None:
        # Arrange
        tools = [{"name": "tool-a"}, {"name": "tool-b"}]
        http.request.return_value = {"items": tools}

        # Act
        result = await servers.list_tools(uuid4())

        # Assert
        assert len(result) == 2
        assert result[0]["name"] == "tool-a"

    async def test_returns_raw_list_if_no_items_key(
        self, servers: ServersResource, http: AsyncMock
    ) -> None:
        # Arrange
        tools = [{"name": "tool-a"}]
        http.request.return_value = tools

        # Act
        result = await servers.list_tools(uuid4())

        # Assert
        assert len(result) == 1
        assert result[0]["name"] == "tool-a"


# ---------------------------------------------------------------------------
# Tools on server: update_tool_overrides
# ---------------------------------------------------------------------------


class TestUpdateToolOverrides:
    """Tests for ServersResource.update_tool_overrides."""

    async def test_sends_patch_with_overrides(
        self, servers: ServersResource, http: AsyncMock
    ) -> None:
        # Arrange
        server_id = uuid4()
        tool_id = uuid4()
        overrides = {"description": "Custom desc", "enabled": False}
        http.request.return_value = overrides

        # Act
        await servers.update_tool_overrides(server_id, tool_id, overrides)

        # Assert
        http.request.assert_awaited_once_with(
            "PATCH",
            f"/api/v1/servers/{server_id}/tools/{tool_id}/overrides",
            json=overrides,
        )

    async def test_returns_dict(self, servers: ServersResource, http: AsyncMock) -> None:
        # Arrange
        http.request.return_value = {"description": "Custom"}

        # Act
        result = await servers.update_tool_overrides(uuid4(), uuid4(), {"description": "Custom"})

        # Assert
        assert isinstance(result, dict)


# ---------------------------------------------------------------------------
# Tools on server: reset_tool_overrides
# ---------------------------------------------------------------------------


class TestResetToolOverrides:
    """Tests for ServersResource.reset_tool_overrides."""

    async def test_sends_delete(self, servers: ServersResource, http: AsyncMock) -> None:
        # Arrange
        server_id = uuid4()
        tool_id = uuid4()
        http.request.return_value = None

        # Act
        await servers.reset_tool_overrides(server_id, tool_id)

        # Assert
        http.request.assert_awaited_once_with(
            "DELETE",
            f"/api/v1/servers/{server_id}/tools/{tool_id}/overrides",
        )

    async def test_returns_none(self, servers: ServersResource, http: AsyncMock) -> None:
        # Arrange
        http.request.return_value = None

        # Act
        result = await servers.reset_tool_overrides(uuid4(), uuid4())

        # Assert
        assert result is None


# ---------------------------------------------------------------------------
# Tools on server: correct_tool_schema
# ---------------------------------------------------------------------------


class TestCorrectToolSchema:
    """Tests for ServersResource.correct_tool_schema."""

    async def test_sends_post(self, servers: ServersResource, http: AsyncMock) -> None:
        # Arrange
        server_id = uuid4()
        tool_id = uuid4()
        http.request.return_value = {"corrected": True}

        # Act
        await servers.correct_tool_schema(server_id, tool_id)

        # Assert
        http.request.assert_awaited_once_with(
            "POST",
            f"/api/v1/servers/{server_id}/tools/{tool_id}/correct-schema",
        )

    async def test_returns_dict(self, servers: ServersResource, http: AsyncMock) -> None:
        # Arrange
        http.request.return_value = {"corrected": True, "changes": []}

        # Act
        result = await servers.correct_tool_schema(uuid4(), uuid4())

        # Assert
        assert isinstance(result, dict)
        assert result["corrected"] is True


# ---------------------------------------------------------------------------
# Tools on server: get_tool_selection
# ---------------------------------------------------------------------------


class TestGetToolSelection:
    """Tests for ServersResource.get_tool_selection."""

    async def test_sends_get(self, servers: ServersResource, http: AsyncMock) -> None:
        # Arrange
        server_id = uuid4()
        http.request.return_value = {"mode": "all", "tools": []}

        # Act
        await servers.get_tool_selection(server_id)

        # Assert
        http.request.assert_awaited_once_with("GET", f"/api/v1/servers/{server_id}/tool-selection")

    async def test_returns_dict(self, servers: ServersResource, http: AsyncMock) -> None:
        # Arrange
        http.request.return_value = {"mode": "selected", "tools": ["tool-a"]}

        # Act
        result = await servers.get_tool_selection(uuid4())

        # Assert
        assert isinstance(result, dict)
        assert result["mode"] == "selected"


# ---------------------------------------------------------------------------
# Tools on server: update_tool_selection
# ---------------------------------------------------------------------------


class TestUpdateToolSelection:
    """Tests for ServersResource.update_tool_selection."""

    async def test_sends_patch_with_tools(self, servers: ServersResource, http: AsyncMock) -> None:
        # Arrange
        server_id = uuid4()
        tools = {"mode": "selected", "tool_ids": ["tool-a", "tool-b"]}
        http.request.return_value = tools

        # Act
        await servers.update_tool_selection(server_id, tools)

        # Assert
        http.request.assert_awaited_once_with(
            "PATCH",
            f"/api/v1/servers/{server_id}/tool-selection",
            json=tools,
        )

    async def test_returns_dict(self, servers: ServersResource, http: AsyncMock) -> None:
        # Arrange
        http.request.return_value = {"mode": "all"}

        # Act
        result = await servers.update_tool_selection(uuid4(), {"mode": "all"})

        # Assert
        assert isinstance(result, dict)


# ---------------------------------------------------------------------------
# Tools on server: sync_tools
# ---------------------------------------------------------------------------


class TestSyncTools:
    """Tests for ServersResource.sync_tools."""

    async def test_sends_post(self, servers: ServersResource, http: AsyncMock) -> None:
        # Arrange
        server_id = uuid4()
        http.request.return_value = {"synced": 5}

        # Act
        await servers.sync_tools(server_id)

        # Assert
        http.request.assert_awaited_once_with("POST", f"/api/v1/servers/{server_id}/sync")

    async def test_returns_dict(self, servers: ServersResource, http: AsyncMock) -> None:
        # Arrange
        http.request.return_value = {"synced": 5, "added": 2, "removed": 1}

        # Act
        result = await servers.sync_tools(uuid4())

        # Assert
        assert isinstance(result, dict)
        assert result["synced"] == 5


# ---------------------------------------------------------------------------
# Local tools: create_tool
# ---------------------------------------------------------------------------


class TestCreateTool:
    """Tests for ServersResource.create_tool."""

    async def test_sends_post_with_fields(self, servers: ServersResource, http: AsyncMock) -> None:
        # Arrange
        server_id = uuid4()
        http.request.return_value = {"id": str(uuid4()), "name": "my-tool"}

        # Act
        await servers.create_tool(server_id, name="my-tool", description="A tool")

        # Assert
        call_kwargs = http.request.call_args
        assert call_kwargs.args == ("POST", f"/api/v1/servers/{server_id}/tools")
        body = call_kwargs.kwargs["json"]
        assert body["name"] == "my-tool"
        assert body["description"] == "A tool"

    async def test_returns_dict(self, servers: ServersResource, http: AsyncMock) -> None:
        # Arrange
        http.request.return_value = {"id": str(uuid4()), "name": "tool"}

        # Act
        result = await servers.create_tool(uuid4(), name="tool")

        # Assert
        assert isinstance(result, dict)


# ---------------------------------------------------------------------------
# Local tools: update_tool
# ---------------------------------------------------------------------------


class TestUpdateTool:
    """Tests for ServersResource.update_tool."""

    async def test_sends_patch_with_fields(self, servers: ServersResource, http: AsyncMock) -> None:
        # Arrange
        server_id = uuid4()
        tool_id = uuid4()
        http.request.return_value = {"id": str(tool_id), "name": "updated-tool"}

        # Act
        await servers.update_tool(server_id, tool_id, name="updated-tool")

        # Assert
        call_kwargs = http.request.call_args
        assert call_kwargs.args == (
            "PATCH",
            f"/api/v1/servers/{server_id}/tools/{tool_id}",
        )
        body = call_kwargs.kwargs["json"]
        assert body["name"] == "updated-tool"

    async def test_returns_dict(self, servers: ServersResource, http: AsyncMock) -> None:
        # Arrange
        http.request.return_value = {"id": str(uuid4()), "name": "tool"}

        # Act
        result = await servers.update_tool(uuid4(), uuid4(), description="new desc")

        # Assert
        assert isinstance(result, dict)


# ---------------------------------------------------------------------------
# Local tools: delete_tool
# ---------------------------------------------------------------------------


class TestDeleteTool:
    """Tests for ServersResource.delete_tool."""

    async def test_sends_delete(self, servers: ServersResource, http: AsyncMock) -> None:
        # Arrange
        server_id = uuid4()
        tool_id = uuid4()
        http.request.return_value = None

        # Act
        await servers.delete_tool(server_id, tool_id)

        # Assert
        http.request.assert_awaited_once_with(
            "DELETE",
            f"/api/v1/servers/{server_id}/tools/{tool_id}",
        )

    async def test_returns_none(self, servers: ServersResource, http: AsyncMock) -> None:
        # Arrange
        http.request.return_value = None

        # Act
        result = await servers.delete_tool(uuid4(), uuid4())

        # Assert
        assert result is None


# ---------------------------------------------------------------------------
# Credentials: list_credentials
# ---------------------------------------------------------------------------


class TestListCredentials:
    """Tests for ServersResource.list_credentials."""

    async def test_sends_get(self, servers: ServersResource, http: AsyncMock) -> None:
        # Arrange
        server_id = uuid4()
        http.request.return_value = {"items": []}

        # Act
        await servers.list_credentials(server_id)

        # Assert
        http.request.assert_awaited_once_with("GET", f"/api/v1/servers/{server_id}/credentials")

    async def test_extracts_items_from_dict(
        self, servers: ServersResource, http: AsyncMock
    ) -> None:
        # Arrange
        creds = [{"id": str(uuid4()), "name": "api-key"}]
        http.request.return_value = {"items": creds}

        # Act
        result = await servers.list_credentials(uuid4())

        # Assert
        assert isinstance(result, list)
        assert result[0]["name"] == "api-key"

    async def test_extracts_credentials_key_from_dict(
        self, servers: ServersResource, http: AsyncMock
    ) -> None:
        # Arrange
        creds = [{"id": str(uuid4()), "name": "token"}]
        http.request.return_value = {"credentials": creds}

        # Act
        result = await servers.list_credentials(uuid4())

        # Assert
        assert isinstance(result, list)
        assert result[0]["name"] == "token"

    async def test_returns_raw_list(self, servers: ServersResource, http: AsyncMock) -> None:
        # Arrange
        creds = [{"id": str(uuid4()), "name": "api-key"}]
        http.request.return_value = creds

        # Act
        result = await servers.list_credentials(uuid4())

        # Assert
        assert isinstance(result, list)
        assert result[0]["name"] == "api-key"


# ---------------------------------------------------------------------------
# Credentials: create_credential
# ---------------------------------------------------------------------------


class TestCreateCredential:
    """Tests for ServersResource.create_credential."""

    async def test_sends_post_with_fields(self, servers: ServersResource, http: AsyncMock) -> None:
        # Arrange
        server_id = uuid4()
        http.request.return_value = {"id": str(uuid4()), "name": "api-key"}

        # Act
        await servers.create_credential(server_id, name="api-key", value="secret")

        # Assert
        call_kwargs = http.request.call_args
        assert call_kwargs.args == (
            "POST",
            f"/api/v1/servers/{server_id}/credentials",
        )
        body = call_kwargs.kwargs["json"]
        assert body["name"] == "api-key"
        assert body["value"] == "secret"

    async def test_returns_dict(self, servers: ServersResource, http: AsyncMock) -> None:
        # Arrange
        http.request.return_value = {"id": str(uuid4()), "name": "cred"}

        # Act
        result = await servers.create_credential(uuid4(), name="cred")

        # Assert
        assert isinstance(result, dict)


# ---------------------------------------------------------------------------
# Credentials: delete_credential
# ---------------------------------------------------------------------------


class TestDeleteCredential:
    """Tests for ServersResource.delete_credential."""

    async def test_sends_delete(self, servers: ServersResource, http: AsyncMock) -> None:
        # Arrange
        server_id = uuid4()
        credential_id = uuid4()
        http.request.return_value = None

        # Act
        await servers.delete_credential(server_id, credential_id)

        # Assert
        http.request.assert_awaited_once_with(
            "DELETE",
            f"/api/v1/servers/{server_id}/credentials/{credential_id}",
        )

    async def test_returns_none(self, servers: ServersResource, http: AsyncMock) -> None:
        # Arrange
        http.request.return_value = None

        # Act
        result = await servers.delete_credential(uuid4(), uuid4())

        # Assert
        assert result is None


# ---------------------------------------------------------------------------
# OAuth config: get_oauth_config
# ---------------------------------------------------------------------------


class TestGetOauthConfig:
    """Tests for ServersResource.get_oauth_config."""

    async def test_sends_get(self, servers: ServersResource, http: AsyncMock) -> None:
        # Arrange
        server_id = uuid4()
        http.request.return_value = {"client_id": "abc"}

        # Act
        await servers.get_oauth_config(server_id)

        # Assert
        http.request.assert_awaited_once_with("GET", f"/api/v1/servers/{server_id}/oauth-config")

    async def test_returns_dict(self, servers: ServersResource, http: AsyncMock) -> None:
        # Arrange
        http.request.return_value = {
            "client_id": "abc",
            "auth_url": "https://auth.example.com",
        }

        # Act
        result = await servers.get_oauth_config(uuid4())

        # Assert
        assert isinstance(result, dict)
        assert result["client_id"] == "abc"


# ---------------------------------------------------------------------------
# OAuth config: configure_oauth
# ---------------------------------------------------------------------------


class TestConfigureOauth:
    """Tests for ServersResource.configure_oauth."""

    async def test_sends_post_with_config(self, servers: ServersResource, http: AsyncMock) -> None:
        # Arrange
        server_id = uuid4()
        config_data = {"client_id": "abc", "client_secret": "xyz"}
        http.request.return_value = config_data

        # Act
        await servers.configure_oauth(server_id, client_id="abc", client_secret="xyz")

        # Assert
        call_kwargs = http.request.call_args
        assert call_kwargs.args == (
            "POST",
            f"/api/v1/servers/{server_id}/oauth-config",
        )
        body = call_kwargs.kwargs["json"]
        assert body["client_id"] == "abc"
        assert body["client_secret"] == "xyz"

    async def test_returns_dict(self, servers: ServersResource, http: AsyncMock) -> None:
        # Arrange
        http.request.return_value = {"client_id": "abc"}

        # Act
        result = await servers.configure_oauth(uuid4(), client_id="abc")

        # Assert
        assert isinstance(result, dict)


# ---------------------------------------------------------------------------
# OAuth config: delete_oauth_config
# ---------------------------------------------------------------------------


class TestDeleteOauthConfig:
    """Tests for ServersResource.delete_oauth_config."""

    async def test_sends_delete(self, servers: ServersResource, http: AsyncMock) -> None:
        # Arrange
        server_id = uuid4()
        http.request.return_value = None

        # Act
        await servers.delete_oauth_config(server_id)

        # Assert
        http.request.assert_awaited_once_with("DELETE", f"/api/v1/servers/{server_id}/oauth-config")

    async def test_returns_none(self, servers: ServersResource, http: AsyncMock) -> None:
        # Arrange
        http.request.return_value = None

        # Act
        result = await servers.delete_oauth_config(uuid4())

        # Assert
        assert result is None


# ---------------------------------------------------------------------------
# Connection: get_connection_options
# ---------------------------------------------------------------------------


class TestGetConnectionOptions:
    """Tests for ServersResource.get_connection_options."""

    async def test_sends_get(self, servers: ServersResource, http: AsyncMock) -> None:
        # Arrange
        server_id = uuid4()
        http.request.return_value = {"sse_url": "https://example.com/sse"}

        # Act
        await servers.get_connection_options(server_id)

        # Assert
        http.request.assert_awaited_once_with(
            "GET", f"/api/v1/servers/{server_id}/connection-options"
        )

    async def test_returns_dict(self, servers: ServersResource, http: AsyncMock) -> None:
        # Arrange
        http.request.return_value = {
            "sse_url": "https://example.com/sse",
            "streamable_http_url": "https://example.com/http",
        }

        # Act
        result = await servers.get_connection_options(uuid4())

        # Assert
        assert isinstance(result, dict)
        assert "sse_url" in result


# ---------------------------------------------------------------------------
# Catalog: catalog_search
# ---------------------------------------------------------------------------


class TestCatalogSearch:
    """Tests for ServersResource.catalog_search."""

    async def test_sends_get_without_query(self, servers: ServersResource, http: AsyncMock) -> None:
        # Arrange
        http.request.return_value = []

        # Act
        await servers.catalog_search()

        # Assert
        http.request.assert_awaited_once_with("GET", "/api/v1/catalog/search", params={})

    async def test_sends_get_with_query(self, servers: ServersResource, http: AsyncMock) -> None:
        # Arrange
        http.request.return_value = []

        # Act
        await servers.catalog_search(query="github")

        # Assert
        http.request.assert_awaited_once_with(
            "GET", "/api/v1/catalog/search", params={"search": "github"}
        )

    async def test_returns_list_from_items(self, servers: ServersResource, http: AsyncMock) -> None:
        # Arrange
        entries = [{"name": "github-server"}, {"name": "slack-server"}]
        http.request.return_value = {"items": entries}

        # Act
        result = await servers.catalog_search()

        # Assert
        assert len(result) == 2
        assert result[0]["name"] == "github-server"

    async def test_returns_raw_list_if_no_items_key(
        self, servers: ServersResource, http: AsyncMock
    ) -> None:
        # Arrange
        entries = [{"name": "entry-a"}]
        http.request.return_value = entries

        # Act
        result = await servers.catalog_search()

        # Assert
        assert len(result) == 1
        assert result[0]["name"] == "entry-a"


# ---------------------------------------------------------------------------
# Catalog: catalog_get
# ---------------------------------------------------------------------------


class TestCatalogGet:
    """Tests for ServersResource.catalog_get."""

    async def test_sends_get_with_registry_and_id(
        self, servers: ServersResource, http: AsyncMock
    ) -> None:
        # Arrange
        http.request.return_value = {"name": "github-server"}

        # Act
        await servers.catalog_get("npm", "github-mcp-server")

        # Assert
        http.request.assert_awaited_once_with("GET", "/api/v1/catalog/npm/github-mcp-server")

    async def test_returns_dict(self, servers: ServersResource, http: AsyncMock) -> None:
        # Arrange
        http.request.return_value = {
            "name": "github-server",
            "registry": "npm",
            "install_command": "npx @github/mcp-server",
        }

        # Act
        result = await servers.catalog_get("npm", "github-mcp-server")

        # Assert
        assert isinstance(result, dict)
        assert result["name"] == "github-server"


# ---------------------------------------------------------------------------
# Catalog: import_from_catalog
# ---------------------------------------------------------------------------


class TestImportFromCatalog:
    """Tests for ServersResource.import_from_catalog."""

    async def test_sends_post_with_registry_fields(
        self, servers: ServersResource, http: AsyncMock
    ) -> None:
        # Arrange
        http.request.return_value = _server_data()

        # Act
        await servers.import_from_catalog("npm", "@anthropic/time-mcp", "Time Server")

        # Assert
        call_kwargs = http.request.call_args
        assert call_kwargs.args == ("POST", "/api/v1/servers/catalog-imports")
        body = call_kwargs.kwargs["json"]
        assert body["registry"] == "npm"
        assert body["registry_id"] == "@anthropic/time-mcp"
        assert body["name"] == "Time Server"

    async def test_sends_additional_kwargs(self, servers: ServersResource, http: AsyncMock) -> None:
        # Arrange
        http.request.return_value = _server_data()

        # Act
        await servers.import_from_catalog("npm", "@test/mcp", "Test", auto_start=True)

        # Assert
        body = http.request.call_args.kwargs["json"]
        assert body["registry"] == "npm"
        assert body["registry_id"] == "@test/mcp"
        assert body["name"] == "Test"
        assert body["auto_start"] is True

    async def test_returns_server_model(self, servers: ServersResource, http: AsyncMock) -> None:
        # Arrange
        http.request.return_value = _server_data(name="imported-server")

        # Act
        result = await servers.import_from_catalog("npm", "@test/mcp", "Imported")

        # Assert
        assert isinstance(result, Server)
        assert result.name == "imported-server"
