"use strict";

const BASE_URL = (process.env.MNOTE_UI_BASE_URL || "http://127.0.0.1:3000").replace(/\/+$/, "");
const REQUEST_TIMEOUT_MS = Number(process.env.MNOTE_SMOKE_TIMEOUT_MS || 20_000);

function assert(condition, message) {
  if (!condition) {
    throw new Error(message);
  }
}

async function fetchWithTimeout(path, init = {}) {
  const controller = new AbortController();
  const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
  try {
    return await fetch(`${BASE_URL}${path}`, {
      ...init,
      signal: controller.signal,
    });
  } finally {
    clearTimeout(timeout);
  }
}

async function readTextResponse(path, init) {
  const response = await fetchWithTimeout(path, init);
  const text = await response.text();
  assert(response.ok, `${path} 请求失败: ${response.status} ${text.slice(0, 200)}`);
  return { response, text };
}

function runtimeInputToolPlan() {
  return {
    kind: "tool",
    context: {
      deploymentId: null,
      projectId: null,
      workspaceId: "ws_task116",
      requestId: "req_task116",
      traceId: "trace_task116",
      actor: {
        actorType: "user",
        actorId: "task116-user",
        sessionId: null,
      },
      source: {
        channel: "rust-web",
        client: "task116-smoke",
      },
      tenantId: null,
      authToken: null,
      idempotencyKey: null,
      validateOnly: false,
      dryRun: false,
    },
    tool: {
      tool: "mnote.knowledge_rag.query",
      kind: "query",
      mode: "plan",
      argsJson: { query: "Rust Web islands" },
      target: null,
      reason: "task116 owner smoke",
      refs: ["task-025"],
    },
    data: null,
  };
}

async function checkSearchShell() {
  const { response, text } = await readTextResponse("/search?workspaceId=ws_task116&q=Rust");
  assert(response.headers.get("x-mnote-web-owner") === "mnote-web", "Search shell 缺少 mnote-web owner header");
  assert(response.headers.get("x-mnote-web-shell") === "search", "Search shell 缺少 search shell header");
  assert(text.includes("mnote.search_shell.v1"), "Search shell 缺少 contract schema");
  assert(text.includes("search_interaction_island"), "Search shell 缺少 search interaction island");
  return { owner: "mnote-web", shell: "search", island: "search_interaction_island" };
}

async function checkAiBridge() {
  const { response, text } = await readTextResponse("/api/hermes/bridge", {
    method: "POST",
    headers: { "content-type": "application/json" },
    body: JSON.stringify(runtimeInputToolPlan()),
  });
  assert(response.headers.get("x-mnote-web-owner") === "mnote-web", "AI bridge 缺少 mnote-web owner header");
  assert(response.headers.get("x-mnote-ai-bridge-owner") === "rust-web-hermes", "AI bridge 缺少 rust-web-hermes owner header");
  const payload = JSON.parse(text);
  assert(payload?.contract?.schema === "mnote.ai_bridge.v1", "AI bridge 缺少 contract schema");
  assert(payload?.contract?.toolEventOwner === "rust-web-hermes", "AI bridge tool event owner 未固定到 Hermes");
  return { owner: "mnote-web", bridgeOwner: "rust-web-hermes", schema: payload.contract.schema };
}

async function checkMindmapShell() {
  const { response, text } = await readTextResponse("/mindmap/doc_task116/mind_task116");
  assert(response.headers.get("x-mnote-web-owner") === "mnote-web", "Mindmap shell 缺少 mnote-web owner header");
  assert(response.headers.get("x-mnote-web-shell") === "mindmap", "Mindmap shell 缺少 mindmap shell header");
  assert(text.includes("mnote.mindmap_shell.v1"), "Mindmap shell 缺少 contract schema");
  assert(text.includes('data-react-island="mindmap_runtime"'), "Mindmap shell 缺少 mindmap runtime island");
  return { owner: "mnote-web", shell: "mindmap", island: "mindmap_runtime" };
}

async function main() {
  const search = await checkSearchShell();
  const aiBridge = await checkAiBridge();
  const mindmap = await checkMindmapShell();
  console.log(
    JSON.stringify(
      {
        ok: true,
        baseUrl: BASE_URL,
        results: {
          search,
          aiBridge,
          mindmap,
        },
      },
      null,
      2,
    ),
  );
}

main().catch((error) => {
  console.error(error instanceof Error ? error.stack || error.message : String(error));
  process.exit(1);
});
