// src/app/api/mcp/route.ts — remote MCP server (Streamable HTTP) for the
// Checklist add-on. Mounted via mcp-handler (the Next adapter for
// @modelcontextprotocol/sdk), stateless (no Redis/SSE store).
//
// Auth is enforced manually here rather than via experimental_withMcpAuth so we
// can return distinct HTTP codes: 401 (missing/invalid/revoked key) vs 403
// (valid key, add-on not enabled) — verifyToken can only express 401. We resolve
// the org from the bearer token, stash it on `request.auth.extra.orgId`, and the
// server's tools read it per-call (see server.ts). Every tool is org-scoped.
import { createMcpHandler } from "mcp-handler";
import type { NextRequest } from "next/server";
import { resolveMcpOrg } from "@/lib/mcp/auth";
import { checkRateLimit, clientIpFromHeaders } from "@/lib/mcp/rate-limit";
import {
  MCP_INSTRUCTIONS,
  MCP_SERVER_INFO,
  registerMcpTools,
} from "@/lib/mcp/server";

// HTTP rate limits (in-memory, per process — see rate-limit.ts for the
// deployment caveats). Two tiers:
//  • Pre-auth by client IP — bounds UNAUTHENTICATED floods. Every request costs
//    a sha256 + a SELECT on mcp_api_keys BEFORE any rejection, so we cap the IP
//    rate first and never reach that work for abusive callers.
//  • Post-auth by API key id — bounds an authenticated client that loops. The
//    monthly quota caps total volume but not the RATE; this caps burst writes on
//    the hot `organization` row (enforced inside resolveMcpOrg, before quota).
const MCP_PREAUTH_IP_LIMIT = 60;
const MCP_PREAUTH_WINDOW_MS = 60_000;
const MCP_POSTAUTH_KEY_LIMIT = 120;
const MCP_POSTAUTH_WINDOW_MS = 60_000;

const mcpHandler = createMcpHandler(
  (server) => {
    // Register the MCP tools (read-only except run_site_audit) on the adapter-owned server. Org-scoping is
    // per-request via request.auth.extra.orgId (set in handle() below).
    registerMcpTools(server);
  },
  { serverInfo: MCP_SERVER_INFO, instructions: MCP_INSTRUCTIONS },
  { basePath: "/api", maxDuration: 60, verboseLogs: false },
);

function jsonError(status: 401 | 403, error: string): Response {
  return new Response(JSON.stringify({ error }), {
    status,
    headers: {
      "Content-Type": "application/json",
      ...(status === 401 ? { "WWW-Authenticate": "Bearer" } : {}),
    },
  });
}

/** 429 with a Retry-After (seconds) header and a short JSON body. */
function tooManyRequests(retryAfterSec: number): Response {
  return new Response(
    JSON.stringify({ error: "Trop de requêtes", retryAfterSec }),
    {
      status: 429,
      headers: {
        "Content-Type": "application/json",
        "Retry-After": String(retryAfterSec),
      },
    },
  );
}

/** Resolve auth, attach orgId to request.auth, then delegate to the adapter. */
async function handle(request: NextRequest): Promise<Response> {
  // Pre-auth flood guard: cap the rate per client IP BEFORE any auth work
  // (sha256 + DB SELECT) is done, so an unauthenticated flood is cheap to shed.
  const ip = clientIpFromHeaders(request.headers);
  const preAuth = checkRateLimit(`mcp:ip:${ip}`, {
    limit: MCP_PREAUTH_IP_LIMIT,
    windowMs: MCP_PREAUTH_WINDOW_MS,
  });
  if (!preAuth.ok) return tooManyRequests(preAuth.retryAfterSec);

  // Post-auth per-key limit runs inside resolveMcpOrg (after key+add-on
  // resolution, before the quota UPDATE). We pass a bucket keyed on the DB key
  // id — never the clear bearer token.
  const auth = await resolveMcpOrg(request.headers.get("authorization"), {
    checkKeyRate: (keyId) =>
      checkRateLimit(`mcp:key:${keyId}`, {
        limit: MCP_POSTAUTH_KEY_LIMIT,
        windowMs: MCP_POSTAUTH_WINDOW_MS,
      }),
  });
  if (!auth.ok) {
    if (auth.status === 429) {
      return tooManyRequests(auth.retryAfterSec ?? 1);
    }
    return jsonError(auth.status, auth.error);
  }

  // Stash the validated org on the request; the SDK forwards request.auth into
  // each tool's `extra.authInfo`, where server.ts reads `extra.orgId`.
  (request as Request).auth = {
    token: auth.keyId,
    clientId: auth.orgId,
    scopes: [],
    extra: { orgId: auth.orgId },
  };

  return mcpHandler(request);
}

export { handle as GET, handle as POST, handle as DELETE };
