#!/usr/bin/env python3
"""Настоящий MCP-клиент STDIO без SDK: initialize → tools/list/call → завершение речи."""
import argparse
import json
import queue
import subprocess
import sys
import threading
import time
from pathlib import Path


class McpClient:
    def __init__(self, binary, timeout=15, env=None):
        # Запускаем свой helper и согласуем MCP; это не HTTP-запрос к модели.
        self.timeout = timeout
        self.sequence = 0
        self.process = subprocess.Popen([str(binary), "mcp"], stdin=subprocess.PIPE,
                                        stdout=subprocess.PIPE, stderr=subprocess.DEVNULL,
                                        text=True, bufsize=1, env=env)
        self.messages = queue.Queue()
        def reader():
            # Каждая STDIO-строка - отдельный JSON; None обозначает закрытие pipe.
            try:
                for line in self.process.stdout:
                    self.messages.put(json.loads(line))
            finally:
                self.messages.put(None)
        threading.Thread(target=reader, daemon=True).start()
        self.request("initialize", {"protocolVersion": "2025-03-26", "capabilities": {},
                                   "clientInfo": {"name": "tts-demo", "version": "1.0"}})
        self.send({"jsonrpc": "2.0", "method": "notifications/initialized"})

    def send(self, message):
        # Flush обязателен: передача должна завершить строку, а не ждать буфера.
        self.process.stdin.write(json.dumps(message) + "\n")
        self.process.stdin.flush()

    def request(self, method, params):
        # request ID отделяет нужный ответ от уведомлений; deadline ограничивает ожидание.
        self.sequence += 1
        self.send({"jsonrpc": "2.0", "id": self.sequence, "method": method, "params": params})
        deadline = time.monotonic() + self.timeout
        while True:
            remaining = deadline - time.monotonic()
            if remaining <= 0:
                raise TimeoutError(f"MCP timeout: {method}")
            message = self.messages.get(timeout=remaining)
            if message is None:
                raise RuntimeError("MCP-сервер закрыл stdout")
            if message.get("id") == self.sequence:
                if "error" in message:
                    raise RuntimeError(f"MCP error: {message['error']}")
                return message["result"]

    def tool(self, name, arguments=None):
        # MCP CallToolResult может содержать structuredContent или JSON внутри text.
        result = self.request("tools/call", {"name": name, "arguments": arguments or {}})
        if result.get("isError"):
            raise RuntimeError(f"MCP tool error: {result}")
        if "structuredContent" in result:
            return result["structuredContent"]
        for content in result.get("content", []):
            if content.get("type") == "text":
                return json.loads(content["text"])
        raise RuntimeError("Инструмент не вернул JSON")

    def close(self):
        # stop и завершение собственного процесса не обещают очистку системной
        # очереди Speech Dispatcher; её состояние нужно проверять отдельно.
        if self.process.poll() is None and self.process.stdin and not self.process.stdin.closed:
            try:
                self.tool("stop")
            except (OSError, RuntimeError, TimeoutError, queue.Empty):
                pass
        if self.process.stdin and not self.process.stdin.closed:
            self.process.stdin.close()
        try:
            self.process.wait(timeout=3)
        except subprocess.TimeoutExpired:
            self.process.terminate()
            self.process.wait(timeout=3)
        self.process.stdout.close()


def main():
    # started:true означает запуск backend, а не подтверждение всей слышимой речи.
    repo = Path(__file__).resolve().parents[2]
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("action", choices=["list", "doctor", "read", "read-stop"])
    parser.add_argument("--binary", type=Path, default=repo / "target/debug/codex-read-aloud-linux")
    parser.add_argument("--text-file", type=Path)
    parser.add_argument("--timeout", type=float, default=90)
    args = parser.parse_args()
    if args.timeout <= 0:
        parser.error("timeout должен быть положительным")
    text = args.text_file.read_text(encoding="utf-8") if args.text_file else None
    if args.action.startswith("read") and not text:
        parser.error("Нужен --text-file")
    client = McpClient(args.binary)
    try:
        if args.action == "list":
            result = client.request("tools/list", {})
        elif args.action == "doctor":
            result = client.tool("doctor")
        else:
            result = client.tool("read_aloud", {"text": text, "interrupt": True})
            print(json.dumps(result, ensure_ascii=False))
            if not result.get("started"):
                raise RuntimeError("Backend не запущен")
            if args.action == "read-stop":
                result = client.tool("stop")
            else:
                deadline = time.monotonic() + args.timeout
                while client.tool("doctor").get("active_backend"):
                    if time.monotonic() >= deadline:
                        client.tool("stop")
                        raise TimeoutError("Озвучивание не завершилось до timeout")
                    time.sleep(.1)
                result = {"tracked_process_completed": True,
                          "note": "Завершение процесса не гарантирует качество или слышимость звука"}
        print(json.dumps(result, ensure_ascii=False, indent=2))
    finally:
        client.close()


if __name__ == "__main__":
    try:
        main()
    except (OSError, ValueError, RuntimeError, TimeoutError, queue.Empty) as exc:
        print(f"MCP TTS: {exc}", file=sys.stderr)
        raise SystemExit(1)
