"""Tests for the SEP-2133 extensions capability negotiation plumbing.

The extension-map negotiation is independent of any concrete extension (Apps,
Tasks): the lowlevel `Server` advertises `self.extensions` under
`ServerCapabilities.extensions`, a client mirrors its own support under
`ClientCapabilities.extensions`, and `Connection.check_capability` resolves the
server-side query. These tests pin that plumbing end-to-end and at the unit
level. Per-extension contribution wiring lives in `test_extension.py`; this file
covers only the capability advertisement and negotiation.
"""

import mcp_types as types
import pytest
from inline_snapshot import snapshot

from mcp.client import advertise
from mcp.client.client import Client
from mcp.server import Server, ServerRequestContext
from mcp.server.extension import Extension
from mcp.server.mcpserver import MCPServer

pytestmark = pytest.mark.anyio

_EXTENSION_ID = "com.example/x"
_OTHER_EXTENSION_ID = "com.example/other"


class _Extension(Extension):
    identifier = _EXTENSION_ID

    def settings(self) -> dict[str, object]:
        return {"k": 1}


def test_get_capabilities_omits_extensions_when_none_registered() -> None:
    """SDK-defined: a lowlevel `Server` with an empty `extensions` map advertises
    `ServerCapabilities.extensions` as `None`, not an empty map."""
    server = Server("bare")
    assert server.get_capabilities().extensions is None


def test_get_capabilities_advertises_populated_self_extensions() -> None:
    """SDK-defined: `get_capabilities` reads `self.extensions` (the map higher
    layers populate) and advertises it under `ServerCapabilities.extensions`."""
    server = Server("with-ext")
    settings = {"k": 1}
    server.extensions = {_EXTENSION_ID: settings}
    assert server.get_capabilities().extensions == {_EXTENSION_ID: settings}


async def test_modern_connection_carries_the_advertised_extensions_map() -> None:
    """SDK-defined: over a modern (`server/discover`) connection the client reads
    the server's advertised extension map from `server_capabilities`."""
    server = MCPServer("host", extensions=[_Extension()])
    async with Client(server, mode="auto") as client:
        assert client.server_capabilities.extensions == snapshot({"com.example/x": {"k": 1}})


async def test_legacy_handshake_drops_the_extensions_map() -> None:
    """Pinned gap: the handshake-era `initialize` result is serialized against the
    2025 wire schema, which has no `extensions` field, so a legacy handshake cannot
    carry it; the client sees `None` even though the server advertised one."""
    server = MCPServer("host", extensions=[_Extension()])
    async with Client(server, mode="legacy") as client:
        assert client.server_capabilities.extensions is None


async def test_server_accepts_capability_for_client_advertised_extension() -> None:
    """SDK-defined: a client advertising `extensions={id: ...}` makes the
    server-side `check_client_capability` return True when queried for that id.
    Observed inside a tool handler."""
    queried = types.ClientCapabilities(extensions={_EXTENSION_ID: {}})
    supported: list[bool] = []

    async def call_tool(ctx: ServerRequestContext, params: types.CallToolRequestParams) -> types.CallToolResult:
        assert params.name == "probe"
        supported.append(ctx.session.check_client_capability(queried))
        return types.CallToolResult(content=[])

    async def list_tools(
        ctx: ServerRequestContext, params: types.PaginatedRequestParams | None
    ) -> types.ListToolsResult:
        return types.ListToolsResult(tools=[types.Tool(name="probe", input_schema={"type": "object"})])

    server = Server("checker", on_call_tool=call_tool, on_list_tools=list_tools)
    async with Client(server, extensions=[advertise(_EXTENSION_ID, {"mimeTypes": ["text/html"]})]) as client:
        await client.call_tool("probe", {})

    assert supported == [True]


async def test_server_rejects_capability_for_undeclared_extension() -> None:
    """SDK-defined: when the client advertises one extension, a server query for a
    *different* identifier returns False - presence, not value, is the check."""
    queried = types.ClientCapabilities(extensions={_OTHER_EXTENSION_ID: {}})
    supported: list[bool] = []

    async def call_tool(ctx: ServerRequestContext, params: types.CallToolRequestParams) -> types.CallToolResult:
        assert params.name == "probe"
        supported.append(ctx.session.check_client_capability(queried))
        return types.CallToolResult(content=[])

    async def list_tools(
        ctx: ServerRequestContext, params: types.PaginatedRequestParams | None
    ) -> types.ListToolsResult:
        return types.ListToolsResult(tools=[types.Tool(name="probe", input_schema={"type": "object"})])

    server = Server("checker", on_call_tool=call_tool, on_list_tools=list_tools)
    async with Client(server, extensions=[advertise(_EXTENSION_ID, {"mimeTypes": ["text/html"]})]) as client:
        await client.call_tool("probe", {})

    assert supported == [False]


async def test_server_rejects_capability_when_client_advertises_no_extensions() -> None:
    """SDK-defined: a client that declares no extensions makes any server
    `check_client_capability` query for an extension return False."""
    queried = types.ClientCapabilities(extensions={_EXTENSION_ID: {}})
    supported: list[bool] = []

    async def call_tool(ctx: ServerRequestContext, params: types.CallToolRequestParams) -> types.CallToolResult:
        assert params.name == "probe"
        supported.append(ctx.session.check_client_capability(queried))
        return types.CallToolResult(content=[])

    async def list_tools(
        ctx: ServerRequestContext, params: types.PaginatedRequestParams | None
    ) -> types.ListToolsResult:
        return types.ListToolsResult(tools=[types.Tool(name="probe", input_schema={"type": "object"})])

    server = Server("checker", on_call_tool=call_tool, on_list_tools=list_tools)
    async with Client(server) as client:
        await client.call_tool("probe", {})

    assert supported == [False]
