// src/lib/mcp/route.test.ts — in-process smoke of the /api/mcp route handler.
// Drives the real mcp-handler adapter via the exported POST handler with a
// seeded valid key (200 + tools/list shows the 10 tools) and unauthenticated
// requests (401 missing key, 403 add-on disabled). DB-backed; DB_TESTS=1 only.
import { beforeEach, describe, expect, mock, test } from "bun:test";

mock.module("server-only", () => ({}));
mock.module("next/cache", () => ({
  revalidatePath: () => {},
  revalidateTag: () => {},
  updateTag: () => {},
  unstable_cache: (fn: unknown) => fn,
}));

const RUN_DB = process.env.DB_TESTS === "1";
const ORG = "demo-org";
const ADDON = "checklist_mcp";
const MCP_URL = "http://localhost/api/mcp";

const INIT_BODY = {
  jsonrpc: "2.0",
  id: 1,
  method: "initialize",
  params: {
    protocolVersion: "2025-03-26",
    capabilities: {},
    clientInfo: { name: "test", version: "0.0.0" },
  },
};

function mcpRequest(body: unknown, auth?: string): Request {
  const headers: Record<string, string> = {
    "Content-Type": "application/json",
    // Streamable HTTP requires the client to accept both JSON and SSE.
    Accept: "application/json, text/event-stream",
  };
  if (auth) headers.Authorization = auth;
  return new Request(MCP_URL, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
  });
}

/** Parse an MCP streamable response (JSON or SSE-framed) into the JSON-RPC payload. */
async function readRpc(res: Response): Promise<Record<string, unknown>> {
  const ct = res.headers.get("content-type") || "";
  const raw = await res.text();
  if (ct.includes("text/event-stream")) {
    // Lines look like: `event: message\ndata: {…}\n\n`. Grab the first data line.
    const dataLine = raw.split(/\r?\n/).find((l) => l.startsWith("data:"));
    return dataLine ? JSON.parse(dataLine.slice(5).trim()) : {};
  }
  return raw ? JSON.parse(raw) : {};
}

describe.skipIf(!RUN_DB)("/api/mcp route (postgres)", () => {
  let db: typeof import("@/lib/db/client").db;
  let schema: typeof import("@/lib/db/domain-schema");
  let authSchema: typeof import("@/lib/db/auth-schema");
  let POST: (req: Request) => Promise<Response>;
  let validKey: string;
  const USER = "seed-owner";

  beforeEach(async () => {
    ({ db } = await import("@/lib/db/client"));
    schema = await import("@/lib/db/domain-schema");
    authSchema = await import("@/lib/db/auth-schema");
    const { assertTestDb } = await import("@/lib/db/assert-test-db");
    await assertTestDb();
    await db.delete(schema.mcpApiKeys);
    await db.delete(schema.orgAddons);
    await db
      .insert(authSchema.user)
      .values({
        id: USER,
        name: "Seed Owner",
        email: "owner@seo-insight.local",
        emailVerified: true,
        createdAt: new Date(),
        updatedAt: new Date(),
      })
      .onConflictDoNothing();

    const { generateApiKey } = await import("./keys");
    const { key, prefix, hash } = generateApiKey();
    validKey = key;
    await db.insert(schema.mcpApiKeys).values({
      id: `route-key-${prefix}`,
      orgId: ORG,
      userId: USER,
      name: "Route test",
      prefix,
      keyHash: hash,
    });

    ({ POST } = (await import("@/app/api/mcp/route")) as unknown as {
      POST: (req: Request) => Promise<Response>;
    });
  });

  async function enableAddon() {
    await db
      .insert(schema.orgAddons)
      .values({ orgId: ORG, addon: ADDON })
      .onConflictDoNothing();
  }

  test("missing key → 401", async () => {
    const res = await POST(mcpRequest(INIT_BODY));
    expect(res.status).toBe(401);
  });

  test("valid key, add-on disabled → 403", async () => {
    const res = await POST(mcpRequest(INIT_BODY, `Bearer ${validKey}`));
    expect(res.status).toBe(403);
  });

  test("valid key + add-on: initialize 200 and tools/list shows the registered tools", async () => {
    await enableAddon();

    const initRes = await POST(mcpRequest(INIT_BODY, `Bearer ${validKey}`));
    expect(initRes.status).toBe(200);

    const listRes = await POST(
      mcpRequest(
        { jsonrpc: "2.0", id: 2, method: "tools/list", params: {} },
        `Bearer ${validKey}`,
      ),
    );
    expect(listRes.status).toBe(200);
    const payload = await readRpc(listRes);
    const result = payload.result as { tools?: { name: string }[] } | undefined;
    const names = (result?.tools ?? []).map((t) => t.name).sort();
    expect(names).toEqual(
      [
        "audit_overview",
        "get_capabilities",
        "get_keyword",
        "get_keyword_brief",
        "get_site_audit",
        "get_spec_topic",
        "list_sites",
        "list_spec_topics",
        "run_site_audit",
        "search_spec_topics",
      ].sort(),
    );
  });
});
