"""Tests for CapabilityToken transmission to MCP servers.

Spec source: docs/INTERFACES.md Section H.5 (Token Transport)
The token is presented to tela at MCP connection time via clientInfo.capability_token.
"""

from datetime import datetime
from pathlib import Path
from typing import Any, cast

import pytest

from anima.core.schemas import CapabilityToken
from anima.shell.mcp_servers import parse_mcp_server, parse_mcp_servers


def test_parse_mcp_server_http_without_token():
    """HTTP URL without token creates MCPServerSSE without client_info."""
    from pydantic_ai.mcp import MCPServerSSE

    server = parse_mcp_server("https://api.example.com/mcp")

    assert isinstance(server, MCPServerSSE)
    assert server.url == "https://api.example.com/mcp"
    # client_info should be None when no token provided
    assert server.client_info is None


def test_parse_mcp_server_stdio_without_token():
    """Command without token creates MCPServerStdio without client_info."""
    from pydantic_ai.mcp import MCPServerStdio

    server = parse_mcp_server("npx @modelcontextprotocol/server-filesystem /tmp")

    assert isinstance(server, MCPServerStdio)
    assert server.command == "npx"
    assert server.args == ["@modelcontextprotocol/server-filesystem", "/tmp"]
    # client_info should be None when no token provided
    assert server.client_info is None


@pytest.mark.asyncio
async def test_parse_mcp_server_shell_process_tool_call_normalizes_relative_directory():
    """Shell preset normalizes shell_execute.directory to absolute path."""
    server = parse_mcp_server("uvx mcp-shell-server")
    assert server.process_tool_call is not None

    captured: dict[str, Any] = {}

    async def fake_call_tool(
        name: str, args: dict[str, Any], meta: Any = None
    ) -> dict[str, Any]:
        _ = meta
        captured["name"] = name
        captured["args"] = args
        return {"ok": True}

    await server.process_tool_call(  # type: ignore[misc]
        cast(Any, None),
        fake_call_tool,
        "shell_execute",
        {"command": ["pwd"], "directory": "."},
    )

    assert captured["name"] == "shell_execute"
    assert captured["args"]["directory"] == str(Path.cwd().resolve())


@pytest.mark.asyncio
async def test_parse_mcp_server_shell_process_tool_call_sets_missing_directory_to_cwd():
    """Shell preset fills missing shell_execute.directory with absolute cwd."""
    server = parse_mcp_server("uvx mcp-shell-server")
    assert server.process_tool_call is not None

    captured: dict[str, Any] = {}

    async def fake_call_tool(
        name: str, args: dict[str, Any], meta: Any = None
    ) -> dict[str, Any]:
        _ = meta
        captured["name"] = name
        captured["args"] = args
        return {"ok": True}

    await server.process_tool_call(  # type: ignore[misc]
        cast(Any, None),
        fake_call_tool,
        "shell_execute",
        {"command": ["pwd"]},
    )

    assert captured["name"] == "shell_execute"
    assert captured["args"]["directory"] == str(Path.cwd().resolve())


def test_parse_mcp_server_non_shell_does_not_install_process_tool_call():
    """Non-shell MCP servers keep default process_tool_call behavior."""
    server = parse_mcp_server("npx @modelcontextprotocol/server-filesystem /tmp")
    assert server.process_tool_call is None


def test_parse_mcp_server_http_with_token():
    """HTTP URL with token creates MCPServerSSE with client_info containing token.

    Spec source: docs/INTERFACES.md H.5 - token passed via clientInfo.capability_token
    """
    from pydantic_ai.mcp import MCPServerSSE

    token = CapabilityToken(
        token_id="tok_test123",
        tools_profile="default",
        issued_at=datetime(2026, 3, 1, 0, 0, 0),
        expires_at=datetime(2026, 3, 1, 1, 0, 0),
        signature="sig_test",
    )

    server = parse_mcp_server("https://api.example.com/mcp", capability_token=token)

    assert isinstance(server, MCPServerSSE)
    assert server.url == "https://api.example.com/mcp"

    # Verify client_info is set with capability_token
    assert server.client_info is not None
    assert server.client_info.name == "anima"
    assert server.client_info.version == "1.0.0"

    # Implementation has additionalProperties: true, so capability_token is embedded
    # We need to check the model_dump() to see extra fields
    client_info_dict = server.client_info.model_dump()
    assert "capability_token" in client_info_dict
    capability_token_data = client_info_dict["capability_token"]
    assert capability_token_data["token_id"] == "tok_test123"
    assert capability_token_data["tools_profile"] == "default"
    assert capability_token_data["signature"] == "sig_test"


def test_parse_mcp_server_stdio_with_token():
    """Command with token creates MCPServerStdio with client_info containing token.

    Spec source: docs/INTERFACES.md H.5 - token passed via clientInfo.capability_token
    """
    from pydantic_ai.mcp import MCPServerStdio

    token = CapabilityToken(
        token_id="tok_stdio_test",
        tools_profile="full",
        persona_ref="worker@v1",
        issued_at=datetime(2026, 3, 4, 12, 0, 0),
        expires_at=datetime(2026, 3, 4, 13, 0, 0),
        signature="sig_stdio",
    )

    server = parse_mcp_server(
        "npx @modelcontextprotocol/server-filesystem /tmp", capability_token=token
    )

    assert isinstance(server, MCPServerStdio)
    assert server.command == "npx"
    assert server.args == ["@modelcontextprotocol/server-filesystem", "/tmp"]

    # Verify client_info is set with capability_token
    assert server.client_info is not None
    assert server.client_info.name == "anima"
    assert server.client_info.version == "1.0.0"

    client_info_dict = server.client_info.model_dump()
    assert "capability_token" in client_info_dict
    capability_token_data = client_info_dict["capability_token"]
    assert capability_token_data["token_id"] == "tok_stdio_test"
    assert capability_token_data["tools_profile"] == "full"
    assert capability_token_data["persona_ref"] == "worker@v1"


def test_parse_mcp_servers_propagates_token_to_all_servers():
    """Token is propagated to all parsed MCP servers."""
    from pydantic_ai.mcp import MCPServerSSE, MCPServerStdio

    token = CapabilityToken(
        token_id="tok_multi",
        tools_profile="default",
        issued_at=datetime(2026, 3, 4, 0, 0, 0),
        expires_at=datetime(2026, 3, 4, 1, 0, 0),
        signature="sig_multi",
    )

    servers = parse_mcp_servers(
        ["https://api.example.com/mcp", "npx mcp-server /tmp"],
        capability_token=token,
    )

    assert servers is not None
    assert len(servers) == 2

    # First server (SSE)
    assert isinstance(servers[0], MCPServerSSE)
    assert servers[0].client_info is not None
    assert servers[0].client_info.name == "anima"
    capability_token_data_0 = servers[0].client_info.model_dump()["capability_token"]
    assert capability_token_data_0["token_id"] == "tok_multi"

    # Second server (Stdio)
    assert isinstance(servers[1], MCPServerStdio)
    assert servers[1].client_info is not None
    assert servers[1].client_info.name == "anima"
    capability_token_data_1 = servers[1].client_info.model_dump()["capability_token"]
    assert capability_token_data_1["token_id"] == "tok_multi"


def test_parse_mcp_servers_empty_without_token():
    """Empty list returns None (no servers created)."""
    servers = parse_mcp_servers([])
    assert servers is None

    servers = parse_mcp_servers(None)
    assert servers is None


def test_parse_mcp_servers_empty_with_token():
    """Empty list with token still returns None (no servers to attach token to)."""
    token = CapabilityToken(
        token_id="tok_empty",
        tools_profile="default",
        issued_at=datetime(2026, 3, 4, 0, 0, 0),
        expires_at=datetime(2026, 3, 4, 1, 0, 0),
        signature="sig_empty",
    )

    servers = parse_mcp_servers([], capability_token=token)
    assert servers is None

    servers = parse_mcp_servers(None, capability_token=token)
    assert servers is None


def test_capability_token_serialization():
    """CapabilityToken serializes correctly for MCP client_info."""
    token = CapabilityToken(
        token_id="tok_serialize",
        tools_profile="worker",
        persona_ref="worker@v1",
        instance_id="inst_123",
        max_depth=10,
        issued_at=datetime(2026, 3, 4, 10, 0, 0),
        expires_at=datetime(2026, 3, 4, 11, 0, 0),
        signature="sig_serialize",
    )

    token_dict = token.model_dump()

    # Verify all fields are present
    assert token_dict["token_id"] == "tok_serialize"
    assert token_dict["tools_profile"] == "worker"
    assert token_dict["persona_ref"] == "worker@v1"
    assert token_dict["instance_id"] == "inst_123"
    assert token_dict["max_depth"] == 10
    assert token_dict["signature"] == "sig_serialize"

    # Verify JSON serialization works
    token_json = token.model_dump_json()
    assert "tok_serialize" in token_json
    assert "worker" in token_json


def test_client_info_additional_properties():
    """Verify MCP Implementation type allows additional properties.

    This is critical for the H.5 spec - clientInfo.capability_token
    must be accepted by the MCP Implementation type.
    """
    from mcp.types import Implementation

    # Implementation allows extra fields (additionalProperties: true)
    impl = Implementation(
        name="test-client",
        version="1.0.0",
        capability_token={"token_id": "tok_test", "signature": "sig_test"},  # type: ignore[call-arg]
    )

    assert impl.name == "test-client"
    assert impl.version == "1.0.0"

    # Extra fields are preserved
    impl_dict = impl.model_dump()
    assert "capability_token" in impl_dict
    assert impl_dict["capability_token"]["token_id"] == "tok_test"
