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

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

async function assertPageAiPermissionFailureUi(page, target, suffix) {
  const sessionId = `mnote_smoke_permission_${suffix}`;
  const runId = `run_smoke_permission_${suffix}`;
  const sessionRoute = "**/api/hermes/client/sessions";
  const sessionDetailRoute = `**/api/hermes/client/sessions/${sessionId}`;
  const runsRoute = "**/api/hermes/client/runs";
  const eventsRoute = `**/api/hermes/client/events/${runId}`;

  await page.route(sessionRoute, async (route) => {
    await route.fulfill({
      status: 200,
      headers: { "content-type": "application/json" },
      body: JSON.stringify({
        ok: true,
        sessionId,
        title: "权限失败 UI 验证",
        traceId: `trace_permission_${suffix}`,
      }),
    });
  });
  await page.route(sessionDetailRoute, async (route) => {
    await route.fulfill({
      status: 200,
      headers: { "content-type": "application/json" },
      body: JSON.stringify({
        ok: true,
        sessionId,
        session: { sessionId, messages: [] },
      }),
    });
  });
  await page.route(runsRoute, async (route) => {
    await route.fulfill({
      status: 200,
      headers: { "content-type": "application/json" },
      body: JSON.stringify({
        ok: true,
        upstream: { run_id: runId, status: "started" },
        traceId: `trace_permission_${suffix}`,
      }),
    });
  });
  await page.route(eventsRoute, async (route) => {
    await route.fulfill({
      status: 200,
      headers: { "content-type": "text/event-stream; charset=utf-8" },
      body:
        `data: ${JSON.stringify({ event: "tool.started", run_id: runId, session_id: sessionId, tool: "mnote.page.save" })}\n\n` +
        `data: ${JSON.stringify({ event: "tool.failed", run_id: runId, session_id: sessionId, tool: "mnote.page.save", error: { code: "permission_denied" } })}\n\n` +
        `data: ${JSON.stringify({ event: "run.completed", run_id: runId, session_id: sessionId, output: "permission_denied" })}\n\n`,
    });
  });

  try {
    await openDocument(page, target.workspaceId, target.documentId);
    await page.getByTestId("wolai-floating-ai").click({ timeout: UI_TIMEOUT_MS });
    await page.locator("[data-page-ai-input]").fill("权限失败 UI 验证", { timeout: UI_TIMEOUT_MS });
    await page.locator('[data-page-ai-action="send"]').click({ timeout: UI_TIMEOUT_MS });
    await page.waitForFunction(
      () => {
        const text = document.querySelector('[data-testid="wolai-page-ai-drawer"]')?.textContent || "";
        return text.includes("mnote.page.save") && text.includes("permission_denied");
      },
      null,
      { timeout: UI_TIMEOUT_MS },
    );
  } finally {
    await page.unroute(sessionRoute).catch(() => undefined);
    await page.unroute(sessionDetailRoute).catch(() => undefined);
    await page.unroute(runsRoute).catch(() => undefined);
    await page.unroute(eventsRoute).catch(() => undefined);
  }
}

async function main() {
  const suffix = Date.now().toString(36);
  const title = `TEST-HERMES-AI-write-${suffix}`;
  const marker = `TEST-HERMES-AI-WRITEBACK-${suffix}`;
  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 ensureAuthenticated(page, context.request);
    const target = await createTempDocument(context.request);
    createdIds.push(target.documentId);
    await renameDocument(context.request, target.workspaceId, target.documentId, title);

    const baseTool = {
      toolName: "mnote.page.save",
      workspaceId: target.workspaceId,
      documentId: target.documentId,
      actorId: "smoke-user",
      sessionId: `mnote_smoke_write_${suffix}`,
      runId: `run_smoke_write_${suffix}`,
      toolCallId: `call_smoke_write_${suffix}`,
      traceId: `trace_smoke_write_${suffix}`,
      idempotencyKey: `idem_smoke_write_${suffix}`,
      capabilityScope: ["page.write"],
      args: {
        mode: "replace",
        content: [
          {
            id: `block_${suffix}`,
            type: "paragraph",
            content: [{ type: "text", text: marker }],
          },
        ],
      },
    };

    const deniedResponse = await context.request.fetch(`${BASE_URL}/api/hermes/tools/mnote/call`, {
      method: "POST",
      headers: {
        "content-type": "application/json",
        "x-mnote-actor-id": "smoke-user",
        "x-mnote-workspace-id": "ws_denied",
      },
      data: JSON.stringify({
        ...baseTool,
        traceId: `trace_smoke_write_denied_${suffix}`,
        toolCallId: `call_smoke_write_denied_${suffix}`,
        idempotencyKey: `idem_smoke_write_denied_${suffix}`,
      }),
    });
    const deniedText = await deniedResponse.text();
    assert.equal(deniedResponse.status(), 403, "workspace 上下文冲突应返回 403");
    assert(deniedText.includes("workspace_context_conflict"), "权限失败应返回稳定 workspace_context_conflict");
    assert(!deniedText.includes(title), "权限失败响应不应泄露页面标题");
    assert(!deniedText.includes(marker), "权限失败响应不应泄露正文内容");
    await assertPageAiPermissionFailureUi(page, target, suffix);

    const dryRun = await requestJson(context.request, "/api/hermes/tools/mnote/call", {
      method: "POST",
      headers: { "x-mnote-actor-id": "smoke-user" },
      data: { ...baseTool, dryRun: true, idempotencyKey: `idem_smoke_write_dry_${suffix}` },
    });
    assert.equal(dryRun.result.dryRun, true, "dryRun 不应写入");
    const dryRunContent = await requestJson(
      context.request,
      `/api/documents/content?documentId=${encodeURIComponent(target.documentId)}&workspaceId=${encodeURIComponent(target.workspaceId)}`,
      { method: "GET" },
    );
    assert(!JSON.stringify(dryRunContent).includes(marker), "dryRun 后不应读到 AI 写入标记");

    await openDocument(page, target.workspaceId, target.documentId);
    await page
      .locator('[data-testid="mnote-leptos-tiptap-island-editor-root"]')
      .waitFor({ state: "attached", timeout: UI_TIMEOUT_MS })
      .catch(() => undefined);

    const write = await requestJson(context.request, "/api/hermes/tools/mnote/call", {
      method: "POST",
      headers: { "x-mnote-actor-id": "smoke-user" },
      data: { ...baseTool, dryRun: false },
    });
    assert.equal(write.ok, true, "写入 tool call 应成功");
    assert.equal(write.audit.effect, "write", "写入 audit effect 应为 write");
    assert.equal(write.result.commandName, "page.body.save", "写入必须走 page.body.save");
    assert(write.result.commandId, "写入结果必须包含 Rust commandId");
    assert.equal(write.audit.commandId, write.result.commandId, "audit commandId 必须指向 Rust commandId");
    await page.waitForFunction((expected) => (document.body.textContent || "").includes(expected), marker, {
      timeout: UI_TIMEOUT_MS,
    });

    const retry = await requestJson(context.request, "/api/hermes/tools/mnote/call", {
      method: "POST",
      headers: { "x-mnote-actor-id": "smoke-user" },
      data: { ...baseTool, dryRun: false },
    });
    assert.equal(retry.ok, true, "幂等重试 tool call 应成功");
    assert.equal(retry.result.commandId, write.result.commandId, "同一 idempotencyKey 重试必须返回同一个 commandId");
    assert.equal(retry.audit.commandId, write.audit.commandId, "同一 idempotencyKey 重试必须返回同一个 audit commandId");

    const content = await requestJson(
      context.request,
      `/api/documents/content?documentId=${encodeURIComponent(target.documentId)}&workspaceId=${encodeURIComponent(target.workspaceId)}`,
      { method: "GET" },
    );
    assert(JSON.stringify(content).includes(marker), "刷新读取内容后未找到 AI 写入标记");

    console.log(
      JSON.stringify(
        {
          ok: true,
          baseUrl: BASE_URL,
          documentId: target.documentId,
          workspaceId: target.workspaceId,
          marker,
          commandName: write.result.commandName,
          audit: write.audit,
        },
        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);
});
