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

import pytest
from inline_snapshot import snapshot
from mcp_types import Prompt, PromptArgument, PromptReference, TextContent, TextResourceContents, Tool

from docs_src.client import tutorial001, tutorial002, tutorial003, tutorial004, tutorial005, tutorial006, tutorial007
from mcp import Client, MCPDeprecationWarning, MCPError
from mcp.shared.metadata_utils import get_display_name

# 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")]


async def test_every_client_program_on_the_page_runs(capsys: pytest.CaptureFixture[str]) -> None:
    """Each `main()` is the literal client program shown on the page; all seven run clean in-memory."""
    await tutorial001.main()
    await tutorial002.main()
    await tutorial003.main()
    await tutorial004.main()
    await tutorial005.main()
    await tutorial006.main()
    await tutorial007.main()
    assert "Bookshop" in capsys.readouterr().out


async def test_connected_properties_are_populated_inside_the_block() -> None:
    """tutorial001: server_info, server_capabilities, protocol_version and instructions are just there."""
    async with Client(tutorial001.mcp) as client:
        assert client.server_info.name == "Bookshop"
        assert client.protocol_version == "2026-07-28"
        assert client.instructions == "Search the catalog before recommending a book."
        assert client.server_capabilities.tools is not None
        assert client.server_capabilities.logging is None


async def test_a_client_is_not_reusable_after_the_block_ends() -> None:
    """tutorial001: `async with` is the whole lifecycle. Construct a new Client per connection."""
    client = Client(tutorial001.mcp)
    async with client:
        assert client.server_info.name == "Bookshop"
    with pytest.raises(RuntimeError, match="cannot reenter"):
        await client.__aenter__()


async def test_list_tools_returns_the_full_definition() -> None:
    """tutorial002: each listed tool carries its name, title, description and the derived input schema."""
    async with Client(tutorial002.mcp) as client:
        (tool,) = (await client.list_tools()).tools
        assert tool.name == "search_books"
        assert tool.title == "Search the catalog"
        assert tool.description == "Search the catalog by title or author."
        assert tool.input_schema == snapshot(
            {
                "type": "object",
                "properties": {
                    "query": {"title": "Query", "type": "string"},
                    "limit": {"default": 10, "title": "Limit", "type": "integer"},
                },
                "required": ["query"],
                "title": "search_booksArguments",
            }
        )


def test_get_display_name_prefers_the_title() -> None:
    """The `!!! tip`: get_display_name returns the title when there is one and the name when there isn't."""
    titled = Tool(name="search_books", title="Search the catalog", input_schema={"type": "object"})
    untitled = Tool(name="search_books", input_schema={"type": "object"})
    assert get_display_name(titled) == "Search the catalog"
    assert get_display_name(untitled) == "search_books"


async def test_call_tool_result_has_three_things_to_read() -> None:
    """tutorial003: content for the model, structured_content for code, is_error for both."""
    async with Client(tutorial003.mcp) as client:
        result = await client.call_tool("lookup_book", {"title": "Dune"})
        assert not result.is_error
        (block,) = result.content
        assert isinstance(block, TextContent)
        assert block.text == '{\n  "title": "Dune",\n  "author": "Frank Herbert",\n  "year": 1965\n}'
        assert result.structured_content == {"title": "Dune", "author": "Frank Herbert", "year": 1965}


async def test_a_raising_tool_is_a_result_not_an_exception() -> None:
    """tutorial003 `!!! check`: the exception's message comes back in content with is_error=True."""
    async with Client(tutorial003.mcp) as client:
        result = await client.call_tool("lookup_book", {"title": "Solaris"})
        assert result.is_error
        (block,) = result.content
        assert isinstance(block, TextContent)
        assert block.text == "Error executing tool lookup_book: No book titled 'Solaris' in the catalog."
        assert result.structured_content is None


async def test_an_unknown_tool_name_is_a_result_not_an_exception() -> None:
    """The `!!! warning`: a tool the server doesn't have comes back as is_error=True, not as MCPError."""
    async with Client(tutorial003.mcp) as client:
        result = await client.call_tool("does_not_exist", {})
        assert result.is_error
        (block,) = result.content
        assert isinstance(block, TextContent)
        assert block.text == "Unknown tool: does_not_exist"
        assert result.structured_content is None


async def test_resources_and_templates_are_two_separate_lists() -> None:
    """tutorial004: concrete resources and parameterised templates come back from different verbs."""
    async with Client(tutorial004.mcp) as client:
        (resource,) = (await client.list_resources()).resources
        assert resource.uri == "catalog://genres"
        (template,) = (await client.list_resource_templates()).resource_templates
        assert template.uri_template == "catalog://genres/{genre}"


async def test_read_resource_fills_in_a_template() -> None:
    """tutorial004: read_resource takes a plain str URI; narrow the contents with isinstance."""
    async with Client(tutorial004.mcp) as client:
        (contents,) = (await client.read_resource("catalog://genres/poetry")).contents
        assert isinstance(contents, TextResourceContents)
        assert contents.text == "3 books filed under poetry."


async def test_resource_subscriptions_are_listen_based_on_the_modern_wire() -> None:
    """The Resources section: at 2026-07-28 `resources.subscribe` is True (served via
    subscriptions/listen) while the legacy subscribe_resource verb answers -32601."""
    async with Client(tutorial004.mcp) as client:
        assert client.server_capabilities.resources is not None
        assert client.server_capabilities.resources.subscribe is True
        with pytest.raises(MCPError) as exc_info:
            # The verb is itself deprecated; the modern wire also rejects it.
            with pytest.warns(MCPDeprecationWarning, match="use Client.listen"):
                await client.subscribe_resource("catalog://genres")  # pyright: ignore[reportDeprecated]
        assert exc_info.value.error.code == -32601
        assert exc_info.value.error.message == "Method not found"


async def test_list_prompts_describes_the_arguments() -> None:
    """tutorial005: a listed prompt carries its name, title and the arguments it needs."""
    async with Client(tutorial005.mcp) as client:
        (prompt,) = (await client.list_prompts()).prompts
        assert prompt == snapshot(
            Prompt(
                name="recommend",
                title="Recommend a book",
                description="Ask for a recommendation in a genre.",
                arguments=[PromptArgument(name="genre", required=True)],
            )
        )


async def test_get_prompt_renders_the_messages() -> None:
    """tutorial005: get_prompt returns the rendered messages a host hands to the model."""
    async with Client(tutorial005.mcp) as client:
        result = await client.get_prompt("recommend", {"genre": "poetry"})
        (message,) = result.messages
        assert message.role == "user"
        assert message.content == TextContent(
            type="text", text="Recommend one poetry book from the catalog and say why."
        )


async def test_complete_suggests_values_for_an_argument() -> None:
    """tutorial006: complete takes a ref and a name/value pair and returns the matching values."""
    async with Client(tutorial006.mcp) as client:
        result = await client.complete(
            ref=PromptReference(type="ref/prompt", name="recommend"),
            argument={"name": "genre", "value": "p"},
        )
        assert result.completion.values == ["poetry"]


async def test_a_single_page_server_ends_the_pagination_loop_immediately() -> None:
    """tutorial007: every list_* takes cursor=; next_cursor is None when there is nothing left."""
    async with Client(tutorial007.mcp) as client:
        page = await client.list_tools(cursor=None)
        assert page.next_cursor is None
        assert [tool.name for tool in page.tools] == ["search_books", "reserve_book"]


async def test_raise_exceptions_is_a_constructor_flag() -> None:
    """The `## In tests` section: `raise_exceptions=True` is accepted by the in-memory Client."""
    async with Client(tutorial001.mcp, raise_exceptions=True) as client:
        result = await client.call_tool("search_books", {"query": "dune"})
        assert result.structured_content == {"result": "Found 3 books matching 'dune'."}
