#!/usr/bin/env python3
"""
Demo: make an MCP request against mcplocal using an McpToken bearer.

This is the standalone counterpart to `mcpctl test mcp` — intended to show
exactly what a non-Claude client (e.g. a vLLM-driven agent) would do.

Usage:
    # Default: localhost mcplocal, sre project, token from $MCPCTL_TOKEN
    export MCPCTL_TOKEN=mcpctl_pat_...
    python3 scripts/demo-mcp-call.py

    # Custom URL/project/tool
    python3 scripts/demo-mcp-call.py \\
        --url https://mcp.ad.itaz.eu \\
        --project sre \\
        --token "$MCPCTL_TOKEN" \\
        --tool begin_session \\
        --args '{"description":"hello"}'

No third-party deps — pure stdlib. Mirrors the protocol that
src/shared/src/mcp-http/index.ts implements on the TypeScript side.
"""
from __future__ import annotations

import argparse
import json
import os
import sys
import urllib.error
import urllib.request
from typing import Any


def _parse_sse(body: str) -> list[dict[str, Any]]:
    """Parse a text/event-stream body into a list of JSON-RPC messages."""
    out: list[dict[str, Any]] = []
    for line in body.splitlines():
        if line.startswith("data: "):
            try:
                out.append(json.loads(line[6:]))
            except json.JSONDecodeError:
                pass
    return out


class McpSession:
    def __init__(self, url: str, bearer: str | None = None, timeout: float = 30.0):
        self.url = url
        self.bearer = bearer
        self.timeout = timeout
        self.session_id: str | None = None
        self._next_id = 1

    def _headers(self) -> dict[str, str]:
        h = {
            "Content-Type": "application/json",
            "Accept": "application/json, text/event-stream",
        }
        if self.bearer:
            h["Authorization"] = f"Bearer {self.bearer}"
        if self.session_id:
            h["mcp-session-id"] = self.session_id
        return h

    def send(self, method: str, params: dict[str, Any] | None = None) -> Any:
        rid = self._next_id
        self._next_id += 1
        payload = {"jsonrpc": "2.0", "id": rid, "method": method, "params": params or {}}
        req = urllib.request.Request(
            self.url,
            data=json.dumps(payload).encode("utf-8"),
            headers=self._headers(),
            method="POST",
        )
        try:
            with urllib.request.urlopen(req, timeout=self.timeout) as resp:
                body = resp.read().decode("utf-8")
                content_type = resp.headers.get("content-type", "")
                # First successful response carries the session id.
                if self.session_id is None:
                    sid = resp.headers.get("mcp-session-id")
                    if sid:
                        self.session_id = sid
                messages: list[dict[str, Any]] = (
                    _parse_sse(body) if "text/event-stream" in content_type else [json.loads(body)]
                )
        except urllib.error.HTTPError as e:
            err_body = e.read().decode("utf-8", errors="replace")
            raise SystemExit(f"HTTP {e.code} from {self.url}: {err_body}") from None
        except urllib.error.URLError as e:
            raise SystemExit(f"transport error reaching {self.url}: {e.reason}") from None

        # Pick the response matching our id; fall back to first message.
        matched = next((m for m in messages if m.get("id") == rid), messages[0] if messages else None)
        if matched is None:
            raise SystemExit(f"no response for {method}")
        if "error" in matched:
            err = matched["error"]
            raise SystemExit(f"MCP error {err.get('code')}: {err.get('message')}")
        return matched.get("result")

    def initialize(self) -> dict[str, Any]:
        return self.send(
            "initialize",
            {
                "protocolVersion": "2024-11-05",
                "capabilities": {},
                "clientInfo": {"name": "demo-mcp-call.py", "version": "1.0.0"},
            },
        )

    def list_tools(self) -> list[dict[str, Any]]:
        result = self.send("tools/list")
        return result.get("tools", []) if isinstance(result, dict) else []

    def call_tool(self, name: str, args: dict[str, Any]) -> Any:
        return self.send("tools/call", {"name": name, "arguments": args})


def main() -> int:
    ap = argparse.ArgumentParser(description="Demo MCP request via McpToken bearer.")
    ap.add_argument("--url", default=os.environ.get("MCPGW_URL", "http://localhost:3200"),
                    help="Base URL of mcplocal (default: $MCPGW_URL or http://localhost:3200)")
    ap.add_argument("--project", default="sre",
                    help="Project name (default: sre). Must match the token's bound project.")
    ap.add_argument("--token", default=os.environ.get("MCPCTL_TOKEN"),
                    help="Raw mcpctl_pat_* bearer (default: $MCPCTL_TOKEN)")
    ap.add_argument("--tool", help="Optionally call a tool after tools/list")
    ap.add_argument("--args", default="{}", help="JSON-encoded arguments for --tool")
    ap.add_argument("--timeout", type=float, default=30.0)
    opts = ap.parse_args()

    if not opts.token:
        ap.error("--token or $MCPCTL_TOKEN required")

    endpoint = f"{opts.url.rstrip('/')}/projects/{opts.project}/mcp"
    print(f"→ POST {endpoint}")
    print(f"  Bearer: {opts.token[:16]}…")
    print()

    sess = McpSession(endpoint, bearer=opts.token, timeout=opts.timeout)

    info = sess.initialize()
    server_info = info.get("serverInfo", {}) if isinstance(info, dict) else {}
    print(f"initialize:  protocol={info.get('protocolVersion') if isinstance(info, dict) else '?'} "
          f"server={server_info.get('name', '?')}/{server_info.get('version', '?')} "
          f"sessionId={sess.session_id}")

    tools = sess.list_tools()
    print(f"tools/list:  {len(tools)} tool(s)")
    for t in tools:
        desc = (t.get("description") or "").splitlines()[0][:80]
        print(f"  - {t['name']}  {desc}")

    if opts.tool:
        try:
            args = json.loads(opts.args)
        except json.JSONDecodeError as e:
            raise SystemExit(f"--args must be valid JSON: {e}")
        print(f"\ntools/call: {opts.tool} {args}")
        result = sess.call_tool(opts.tool, args)
        print(json.dumps(result, indent=2)[:2000])

    return 0


if __name__ == "__main__":
    sys.exit(main())
