#!/usr/bin/env node
"use strict";

const assert = require("node:assert");
const { chromium } = require("playwright");
const {
  BASE_URL,
  UI_TIMEOUT_MS,
  cleanupDocuments,
  createTempDocument,
  ensureAuthenticated,
  renameDocument,
  requestJson,
} = require("./tree-shell-smoke-helpers");

async function callArtifact(request, target, suffix, toolName, args) {
  return requestJson(request, "/api/hermes/tools/mnote/call", {
    method: "POST",
    headers: { "x-mnote-actor-id": "smoke-user" },
    data: {
      toolName,
      workspaceId: target.workspaceId,
      documentId: target.documentId,
      actorId: "smoke-user",
      sessionId: `mnote_smoke_artifact_${suffix}`,
      runId: `run_smoke_artifact_${suffix}`,
      toolCallId: `call_${toolName.replace(/\W+/g, "_")}_${suffix}`,
      traceId: `trace_smoke_artifact_${suffix}`,
      idempotencyKey: `idem_${toolName.replace(/\W+/g, "_")}_${suffix}`,
      dryRun: false,
      capabilityScope: ["artifact.write"],
      args,
    },
  });
}

function assertArtifactReferenceEdge(edgesPayload, sourceDocumentId, artifactDocumentId, artifactType) {
  const edges = Array.isArray(edgesPayload?.result?.edges) ? edgesPayload.result.edges : [];
  const edge = edges.find(
    (candidate) =>
      candidate &&
      candidate.fromNodeId === sourceDocumentId &&
      candidate.toNodeId === artifactDocumentId &&
      candidate.metadata?.kind === "ai_artifact_reference",
  );
  assert(edge, `未查询到 ${artifactDocumentId} 的 ai_artifact_reference edge`);
  assert.equal(edge.edgeType, "source_of", `${artifactDocumentId} reference edge 类型不对`);
  assert.equal(edge.metadata.artifactType, artifactType, `${artifactDocumentId} artifactType 不对`);
  assert.equal(edge.metadata.projectionOnlyGroup, "AI Artifacts", `${artifactDocumentId} projection group 不对`);
}

async function main() {
  const suffix = Date.now().toString(36);
  const title = `TEST-HERMES-AI-artifact-${suffix}`;
  const quickSessionId = `mnote_smoke_artifact_quick_${suffix}`;
  let quickRunIndex = 0;
  const quickRunBodies = [];
  const createdIds = [];
  const browser = await chromium.launch({ headless: true });
  const context = await browser.newContext({ viewport: { width: 1280, height: 900 } });
  const page = await context.newPage();

  try {
    await page.route("**/api/hermes/tools/mnote/call", async (route) => {
      throw new Error(`页面 AI artifact 快捷入口不应直连 mnote tool route: ${route.request().url()}`);
    });
    await page.route("**/api/hermes/client/sessions", async (route) => {
      await route.fulfill({
        status: 200,
        headers: { "content-type": "application/json" },
        body: JSON.stringify({
          ok: true,
          sessionId: quickSessionId,
          title: "当前页问答",
          traceId: `trace_quick_session_${suffix}`,
        }),
      });
    });
    await page.route(`**/api/hermes/client/sessions/${quickSessionId}`, async (route) => {
      await route.fulfill({
        status: 200,
        headers: { "content-type": "application/json" },
        body: JSON.stringify({
          ok: true,
          sessionId: quickSessionId,
          session: { sessionId: quickSessionId, messages: [] },
        }),
      });
    });
    await page.route("**/api/hermes/client/runs", async (route) => {
      quickRunIndex += 1;
      quickRunBodies.push(JSON.parse(route.request().postData() || "{}"));
      await route.fulfill({
        status: 200,
        headers: { "content-type": "application/json" },
        body: JSON.stringify({
          ok: true,
          sessionId: quickSessionId,
          runId: `run_quick_artifact_${suffix}_${quickRunIndex}`,
          traceId: `trace_quick_artifact_${suffix}_${quickRunIndex}`,
        }),
      });
    });
    await page.route("**/api/hermes/client/events/**", async (route) => {
      const runId = route.request().url().split("/").pop();
      await route.fulfill({
        status: 200,
        headers: { "content-type": "text/event-stream; charset=utf-8" },
        body:
          `data: ${JSON.stringify({ event: "message.delta", run_id: runId, session_id: quickSessionId, delta: "artifact intent queued" })}\n\n` +
          `data: ${JSON.stringify({ event: "run.completed", run_id: runId, session_id: quickSessionId, output: "artifact intent queued" })}\n\n`,
      });
    });

    await ensureAuthenticated(page, context.request);
    const target = await createTempDocument(context.request);
    createdIds.push(target.documentId);
    await renameDocument(context.request, target.workspaceId, target.documentId, title);

    const summary = await callArtifact(context.request, target, suffix, "mnote.artifact.create_summary", {
      summary: `摘要 ${suffix}`,
    });
    const summaryRetry = await callArtifact(context.request, target, suffix, "mnote.artifact.create_summary", {
      summary: `摘要 ${suffix}`,
    });
    const aiNote = await callArtifact(context.request, target, suffix, "mnote.artifact.create_ai_note", {
      content: `AI Note ${suffix}`,
    });
    const aiNoteSecond = await callArtifact(context.request, target, `${suffix}_second`, "mnote.artifact.create_ai_note", {
      content: `AI Note second ${suffix}`,
    });
    const artifactDocumentIds = Array.from(
      new Set([
        summary.result.artifactDocumentId,
        aiNote.result.artifactDocumentId,
        aiNoteSecond.result.artifactDocumentId,
      ]),
    );
    createdIds.push(...artifactDocumentIds);

    for (const result of [summary, aiNote, aiNoteSecond]) {
      assert.equal(result.ok, true, `${result.toolName} 应成功`);
      assert.equal(result.audit.effect, "write", `${result.toolName} audit 应为 write`);
      assert.equal(result.result.commandName, "tree.node.create", `${result.toolName} 必须走 tree.node.create`);
      assert.equal(result.audit.commandId, result.result.commandId, `${result.toolName} audit commandId 必须指向 Rust commandId`);
      assert(result.result.artifactDocumentId, `${result.toolName} 缺少 artifactDocumentId`);
      assert.equal(result.result.referenceEdge?.from, target.documentId, `${result.toolName} reference edge from 不对`);
      assert.equal(result.result.referenceEdge?.to, result.result.artifactDocumentId, `${result.toolName} reference edge to 不对`);
      assert.equal(result.result.referenceEdge?.kind, "ai_artifact_reference", `${result.toolName} reference edge kind 不对`);
      assert(result.result.artifacts?.commandLog, `${result.toolName} 缺少 commandLog artifact`);
      assert(result.result.artifacts?.domainEvent, `${result.toolName} 缺少 domainEvent artifact`);
      assert.equal(
        result.result.artifacts.domainEvent.eventType,
        "tree.node.created",
        `${result.toolName} domain event 类型不对`,
      );
    }
    assert.equal(summaryRetry.result.commandId, summary.result.commandId, "summary 同 idempotencyKey 重试必须返回同一 commandId");
    assert.equal(
      summaryRetry.result.artifactDocumentId,
      summary.result.artifactDocumentId,
      "summary 同页面重试必须指向同一 summary document",
    );
    assert.notEqual(
      aiNoteSecond.result.artifactDocumentId,
      aiNote.result.artifactDocumentId,
      "ai_note 多次创建必须生成独立 artifact document",
    );

    const edgePayload = await requestJson(
      context.request,
      `/api/kernel/edges?workspaceId=${encodeURIComponent(target.workspaceId)}&nodeId=${encodeURIComponent(target.documentId)}`,
      { method: "GET" },
    );
    assertArtifactReferenceEdge(edgePayload, target.documentId, summary.result.artifactDocumentId, "summary");
    assertArtifactReferenceEdge(edgePayload, target.documentId, aiNote.result.artifactDocumentId, "ai_note");
    assertArtifactReferenceEdge(edgePayload, target.documentId, aiNoteSecond.result.artifactDocumentId, "ai_note");

    const fileProjection = await requestJson(
      context.request,
      `/api/tree/projections/file?workspaceId=${encodeURIComponent(target.workspaceId)}&rootNodeId=${encodeURIComponent(target.documentId)}&depth=2`,
      { method: "GET" },
    );
    const projectionItems = Array.isArray(fileProjection?.result?.items) ? fileProjection.result.items : [];
    const aiArtifactsMeta = fileProjection?.result?.meta?.aiArtifacts || {};
    assert.equal(aiArtifactsMeta.title, "AI Artifacts", "AI Artifacts projection meta 标题不对");
    assert.equal(aiArtifactsMeta.projectionOnly, true, "AI Artifacts 必须是 projection-only 分组");
    assert.equal(aiArtifactsMeta.kernelNodeId, null, "AI Artifacts 不应是真实 kernel node");
    assert(
      !projectionItems.some((item) => item && item.nodeId === "AI Artifacts"),
      "AI Artifacts 不应出现在 projection items 中作为真实 node",
    );
    for (const artifactDocumentId of artifactDocumentIds) {
      assert(
        aiArtifactsMeta.artifactDocumentIds?.includes(artifactDocumentId),
        `AI Artifacts projection meta 缺少 ${artifactDocumentId}`,
      );
    }

    const auditPayload = await requestJson(
      context.request,
      `/api/hermes/tools/mnote/audit?traceId=${encodeURIComponent(`trace_smoke_artifact_${suffix}`)}`,
      {
        method: "GET",
        headers: { "x-mnote-actor-id": "smoke-user" },
      },
    );
    const auditEvents = Array.isArray(auditPayload.events) ? auditPayload.events : [];
    for (const result of [summary, aiNote]) {
      assert(
        auditEvents.some(
          (event) =>
            event &&
            event.phase === "completed" &&
            event.toolCallId === result.toolCallId &&
            event.audit?.commandId === result.result.commandId,
        ),
        `${result.toolName} audit 未串到 Hermes tool call 和 Rust command`,
      );
    }

    await page.goto(
      `${BASE_URL}/documents/${encodeURIComponent(target.documentId)}?workspaceId=${encodeURIComponent(target.workspaceId)}`,
      { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS },
    );
    await page.getByTestId("wolai-floating-ai").click({ timeout: UI_TIMEOUT_MS });
    await page.locator('[data-page-ai-intent="create-summary"]').click({ timeout: UI_TIMEOUT_MS });
    await page.waitForFunction(
      () => (document.querySelector('[data-testid="wolai-page-ai-drawer"]')?.textContent || "").includes("artifact intent queued"),
      undefined,
      { timeout: UI_TIMEOUT_MS },
    );
    await page.locator('[data-page-ai-intent="create-ai-note"]').click({ timeout: UI_TIMEOUT_MS });
    const deadline = Date.now() + UI_TIMEOUT_MS;
    while (quickRunBodies.length < 2 && Date.now() < deadline) {
      await page.waitForTimeout(100);
    }
    assert.equal(quickRunBodies.length, 2, "两个 artifact 快捷入口都必须发送 Hermes run intent");
    assert(
      quickRunBodies[0].message.includes("mnote.artifact.create_summary"),
      "创建 Summary 快捷入口必须发送 Hermes artifact summary intent",
    );
    assert(
      quickRunBodies[1].message.includes("mnote.artifact.create_ai_note"),
      "创建 AI Note 快捷入口必须发送 Hermes artifact ai_note intent",
    );
    assert(
      quickRunBodies.every((body) => body.documentId === target.documentId && body.sessionId === quickSessionId),
      "artifact 快捷入口必须携带当前页面 documentId 与 Hermes sessionId",
    );

    console.log(
      JSON.stringify(
        {
          ok: true,
          baseUrl: BASE_URL,
          documentId: target.documentId,
          workspaceId: target.workspaceId,
          summaryArtifactType: summary.result.artifactType,
          aiNoteArtifactType: aiNote.result.artifactType,
          summaryCommand: summary.result.commandName,
          aiNoteCommand: aiNote.result.commandName,
          summaryArtifactDocumentId: summary.result.artifactDocumentId,
          aiNoteArtifactDocumentId: aiNote.result.artifactDocumentId,
          aiNoteSecondArtifactDocumentId: aiNoteSecond.result.artifactDocumentId,
          referenceEdgeCount: edgePayload.result.edges.length,
          aiArtifactsMeta,
          auditEventCount: auditEvents.length,
          quickIntentMessages: quickRunBodies.map((body) => body.message),
        },
        null,
        2,
      ),
    );
  } finally {
    await cleanupDocuments(context.request, createdIds).catch(() => undefined);
    await context.close().catch(() => undefined);
    await browser.close().catch(() => undefined);
  }
}

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