"""Сборка MCP-сервера Synapse (docs/07).

FastMCP (stateless_http) — Starlette-приложение streamable-http; поверх —
McpTokenGuard. Два вида Bearer-ключей:

- статический `MCP_TOKEN` из .env — суперадмин-бэкдор (совместимость с
  сценариями агентов до персональных ключей; токен держать как ключ прод-сервера);
- персональный `mcp_...` — выдаётся пользователем самому себе на SPA
  /mcp-keys; MCP-запрос действует ролью владельца (админ-тулы — только
  для ролей admin/superadmin, остальное — личный набор).
"""

from mcp.server.fastmcp import FastMCP

from app.mcp.context import MCP_USER, reset_mcp_user, resolve_personal_token, set_mcp_user
from app.mcp.tools import register_tools


def build_mcp_server() -> FastMCP:
    mcp = FastMCP(
        "synapse",
        stateless_http=True,
        instructions=(
            "Управление хабом уведомлений Gnexus Synapse: источники и выдача "
            "API-ключей, типы уведомлений, цели каналов, правила маршрутизации, "
            "поток событий и доставок, настройки. Персональный ключ действует "
            "ролью владельца: роль user — личный набор (me, my_events_list, "
            "my_push_*). Удаление = архив (restore возвращает); секреты настроек "
            "write-only."
        ),
    )
    register_tools(mcp)
    return mcp


class McpTokenGuard:
    """Чистый ASGI-гейт (не BaseHTTPMiddleware): резолвит Bearer-токен на
    каждом http-запросе. Не BaseHTTPMiddleware — чтобы не ломать SSE-стримы.

    Резолв персонального ключа — короткий indexed lookup в БД прямо в
    event loop-е (миллисекунды; у гейта нет threadpool-обёртки, как у тулов).
    """

    def __init__(self, app, token: str | None) -> None:
        self.app = app
        self.static_expected = f"Bearer {token}".encode() if token else None

    async def __call__(self, scope, receive, send):
        if scope["type"] != "http":
            await self.app(scope, receive, send)
            return
        headers = {k.lower(): v for k, v in scope.get("headers") or []}
        auth = headers.get(b"authorization", b"")
        user = None
        if self.static_expected is not None and auth == self.static_expected:
            user = MCP_USER  # суперадмин-бэкдор из .env
        elif auth.startswith(b"Bearer mcp_"):
            from app.database import SessionLocal

            with SessionLocal() as db:
                user = resolve_personal_token(db, auth[len("Bearer "):].strip().decode("utf-8", "replace"))
        if user is None:
            body = b'{"detail":"Invalid or missing MCP token"}'
            await send(
                {
                    "type": "http.response.start",
                    "status": 401,
                    "headers": [
                        (b"content-type", b"application/json"),
                        (b"www-authenticate", b"Bearer"),
                        (b"content-length", str(len(body)).encode()),
                    ],
                }
            )
            await send({"type": "http.response.body", "body": body})
            return
        ctx_token = set_mcp_user(user)
        try:
            await self.app(scope, receive, send)
        finally:
            reset_mcp_user(ctx_token)