"use strict";

// 说明：
// - 这是 task-021 的最小真实浏览器回归脚本。
// - 目标覆盖 Mindmap 全屏页、节点新增/删除、保存链与 requestId/traceId 元信息同步。
// - 脚本会创建临时页面和临时导图，回归结束后清理，避免污染现有数据。

const { chromium } = require("playwright");

const BASE_URL = process.env.MNOTE_UI_BASE_URL || "http://127.0.0.1:3000";
const REQUEST_TIMEOUT_MS = 20_000;
const UI_TIMEOUT_MS = 30_000;
const TEST_LOGIN_BUTTON_NAME = "测试账号快速登录";

const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

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

async function requestJson(requestContext, path, init = {}) {
  const response = await requestContext.fetch(`${BASE_URL}${path}`, {
    ...init,
    headers:
      init.data !== undefined
        ? {
            "content-type": "application/json",
            ...(init.headers || {}),
          }
        : {
            ...(init.headers || {}),
          },
    timeout: REQUEST_TIMEOUT_MS,
  });

  const text = await response.text();
  let payload = null;
  try {
    payload = text ? JSON.parse(text) : null;
  } catch {
    payload = text;
  }

  if (!response.ok()) {
    throw new Error(
      `${path} 请求失败: ${response.status()} ${response.statusText()} ${typeof payload === "string" ? payload : JSON.stringify(payload)}`,
    );
  }

  return payload;
}

async function createTempDocument(requestContext) {
  const payload = await requestJson(requestContext, "/api/documents/create", {
    method: "POST",
    data: { parentId: null },
  });

  assert(payload && typeof payload.id === "string", "创建临时页面失败：缺少 id");
  assert(payload && typeof payload.workspace_id === "string", "创建临时页面失败：缺少 workspace_id");

  return {
    documentId: payload.id,
    workspaceId: payload.workspace_id,
  };
}

async function createTempMindmap(requestContext, documentId, mindmapId) {
  await requestJson(requestContext, `/api/mindmap/${documentId}/${mindmapId}`, {
    method: "POST",
    data: {
      createOnly: true,
      data: {
        data: { text: "中心主题" },
        children: [],
      },
    },
  });
}

async function cleanupTempMindmap(requestContext, documentId, mindmapId) {
  try {
    await requestJson(requestContext, `/api/mindmap/${documentId}/${mindmapId}`, {
      method: "DELETE",
    });
  } catch {
    // 忽略清理失败，继续尝试 purge 文档。
  }
}

async function purgeTempDocument(requestContext, documentId) {
  await requestJson(requestContext, "/api/documents/purge", {
    method: "POST",
    data: { documentId },
  });
}

async function getViewerIdentity(requestContext) {
  const payload = await requestJson(requestContext, "/api/auth/whoami", { method: "GET" });
  assert(payload && typeof payload.userId === "string" && payload.userId, "获取当前用户失败：缺少 userId");
  return payload;
}

async function ensureAuthenticated(page, requestContext) {
  await page.goto(`${BASE_URL}/auth`, { waitUntil: "networkidle", timeout: UI_TIMEOUT_MS });

  if (page.url().includes("/auth")) {
    const quickLoginButton = page.getByRole("button", { name: TEST_LOGIN_BUTTON_NAME });
    await quickLoginButton.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS });
    await quickLoginButton.click({ timeout: UI_TIMEOUT_MS });
    await page.waitForURL((url) => !url.toString().includes("/auth"), {
      timeout: UI_TIMEOUT_MS,
    });
  }

  return await getViewerIdentity(requestContext);
}

async function waitForMindmapInstance(page, mindmapId) {
  await page.waitForFunction(
    (id) => Boolean(window.__mindmapInstancesById?.[id] || window.__mindmapInstance),
    mindmapId,
    { timeout: UI_TIMEOUT_MS },
  );
}

async function waitForMindmapReady(page, mindmapId) {
  await page.waitForFunction(
    (id) => {
      const instance = window.__mindmapInstancesById?.[id] || window.__mindmapInstance;
      const persist = window.__mindmapPersistById?.[id];
      const fullscreen = document.querySelector("[data-testid=\"mindmap-fullscreen\"]");
      const requestId = fullscreen?.getAttribute("data-request-id");
      const traceId = fullscreen?.getAttribute("data-trace-id");
      return Boolean(instance && persist && requestId && traceId);
    },
    mindmapId,
    { timeout: UI_TIMEOUT_MS },
  );
}

async function readMindmapMetaAttrs(page) {
  const fullscreen = page.locator("[data-testid=\"mindmap-fullscreen\"]");
  return {
    documentId: await fullscreen.getAttribute("data-document-id"),
    pageId: await fullscreen.getAttribute("data-page-id"),
    attachmentId: await fullscreen.getAttribute("data-attachment-id"),
    mindmapId: await fullscreen.getAttribute("data-mindmap-id"),
    workspaceId: await fullscreen.getAttribute("data-workspace-id"),
    requestId: await fullscreen.getAttribute("data-request-id"),
    traceId: await fullscreen.getAttribute("data-trace-id"),
  };
}

async function waitForMetaAttrs(page, meta) {
  await page.waitForFunction(
    ({ requestId, traceId }) => {
      const el = document.querySelector("[data-testid=\"mindmap-fullscreen\"]");
      if (!el) return false;
      return (
        el.getAttribute("data-request-id") === requestId &&
        el.getAttribute("data-trace-id") === traceId
      );
    },
    {
      requestId: meta.requestId,
      traceId: meta.traceId,
    },
    { timeout: UI_TIMEOUT_MS },
  );
}

async function waitForMetaMutation(page, previousMeta) {
  await page.waitForFunction(
    ({ requestId, traceId }) => {
      const el = document.querySelector("[data-testid=\"mindmap-fullscreen\"]");
      if (!el) return false;
      const nextRequestId = el.getAttribute("data-request-id");
      const nextTraceId = el.getAttribute("data-trace-id");
      return Boolean(
        nextRequestId &&
          nextTraceId &&
          nextRequestId !== requestId &&
          nextTraceId !== traceId,
      );
    },
    {
      requestId: previousMeta.requestId,
      traceId: previousMeta.traceId,
    },
    { timeout: UI_TIMEOUT_MS },
  );
  return await readMindmapMetaAttrs(page);
}

async function waitForMindmapState(requestContext, documentId, mindmapId, check, description) {
  const deadline = Date.now() + UI_TIMEOUT_MS;
  let lastPayload = null;
  while (Date.now() < deadline) {
    lastPayload = await requestJson(requestContext, `/api/mindmap/${documentId}/${mindmapId}`);
    if (check(lastPayload)) {
      return lastPayload;
    }
    await sleep(500);
  }
  throw new Error(`${description} 超时：${JSON.stringify(lastPayload)}`);
}

function assertRouteMeta(meta, expected) {
  assert(meta && typeof meta.requestId === "string" && meta.requestId, "缺少 meta.requestId");
  assert(meta && typeof meta.traceId === "string" && meta.traceId, "缺少 meta.traceId");
  assert(meta.documentId === expected.documentId, `documentId 不一致：${meta.documentId}`);
  assert(meta.pageId === expected.documentId, `pageId 不一致：${meta.pageId}`);
  assert(meta.mindmapId === expected.mindmapId, `mindmapId 不一致：${meta.mindmapId}`);
  assert(meta.attachmentId === expected.mindmapId, `attachmentId 不一致：${meta.attachmentId}`);
  assert(meta.workspaceId === expected.workspaceId, `workspaceId 不一致：${meta.workspaceId}`);
}

async function persistInsertAndRename(page, mindmapId) {
  return page.evaluate(
    ({ currentMindmapId }) => {
      const instance =
        window.__mindmapInstancesById?.[currentMindmapId] ?? window.__mindmapInstance;
      if (!instance) {
        throw new Error("未找到 mindmap 实例");
      }

      const renderer = instance.renderer;
      const root = renderer?.root ?? renderer?.renderTree?._node;
      if (!root) {
        throw new Error("未找到根节点");
      }

      renderer?.clearActiveNodeList?.();
      renderer?.addNodeToActiveList?.(root, true);
      renderer.lastActiveNodeList = [root];
      renderer?.emitNodeActiveEvent?.(root);
      instance.execCommand?.("SET_NODE_ACTIVE", root, true);
      instance.execCommand?.("INSERT_CHILD_NODE", false, [root]);

      const snapshot = instance.getData?.(true) ?? instance.getData?.() ?? null;
      if (!snapshot?.root?.children?.[0]?.data) {
        throw new Error("插入子节点后未拿到快照");
      }

      const persist = window.__mindmapPersistById?.[currentMindmapId];
      if (!persist) {
        throw new Error("未找到 mindmap 持久化回调");
      }
      persist(snapshot);
      return {
        childText: String(snapshot.root.children[0].data.text ?? ""),
        childCount: snapshot.root.children.length,
      };
    },
    {
      currentMindmapId: mindmapId,
    },
  );
}

async function persistDeleteChild(page, mindmapId, childUid) {
  return page.evaluate(async ({ currentMindmapId, currentChildUid }) => {
    const instance =
      window.__mindmapInstancesById?.[currentMindmapId] ?? window.__mindmapInstance;
    if (!instance) {
      throw new Error("未找到 mindmap 实例");
    }
    const renderer = instance.renderer;
    const child =
      typeof renderer?.findNodeByUid === "function"
        ? renderer.findNodeByUid(currentChildUid)
        : null;
    if (!child) {
      throw new Error("删除子节点时未找到目标节点");
    }
    renderer?.clearActiveNodeList?.();
    renderer?.addNodeToActiveList?.(child, true);
    renderer.lastActiveNodeList = [child];
    renderer?.emitNodeActiveEvent?.(child);
    instance.execCommand?.("SET_NODE_ACTIVE", child, true);
    await new Promise((resolve) => window.setTimeout(resolve, 0));
    instance.execCommand?.("REMOVE_NODE");
    await new Promise((resolve) => window.setTimeout(resolve, 0));
    const snapshot = instance.getData?.(true) ?? instance.getData?.() ?? null;
    const root = snapshot?.root ?? snapshot;
    return {
      childCount:
        root && typeof root === "object" && Array.isArray(root.children)
          ? root.children.length
          : -1,
    };
  }, { currentMindmapId: mindmapId, currentChildUid: childUid });
}

async function openOutlinePanel(page) {
  const outlineButton = page.getByRole("button", { name: "大纲" });
  await outlineButton.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS });
  await outlineButton.click();
}

async function runBrowserRegression(page, requestContext, target) {
  const uniqueSuffix = Date.now().toString();
  const mindmapId = `task021-${uniqueSuffix}`;
  const defaultChildText = "二级节点";

  try {
    await createTempMindmap(requestContext, target.documentId, mindmapId);

    const mindmapUrl = `${BASE_URL}/mindmap/${target.documentId}/${mindmapId}`;
    await page.goto(mindmapUrl, { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS });

    const fullscreen = page.locator("[data-testid=\"mindmap-fullscreen\"]");
    const canvas = page.locator("[data-testid=\"mindmap-canvas\"]");
    const rootText = page.getByText("中心主题").first();

    await fullscreen.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS });
    await canvas.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS });
    await rootText.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS });
    await waitForMindmapInstance(page, mindmapId);
    await waitForMindmapReady(page, mindmapId);

    const initialMetaAttrs = await readMindmapMetaAttrs(page);
    assert(initialMetaAttrs.documentId === target.documentId, "页面 data-document-id 不正确");
    assert(initialMetaAttrs.pageId === target.documentId, "页面 data-page-id 不正确");
    assert(initialMetaAttrs.mindmapId === mindmapId, "页面 data-mindmap-id 不正确");
    assert(initialMetaAttrs.attachmentId === mindmapId, "页面 data-attachment-id 不正确");
    assert(initialMetaAttrs.workspaceId === target.workspaceId, "页面 data-workspace-id 不正确");

    const insertMutation = await persistInsertAndRename(page, mindmapId);
    assert(insertMutation.childCount === 1, `插入子节点后数量异常：${insertMutation.childCount}`);
    assert(insertMutation.childText, "插入子节点后名称为空");

    const insertSaveMeta = await waitForMetaMutation(page, initialMetaAttrs);
    assertRouteMeta(insertSaveMeta, {
      documentId: target.documentId,
      mindmapId,
      workspaceId: target.workspaceId,
    });
    await waitForMindmapState(
      requestContext,
      target.documentId,
      mindmapId,
      (payload) => Array.isArray(payload?.data?.children) && payload.data.children.length === 1,
      "插入子节点后后端回查",
    );

    await page.reload({ waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS });
    await fullscreen.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS });
    await canvas.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS });
    await waitForMindmapInstance(page, mindmapId);
    await waitForMindmapReady(page, mindmapId);
    const beforeDeleteMeta = await readMindmapMetaAttrs(page);
    const insertSavedMindmap = await requestJson(requestContext, `/api/mindmap/${target.documentId}/${mindmapId}`);
    assert(
      Array.isArray(insertSavedMindmap.data?.children) &&
        insertSavedMindmap.data.children.length === 1,
      "刷新后导图子节点数量不正确",
    );
    assert(
      typeof insertSavedMindmap.data.children[0]?.data?.text === "string" &&
        insertSavedMindmap.data.children[0].data.text.trim(),
      "刷新后导图子节点名称为空",
    );
    const persistedChildText =
      String(insertSavedMindmap.data.children[0]?.data?.text ?? "")
        .replace(/<[^>]+>/g, "")
        .trim() || defaultChildText;
    const persistedChildUid = String(insertSavedMindmap.data.children[0]?.data?.uid ?? "");
    assert(persistedChildUid, "刷新后导图子节点缺少 uid");
    await openOutlinePanel(page);
    await page.getByText(persistedChildText).first().waitFor({
      state: "visible",
      timeout: UI_TIMEOUT_MS,
    });

    const deleteMutation = await persistDeleteChild(page, mindmapId, persistedChildUid);
    assert(deleteMutation.childCount === 0, `删除子节点后数量异常：${deleteMutation.childCount}`);

    const deleteSaveMeta = await waitForMetaMutation(page, beforeDeleteMeta);
    assertRouteMeta(deleteSaveMeta, {
      documentId: target.documentId,
      mindmapId,
      workspaceId: target.workspaceId,
    });
    await waitForMindmapState(
      requestContext,
      target.documentId,
      mindmapId,
      (payload) => Array.isArray(payload?.data?.children) && payload.data.children.length === 0,
      "删除子节点后后端回查",
    );

    await page.reload({ waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS });
    await fullscreen.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS });
    await canvas.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS });
    await waitForMindmapInstance(page, mindmapId);
    await waitForMindmapReady(page, mindmapId);
    await openOutlinePanel(page);

    await page.getByText("中心主题").first().waitFor({ state: "visible", timeout: UI_TIMEOUT_MS });

    const deleteSavedMindmap = await requestJson(requestContext, `/api/mindmap/${target.documentId}/${mindmapId}`);
    assert(
      Array.isArray(deleteSavedMindmap.data?.children) &&
        deleteSavedMindmap.data.children.length === 0,
      "删除子节点后后端仍保留子节点",
    );
    const canvasText = await canvas.innerText({ timeout: UI_TIMEOUT_MS });
    assert(!canvasText.includes(persistedChildText), "删除子节点后画布仍残留旧节点文本");

    return {
      mindmapUrl,
      mindmapId,
      childText: persistedChildText,
      initialMetaAttrs,
      insertMeta: insertSaveMeta,
      deleteMeta: deleteSaveMeta,
    };
  } finally {
    await cleanupTempMindmap(requestContext, target.documentId, mindmapId);
  }
}

async function main() {
  const health = await fetch(`${BASE_URL}/`, {
    method: "HEAD",
    redirect: "manual",
    signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
  });
  assert(
    [200, 307, 308].includes(health.status),
    `首页探活失败：收到状态码 ${health.status}`,
  );

  const browser = await chromium.launch({ headless: true });
  const context = await browser.newContext({
    viewport: { width: 1440, height: 960 },
  });
  const page = await context.newPage();
  let tempDocument = null;
  let regressionResult = null;
  let caughtError = null;

  try {
    await ensureAuthenticated(page, context.request);
    tempDocument = await createTempDocument(context.request);
    regressionResult = await runBrowserRegression(page, context.request, tempDocument);
    console.log(
      JSON.stringify(
        {
          ok: true,
          workspaceId: tempDocument.workspaceId,
          documentId: tempDocument.documentId,
          ...regressionResult,
        },
        null,
        2,
      ),
    );
  } catch (error) {
    caughtError = error;
  } finally {
    if (tempDocument?.documentId) {
      try {
        await purgeTempDocument(context.request, tempDocument.documentId);
      } catch (cleanupError) {
        if (!caughtError) {
          caughtError = cleanupError;
        } else {
          console.error(
            `清理临时页面失败：${cleanupError instanceof Error ? cleanupError.stack || cleanupError.message : String(cleanupError)}`,
          );
        }
      }
    }
    await page.close().catch(() => undefined);
    await context.close().catch(() => undefined);
    await browser.close().catch(() => undefined);
  }

  if (caughtError) {
    throw caughtError;
  }
}

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