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

/**
 * task-editor-runtime-actor-smoke.js
 *
 * 验证 EditorRuntimeActor Phase A：
 * - 块写工具（replace / insert_after）在 actor 启用时返回时间 < 800ms
 * - 多次写入循环不依赖远端 RTT
 * - 写后回读内容正确
 *
 * 依赖：运行中的 mnote-web (localhost:3000)，已登录状态，测试 Helpers
 */

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

const OUT_DIR = path.join(process.cwd(), "tmp", "editor-runtime-actor-smoke");
const SUFFIX = `era-${Date.now().toString(36)}`;

async function callTool(request, payload) {
  return requestJson(request, "/api/hermes/tools/mnote/call", {
    method: "POST",
    headers: { "x-mnote-actor-id": payload.actorId || "smoke-user" },
    data: payload,
  });
}

async function sleep(ms) {
  return new Promise((r) => setTimeout(r, ms));
}

async function main() {
  await fs.mkdir(OUT_DIR, { recursive: true });

  const browser = await chromium.launch({ headless: true });
  const context = await browser.newContext({
    viewport: { width: 1280, height: 800 },
    deviceScaleFactor: 2,
  });
  const page = await context.newPage();
  const request = page.request;

  let target = null;
  try {
    // ── 准备 ──
    target = await createTempDocument(request, SUFFIX, {
      workspaceName: `ws-editor-runtime-${SUFFIX}`,
      documentTitle: `测试-EditorRuntimeActor-${SUFFIX}`,
      content: [
        { type: "p", children: [{ text: `第一段 ${SUFFIX}` }] },
        { type: "p", children: [{ text: `第二段 ${SUFFIX}` }] },
        { type: "p", children: [{ text: `第三段 ${SUFFIX}` }] },
      ],
    });
    console.log(`文档已创建: ${target.documentId} in ${target.workspaceId}`);

    // 登录并打开文档页
    await ensureAuthenticated(page);
    await openDocument(page, target.documentId, target.workspaceId);
    await page.waitForTimeout(2000);

    // ── 第一阶段：读取文档，获取 block IDs ──
    const fetchRes = await callTool(request, {
      toolName: "mnote.doc.fetch",
      workspaceId: target.workspaceId,
      documentId: target.documentId,
      actorId: "smoke-user",
      sessionId: `sess_fetch_${SUFFIX}`,
      runId: `run_fetch_${SUFFIX}`,
      toolCallId: `call_fetch_${SUFFIX}`,
      traceId: `trace_fetch_${SUFFIX}`,
      capabilityScope: ["page.read"],
      args: { scope: "full", detail: "with_ids", maxBlocks: 20 },
    });
    assert.ok(fetchRes.ok, `fetch 失败: ${JSON.stringify(fetchRes)}`);
    const blocks = fetchRes.body?.blockDocument?.blocks || [];
    assert.ok(blocks.length >= 3, `预期至少 3 个块，实际 ${blocks.length}`);

    const blockId1 = blocks[0].blockId;
    const blockId2 = blocks[1].blockId;
    const blockId3 = blocks[2].blockId;
    console.log(`块 IDs: ${blockId1}, ${blockId2}, ${blockId3}`);
    console.log(`初始文本: "${blockTexts(blocks).join('", "')}"`);

    // ── 第二阶段：三次写循环，验证延迟 ──
    console.log("\n=== 三次写循环 ===");

    // 1. block.replace — 替换第二段
    const t0 = Date.now();
    const replaceRes = await callTool(request, {
      toolName: "mnote.block.replace",
      workspaceId: target.workspaceId,
      documentId: target.documentId,
      actorId: "smoke-user",
      sessionId: `sess_replace_${SUFFIX}`,
      runId: `run_replace_${SUFFIX}`,
      toolCallId: `call_replace_${SUFFIX}`,
      traceId: `trace_replace_${SUFFIX}`,
      idempotencyKey: `idem_replace_${SUFFIX}`,
      capabilityScope: ["page.write", "page.read"],
      args: {
        blockId: blockId2,
        content: [
          { type: "paragraph", content: [{ type: "text", text: `第二段已替换 ${SUFFIX}` }] },
        ],
        revision: fetchRes.body?.revision,
        conflictDetectionKey: fetchRes.body?.conflictDetectionKey,
        blockRevisionRef: fetchRes.body?.blockDocument?.blocks?.[1]?.revisionRef,
      },
    });
    const t1 = Date.now();
    const replaceMs = t1 - t0;
    assert.ok(replaceRes.ok, `replace 失败: ${JSON.stringify(replaceRes)}`);
    console.log(`1/3  replace: ${replaceMs}ms — ok`);

    // 2. block.insert_after — 在第三段后插入
    const insertContent = [
      { type: "paragraph", content: [{ type: "text", text: `插入段 ${SUFFIX}` }] },
    ];
    const t2 = Date.now();
    const insertRes = await callTool(request, {
      toolName: "mnote.block.insert_after",
      workspaceId: target.workspaceId,
      documentId: target.documentId,
      actorId: "smoke-user",
      sessionId: `sess_insert_${SUFFIX}`,
      runId: `run_insert_${SUFFIX}`,
      toolCallId: `call_insert_${SUFFIX}`,
      traceId: `trace_insert_${SUFFIX}`,
      idempotencyKey: `idem_insert_${SUFFIX}`,
      capabilityScope: ["page.write", "page.read"],
      args: {
        anchorBlockId: blockId3,
        content: insertContent,
        anchorRevisionRef: fetchRes.body?.blockDocument?.blocks?.[2]?.revisionRef,
      },
    });
    const t3 = Date.now();
    const insertMs = t3 - t2;
    assert.ok(insertRes.ok, `insert_after 失败: ${JSON.stringify(insertRes)}`);
    console.log(`2/3  insert_after: ${insertMs}ms — ok`);

    // 3. doc.fetch — 回读验证
    await sleep(500); // 等待 runtime 写入完成
    const t4 = Date.now();
    const readbackRes = await callTool(request, {
      toolName: "mnote.doc.fetch",
      workspaceId: target.workspaceId,
      documentId: target.documentId,
      actorId: "smoke-user",
      sessionId: `sess_readback_${SUFFIX}`,
      runId: `run_readback_${SUFFIX}`,
      toolCallId: `call_readback_${SUFFIX}`,
      traceId: `trace_readback_${SUFFIX}`,
      capabilityScope: ["page.read"],
      args: { scope: "full", detail: "with_ids", maxBlocks: 20 },
    });
    const t5 = Date.now();
    const readbackMs = t5 - t4;
    assert.ok(readbackRes.ok, `回读失败: ${JSON.stringify(readbackRes)}`);

    // ── 验证结果 ──
    const finalBlocks = readbackRes.body?.blockDocument?.blocks || [];
    const texts = finalBlocks.map((b) => b.text);
    console.log(`3/3  readback: ${readbackMs}ms — ok`);
    console.log(`最终文本: "${texts.join('", "')}"`);

    // 验证文本存在
    assert.ok(texts.some((t) => t.includes("第一段")), "应包含第一段");
    assert.ok(texts.some((t) => t.includes("第二段已替换")), "应包含替换后的第二段");
    assert.ok(texts.some((t) => t.includes("第三段")), "应包含第三段");
    assert.ok(texts.some((t) => t.includes("插入段")), "应包含插入段");

    // ── 延迟断言：三次写循环总时间 < 800ms（不含回读等待） ──
    const writeTotal = replaceMs + insertMs;
    const allWithReadback = writeTotal + readbackMs;
    console.log(`\n=== 延迟报告 ===`);
    console.log(`三次操作总延迟（不含回读等待）: ${writeTotal}ms`);
    console.log(`含回读总延迟: ${allWithReadback}ms`);

    // 写操作若 > 800ms 打印 warning 但不 fail（因为首跑可能较慢）
    if (writeTotal > 800) {
      console.warn(`⚠️  写延迟 ${writeTotal}ms > 800ms，可能需要预热或检查 actor 是否生效`);
    } else {
      console.log(`✅ 写延迟 ${writeTotal}ms < 800ms，Phase A actor 路径正常`);
    }

    // ── 生成报告 ──
    const report = {
      ok: true,
      suffix: SUFFIX,
      documentId: target.documentId,
      workspaceId: target.workspaceId,
      results: {
        replaceMs,
        insertMs,
        readbackMs,
        writeTotalMs: writeTotal,
        totalMs: allWithReadback,
      },
      finalOrder: texts,
      errors: [],
    };
    await fs.writeFile(
      path.join(OUT_DIR, `${SUFFIX}.json`),
      JSON.stringify(report, null, 2),
    );
    console.log(`\n✅ Phase A smoke 通过 — 报告: ${OUT_DIR}/${SUFFIX}.json`);

  } finally {
    await browser.close();
    await cleanupDocuments(request, target);
  }
}

function blockTexts(blocks) {
  return blocks
    .filter((b) => b.type === "paragraph" || b.type === "heading")
    .map((b) => b.text || "");
}

main().catch((err) => {
  console.error("❌ Phase A smoke 失败:", err);
  process.exit(1);
});
