"""Integration tests for serve_mcp_over_http (Python).

Mirror of streamable-transport.test.ts. Uses Starlette's TestClient
against the composed app (no actual port bind), exercising:
  - GET /health  -> 200 unauth
  - POST /mcp without bearer -> 401
  - GET /sse without bearer -> 401 (when legacy_sse=True)
  - bearer-gated routes accept the configured token

Round-trip Streamable HTTP (initialize -> tools/list -> tools/call) is
covered end-to-end in the TypeScript suite against the same wire
protocol; here we focus on auth gating and the Starlette wiring.
"""

from __future__ import annotations

import pytest

starlette_testclient = pytest.importorskip("starlette.testclient")
TestClient = starlette_testclient.TestClient

mcp_server_mod = pytest.importorskip("mcp.server")
Server = mcp_server_mod.Server

from streamable_transport import serve_mcp_over_http  # noqa: E402

TOKEN = "test-token-py-789"


def _build_server() -> Server:
    server = Server("py-test-mcp")
    # Tool registration via the SDK's decorator API would go here.
    # For these tests we only need the auth/route surface, not tool
    # call semantics -- the round trip is covered in the TS suite.
    return server


def _make_app(legacy_sse: bool = False):
    result = serve_mcp_over_http(
        _build_server(),
        port=0,
        token=TOKEN,
        legacy_sse=legacy_sse,
        run=False,
    )
    return result.app


def test_health_returns_200_unauthenticated() -> None:
    client = TestClient(_make_app())
    res = client.get("/health")
    assert res.status_code == 200
    body = res.json()
    assert body["ok"] is True
    assert body["service"] == "py-test-mcp"


def test_mcp_without_bearer_returns_401() -> None:
    client = TestClient(_make_app())
    res = client.post(
        "/mcp",
        json={"jsonrpc": "2.0", "id": 1, "method": "tools/list"},
    )
    assert res.status_code == 401
    assert "Bearer" in res.headers.get("www-authenticate", "")


def test_mcp_with_wrong_bearer_returns_401() -> None:
    client = TestClient(_make_app())
    res = client.post(
        "/mcp",
        headers={"authorization": "Bearer wrong"},
        json={"jsonrpc": "2.0", "id": 1, "method": "tools/list"},
    )
    assert res.status_code == 401


def test_mcp_with_valid_bearer_does_not_401() -> None:
    """We don't assert success here -- the underlying SDK may require
    a full initialize handshake -- only that bearer auth lets the
    request through to the MCP transport layer."""
    client = TestClient(_make_app())
    res = client.post(
        "/mcp",
        headers={
            "authorization": f"Bearer {TOKEN}",
            "content-type": "application/json",
            "accept": "application/json, text/event-stream",
        },
        json={"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {
            "protocolVersion": "2024-11-05",
            "capabilities": {},
            "clientInfo": {"name": "test", "version": "0.0.0"},
        }},
    )
    assert res.status_code != 401


def test_unknown_route_returns_404() -> None:
    client = TestClient(_make_app())
    # Unauth path that isn't /health should still get bearer-checked.
    res = client.get("/does-not-exist")
    # Could be 401 (caught by middleware before routing) or 404; both
    # acceptable. The contract is "not 200, not a leak".
    assert res.status_code in (401, 404)


def test_legacy_sse_route_is_bearer_gated() -> None:
    client = TestClient(_make_app(legacy_sse=True))
    res = client.get("/sse")
    assert res.status_code == 401
