"""`docs/servers/completions.md`: every claim the page makes, proved against the real SDK."""

import pytest
from inline_snapshot import snapshot
from mcp_types import (
    Completion,
    CompletionContext,
    CompletionsCapability,
    ErrorData,
    PromptReference,
    ResourceTemplateReference,
)

from docs_src.completions import tutorial001, tutorial002, tutorial003
from mcp import Client, MCPError

# See test_index.py for why this is a per-module mark and not a conftest hook.
pytestmark = [pytest.mark.anyio, pytest.mark.filterwarnings("error::mcp.MCPDeprecationWarning")]

TEMPLATE_REF = ResourceTemplateReference(uri="github://repos/{owner}/{repo}")
PROMPT_REF = PromptReference(name="review_code")


async def test_a_server_with_no_handler_has_no_completions_capability() -> None:
    """tutorial001: there is something worth completing, but no handler and no advertised capability."""
    async with Client(tutorial001.mcp) as client:
        (template,) = (await client.list_resource_templates()).resource_templates
        assert template.uri_template == "github://repos/{owner}/{repo}"
        (prompt,) = (await client.list_prompts()).prompts
        assert prompt.name == "review_code"
        assert client.server_capabilities.completions is None


async def test_completing_without_a_handler_is_method_not_found() -> None:
    """tutorial001: nothing handles `completion/complete`, so the request is a JSON-RPC error."""
    async with Client(tutorial001.mcp) as client:
        with pytest.raises(MCPError) as excinfo:
            await client.complete(ref=PROMPT_REF, argument={"name": "language", "value": "py"})
        assert excinfo.value.error == ErrorData(code=-32601, message="Method not found", data="completion/complete")


async def test_registering_the_handler_advertises_the_capability() -> None:
    """tutorial002: `@mcp.completion()` is the whole declaration; the capability is derived from it."""
    async with Client(tutorial002.mcp) as client:
        assert client.server_capabilities.completions == CompletionsCapability()


async def test_prompt_argument_completion_filters_on_the_typed_prefix() -> None:
    """tutorial002: the handler returns the languages that start with `argument.value`."""
    async with Client(tutorial002.mcp) as client:
        result = await client.complete(ref=PROMPT_REF, argument={"name": "language", "value": "py"})
        assert result.completion == snapshot(Completion(values=["python"]))


async def test_empty_value_returns_every_suggestion() -> None:
    """tutorial002: an empty prefix matches everything, so the client gets the whole list."""
    async with Client(tutorial002.mcp) as client:
        result = await client.complete(ref=PROMPT_REF, argument={"name": "language", "value": ""})
        assert result.completion.values == ["go", "javascript", "python", "rust", "typescript"]


async def test_returning_none_is_an_empty_list_not_an_error() -> None:
    """tutorial002: an argument the handler does not recognise produces `values=[]`, never a failure."""
    async with Client(tutorial002.mcp) as client:
        result = await client.complete(ref=PROMPT_REF, argument={"name": "code", "value": "x"})
        assert result.completion == snapshot(Completion(values=[]))
        result = await client.complete(ref=TEMPLATE_REF, argument={"name": "repo", "value": ""})
        assert result.completion.values == []


async def test_context_arguments_resolve_a_dependent_parameter() -> None:
    """tutorial003: the already-resolved `owner` arrives in `context.arguments` and picks the repo list."""
    async with Client(tutorial003.mcp) as client:
        result = await client.complete(
            ref=TEMPLATE_REF,
            argument={"name": "repo", "value": ""},
            context_arguments={"owner": "modelcontextprotocol"},
        )
        assert result.completion == snapshot(Completion(values=["python-sdk", "typescript-sdk", "inspector"]))


async def test_the_typed_prefix_still_filters_a_dependent_parameter() -> None:
    """tutorial003: `argument.value` narrows the owner's repos exactly as it narrows a prompt argument."""
    async with Client(tutorial003.mcp) as client:
        result = await client.complete(
            ref=TEMPLATE_REF,
            argument={"name": "repo", "value": "py"},
            context_arguments={"owner": "modelcontextprotocol"},
        )
        assert result.completion.values == ["python-sdk"]


def test_context_arguments_is_optional() -> None:
    """tutorial003: `context.arguments` is `dict[str, str] | None`; the handler's `None` guard is required."""
    assert CompletionContext.model_fields["arguments"].annotation == (dict[str, str] | None)
    assert CompletionContext().arguments is None


async def test_no_context_means_no_suggestions() -> None:
    """tutorial003: without a resolved `owner` (or with an unknown one) the handler has nothing to offer."""
    async with Client(tutorial003.mcp) as client:
        result = await client.complete(ref=TEMPLATE_REF, argument={"name": "repo", "value": ""})
        assert result.completion.values == []
        result = await client.complete(
            ref=TEMPLATE_REF,
            argument={"name": "repo", "value": ""},
            context_arguments={"owner": "nobody"},
        )
        assert result.completion.values == []


async def test_the_prompt_branch_is_untouched_by_the_new_one() -> None:
    """tutorial003: adding the resource-template branch leaves prompt-argument completion as it was."""
    async with Client(tutorial003.mcp) as client:
        result = await client.complete(ref=PROMPT_REF, argument={"name": "language", "value": "type"})
        assert result.completion.values == ["typescript"]
