"use strict";

// 说明：
// - 这是 task-019 的最小真实浏览器回归脚本。
// - 目标只覆盖文档页元信息、Sidebar、BlockNote 保存链，不扩大到 Mindmap / OnlyOffice。
// - 脚本会先创建一篇临时页面，完成回归后再彻底删除，避免污染现有数据。

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 = "测试账号快速登录";

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)}`,
    );
  }

  const contentType = response.headers()["content-type"] || "";
  if (!contentType.includes("application/json")) {
    const snippet = typeof payload === "string" ? payload.slice(0, 200) : JSON.stringify(payload).slice(0, 200);
    throw new Error(
      `${path} 返回了非 JSON 内容，当前回归脚本需要可直接调用的 API 会话。` +
        `如果页面被重定向到 /auth 或返回 HTML，说明前端未启用 MNOTE_DEV_AUTH=1，或当前节点没有带上有效的 Convex Auth 会话。` +
        `响应片段：${snippet}`,
    );
  }

  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 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 runBrowserRegression(page, target) {
  const uniqueSuffix = Date.now().toString();
  const nextTitle = `task019-ui-${uniqueSuffix}`;
  const nextBody = `task019 正文保存回归 ${uniqueSuffix}`;
  const documentUrl = `${BASE_URL}/documents/${target.documentId}?workspaceId=${encodeURIComponent(target.workspaceId)}`;
  await page.goto(documentUrl, { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS });

  const sidebarPanel = page.getByText("页面树");
  const privateSection = page.getByText("私有 / 我的页面");
  const titleInput = page.getByLabel("页面标题");
  const editorSurface = page.locator(".wolai-editor [contenteditable=\"true\"]").first();
  const saveIndicator = page.locator("text=已保存");

  await sidebarPanel.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS });
  await privateSection.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS });
  await titleInput.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS });
  await editorSurface.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS });
  await saveIndicator.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS });

  await titleInput.fill(nextTitle);
  const titleSaveResponse = page.waitForResponse(
    (response) =>
      response.url().includes("/api/documents/title") &&
      response.request().method() === "POST" &&
      response.status() === 200,
    { timeout: UI_TIMEOUT_MS },
  );
  await titleInput.evaluate((node) => {
    node.blur();
  });
  await titleSaveResponse;

  const saveResponse = page.waitForResponse(
    (response) =>
      response.url().includes("/api/documents/save") &&
      response.request().method() === "POST" &&
      response.status() === 200 &&
      (response.request().postData() || "").includes(nextBody),
    { timeout: UI_TIMEOUT_MS },
  );
  await editorSurface.click({ timeout: UI_TIMEOUT_MS });
  await editorSurface.fill(nextBody, { timeout: UI_TIMEOUT_MS });
  await saveResponse;
  await saveIndicator.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS });

  await page.reload({ waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS });
  await sidebarPanel.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS });
  await privateSection.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS });
  await titleInput.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS });
  await page.locator(`text=${nextBody}`).first().waitFor({ state: "visible", timeout: UI_TIMEOUT_MS });

  const persistedTitle = await titleInput.inputValue();
  assert(persistedTitle === nextTitle, `标题刷新后不一致：期望 ${nextTitle}，实际 ${persistedTitle}`);
  const editorText = await page.locator(".wolai-editor").innerText({ timeout: UI_TIMEOUT_MS });
  assert(editorText.includes(nextBody), "正文刷新后未保留刚写入的内容");

  return {
    documentUrl,
    nextTitle,
    nextBody,
  };
}

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, 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);
});
