"""Tests for issue #1574: Python SDK incorrectly validates Resource URIs.

The Python SDK previously used Pydantic's AnyUrl for URI fields, which rejected
relative paths like 'users/me' that are valid according to the MCP spec and
accepted by the TypeScript SDK.

The fix changed URI fields to plain strings to match the spec, which defines
uri fields as strings with no JSON Schema format validation.

These tests verify the fix works end-to-end through the JSON-RPC protocol.
"""

import mcp_types as types
import pytest
from mcp_types import (
    ListResourcesResult,
    PaginatedRequestParams,
    ReadResourceRequestParams,
    ReadResourceResult,
    TextResourceContents,
)

from mcp import Client
from mcp.server import Server, ServerRequestContext

pytestmark = pytest.mark.anyio


async def test_relative_uri_roundtrip():
    """Relative URIs survive the full server-client JSON-RPC roundtrip.

    This is the critical regression test - if someone reintroduces AnyUrl,
    the server would fail to serialize resources with relative URIs,
    or the URI would be transformed during the roundtrip.
    """

    async def handle_list_resources(
        ctx: ServerRequestContext, params: PaginatedRequestParams | None
    ) -> ListResourcesResult:
        return ListResourcesResult(
            resources=[
                types.Resource(name="user", uri="users/me"),
                types.Resource(name="config", uri="./config"),
                types.Resource(name="parent", uri="../parent/resource"),
            ]
        )

    async def handle_read_resource(ctx: ServerRequestContext, params: ReadResourceRequestParams) -> ReadResourceResult:
        return ReadResourceResult(
            contents=[TextResourceContents(uri=str(params.uri), text=f"data for {params.uri}", mime_type="text/plain")]
        )

    server = Server("test", on_list_resources=handle_list_resources, on_read_resource=handle_read_resource)

    async with Client(server) as client:
        # List should return the exact URIs we specified
        resources = await client.list_resources()
        uri_map = {r.uri: r for r in resources.resources}

        assert "users/me" in uri_map, f"Expected 'users/me' in {list(uri_map.keys())}"
        assert "./config" in uri_map, f"Expected './config' in {list(uri_map.keys())}"
        assert "../parent/resource" in uri_map, f"Expected '../parent/resource' in {list(uri_map.keys())}"

        # Read should work with each relative URI and preserve it in the response
        for uri_str in ["users/me", "./config", "../parent/resource"]:
            result = await client.read_resource(uri_str)
            assert len(result.contents) == 1
            assert result.contents[0].uri == uri_str


async def test_custom_scheme_uri_roundtrip():
    """Custom scheme URIs work through the protocol.

    Some MCP servers use custom schemes like "custom://resource".
    These should work end-to-end.
    """

    async def handle_list_resources(
        ctx: ServerRequestContext, params: PaginatedRequestParams | None
    ) -> ListResourcesResult:
        return ListResourcesResult(
            resources=[
                types.Resource(name="custom", uri="custom://my-resource"),
                types.Resource(name="file", uri="file:///path/to/file"),
            ]
        )

    async def handle_read_resource(ctx: ServerRequestContext, params: ReadResourceRequestParams) -> ReadResourceResult:
        return ReadResourceResult(
            contents=[TextResourceContents(uri=str(params.uri), text="data", mime_type="text/plain")]
        )

    server = Server("test", on_list_resources=handle_list_resources, on_read_resource=handle_read_resource)

    async with Client(server) as client:
        resources = await client.list_resources()
        uri_map = {r.uri: r for r in resources.resources}

        assert "custom://my-resource" in uri_map
        assert "file:///path/to/file" in uri_map

        # Read with custom scheme
        result = await client.read_resource("custom://my-resource")
        assert len(result.contents) == 1


def test_uri_json_roundtrip_preserves_value():
    """URI is preserved exactly through JSON serialization.

    This catches any Pydantic validation or normalization that would
    alter the URI during the JSON-RPC message flow.
    """
    test_uris = [
        "users/me",
        "custom://resource",
        "./relative",
        "../parent",
        "file:///absolute/path",
        "https://example.com/path",
    ]

    for uri_str in test_uris:
        resource = types.Resource(name="test", uri=uri_str)
        json_data = resource.model_dump(mode="json")
        restored = types.Resource.model_validate(json_data)
        assert restored.uri == uri_str, f"URI mutated: {uri_str} -> {restored.uri}"


def test_resource_contents_uri_json_roundtrip():
    """TextResourceContents URI is preserved through JSON serialization."""
    test_uris = ["users/me", "./relative", "custom://resource"]

    for uri_str in test_uris:
        contents = types.TextResourceContents(
            uri=uri_str,
            text="data",
            mime_type="text/plain",
        )
        json_data = contents.model_dump(mode="json")
        restored = types.TextResourceContents.model_validate(json_data)
        assert restored.uri == uri_str, f"URI mutated: {uri_str} -> {restored.uri}"
