"""Protokolltest für einen MCP-Server über stdio (Stufe 9). Nur Standardbibliothek.

Startet den Server, spricht das Protokoll wie ein Client und prüft die Antworten.
Kein Agent, kein Modell: schnell, kostenlos, wiederholbar (auch in einer Pipeline).

Aufruf:
    uv run python pruefe_protokoll.py                      # prüft loesungen/stufe_07.py
    uv run python pruefe_protokoll.py mein_server.py       # prüft eine eigene Datei
    python pruefe_protokoll.py -- python ohne-sdk/server.py  # beliebiger Startbefehl
Exit-Code 0 = alles in Ordnung, 1 = mindestens eine Prüfung fehlgeschlagen.
"""
import json
import queue
import subprocess
import sys
import threading
from pathlib import Path

HIER = Path(__file__).resolve().parent
WARTEZEIT = 15  # Sekunden je Antwort


def startbefehl() -> list[str]:
    args = sys.argv[1:]
    if args and args[0] == "--":
        return args[1:]
    datei = args[0] if args else str(HIER / "loesungen" / "stufe_07.py")
    return [sys.executable, datei]


class Verbindung:
    def __init__(self, befehl: list[str]):
        self.p = subprocess.Popen(befehl, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
                                  text=True, encoding="utf-8", cwd=HIER)
        self.zeilen: queue.Queue = queue.Queue()
        self.fremd: list[str] = []
        threading.Thread(target=self._lesen, daemon=True).start()
        self.nr = 0

    def _lesen(self):
        for zeile in self.p.stdout:
            self.zeilen.put(zeile)
        self.zeilen.put(None)

    def senden(self, methode: str, params: dict | None = None, benachrichtigung: bool = False):
        nachricht = {"jsonrpc": "2.0", "method": methode}
        if params is not None:
            nachricht["params"] = params
        if not benachrichtigung:
            self.nr += 1
            nachricht["id"] = self.nr
        self.p.stdin.write(json.dumps(nachricht, ensure_ascii=False) + "\n")
        self.p.stdin.flush()
        if benachrichtigung:
            return None
        while True:
            try:
                zeile = self.zeilen.get(timeout=WARTEZEIT)
            except queue.Empty:
                raise TimeoutError(f"keine Antwort auf {methode} nach {WARTEZEIT} s")
            if zeile is None:
                raise ConnectionError("Server hat sich beendet. Fehlermeldung:\n" + self.p.stderr.read()[-1500:])
            try:
                antwort = json.loads(zeile)
            except json.JSONDecodeError:
                self.fremd.append(zeile.strip())  # etwas anderes als JSON auf stdout
                continue
            if antwort.get("id") == self.nr:
                return antwort

    def ende(self):
        try:
            self.p.stdin.close()
            self.p.wait(timeout=5)
        except Exception:
            self.p.kill()


def text_von(antwort: dict) -> str:
    return "".join(b.get("text", "") for b in antwort.get("result", {}).get("content", []))


def main() -> int:
    sys.stdout.reconfigure(encoding="utf-8")
    befehl = startbefehl()
    print("Starte:", " ".join(befehl))
    v = Verbindung(befehl)
    ergebnisse: list[tuple[bool, str]] = []

    def pruefe(ok: bool, text: str):
        ergebnisse.append((ok, text))
        print(("  OK      " if ok else "  FEHLER  ") + text)

    try:
        a = v.senden("initialize", {"protocolVersion": "2025-11-25", "capabilities": {},
                                    "clientInfo": {"name": "pruefe_protokoll", "version": "1.0"}})
        info = a.get("result", {})
        pruefe("serverInfo" in info and "protocolVersion" in info,
               f"initialize: {info.get('serverInfo', {}).get('name', '?')}, Protokoll {info.get('protocolVersion', '?')}")
        v.senden("notifications/initialized", benachrichtigung=True)

        werkzeuge = v.senden("tools/list").get("result", {}).get("tools", [])
        namen = [w["name"] for w in werkzeuge]
        pruefe({"produkt_suchen", "lagerbestand"} <= set(namen), f"tools/list: {', '.join(namen) or 'keine'}")
        kurz = [w["name"] for w in werkzeuge if len(w.get("description", "")) < 60]
        pruefe(not kurz, "jede Beschreibung hat mindestens 60 Zeichen" if not kurz
               else f"zu knapp beschrieben: {', '.join(kurz)}")

        a = v.senden("tools/call", {"name": "produkt_suchen", "arguments": {"suchtext": "Werkbank"}})
        pruefe(not a.get("result", {}).get("isError") and "WB-180" in text_von(a),
               "produkt_suchen('Werkbank') findet WB-180")

        a = v.senden("tools/call", {"name": "lagerbestand", "arguments": {"nummer": "XX-999"}})
        pruefe(a.get("result", {}).get("isError") is True and len(text_von(a)) > 30,
               f"lagerbestand('XX-999') meldet einen lesbaren Fehler: {text_von(a)[:70]!r}")

        a = v.senden("tools/call", {"name": "produkt_suchen", "arguments": {}})
        pruefe("error" in a or a.get("result", {}).get("isError") is True,
               "fehlender Parameter wird abgelehnt, der Server läuft weiter")

        pruefe("result" in v.senden("ping"), "ping nach allen Aufrufen")
        pruefe(not v.fremd, "nichts außer JSON auf stdout" if not v.fremd
               else f"fremde Ausgabe auf stdout: {v.fremd[0][:60]!r}")
    except (TimeoutError, ConnectionError) as e:
        pruefe(False, str(e))
    finally:
        v.ende()

    gut = sum(ok for ok, _ in ergebnisse)
    print(f"\n{gut} von {len(ergebnisse)} in Ordnung")
    return 0 if gut == len(ergebnisse) else 1


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