"use strict";

// 说明：
// - 验证 Rust 3000 主入口的 Convex 附件行打开 OnlyOffice 链路。
// - 脚本会创建临时页面、上传临时 docx、点击文件树附件行并断言新窗口打开 /onlyoffice。
// - 结束后清理临时页面，避免污染长期测试空间。

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

const DOCX_MIME = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
const ONLYOFFICE_READY_TIMEOUT_MS = Number(process.env.MNOTE_ONLYOFFICE_READY_TIMEOUT_MS || 120_000);
const PROBE_DOCX_PATH =
  process.env.MNOTE_ONLYOFFICE_PROBE_DOCX ||
  "/tmp/mnote-onlyoffice-manual/mnote-onlyoffice-upload-test.docx";
const TEST_EMAIL = "mnote.e2e@example.com";
const TEST_PASSWORD = "MnoteE2E123!";
let authCookieHeader = "";
const popupConsoleMessages = [];
const popupNetworkFailures = [];

function parseSetCookie(setCookie, origin) {
  const [nameValue] = String(setCookie || "").split(";");
  const separator = nameValue.indexOf("=");
  if (separator <= 0) return null;
  return {
    name: nameValue.slice(0, separator).trim(),
    value: nameValue.slice(separator + 1).trim(),
    domain: new URL(origin).hostname,
    path: "/",
    httpOnly: /;\s*httponly\b/i.test(setCookie),
    sameSite: "Lax",
  };
}

async function forceTestAccountLogin(context) {
  const response = await context.request.fetch(`${BASE_URL}/api/auth`, {
    method: "POST",
    data: {
      action: "auth:signIn",
      args: {
        provider: "password",
        params: {
          email: TEST_EMAIL,
          password: TEST_PASSWORD,
          flow: "signIn",
        },
      },
    },
    headers: { "content-type": "application/json" },
    timeout: UI_TIMEOUT_MS,
  });
  const payload = await response.json().catch(async () => ({ raw: await response.text() }));
  assert(response.ok(), `测试账号登录失败：${response.status()} ${JSON.stringify(payload)}`);

  const cookies = response
    .headersArray()
    .filter((header) => header.name.toLowerCase() === "set-cookie")
    .map((header) => parseSetCookie(header.value, BASE_URL))
    .filter(Boolean);
  assert(cookies.length > 0, "测试账号登录响应缺少 set-cookie");
  await context.addCookies(cookies);
  authCookieHeader = cookies.map((cookie) => `${cookie.name}=${cookie.value}`).join("; ");
}

async function uploadProbeDocx(requestContext, target) {
  assert(fs.existsSync(PROBE_DOCX_PATH), `缺少探测文件：${PROBE_DOCX_PATH}`);
  const buffer = fs.readFileSync(PROBE_DOCX_PATH);
  const response = await requestContext.fetch(`${BASE_URL}/api/media/upload`, {
    method: "POST",
    headers: authCookieHeader ? { cookie: authCookieHeader } : undefined,
    multipart: {
      file: {
        name: "task174-onlyoffice-attachment.docx",
        mimeType: DOCX_MIME,
        buffer,
      },
      workspaceId: target.workspaceId,
      documentId: target.documentId,
    },
    timeout: UI_TIMEOUT_MS,
  });
  const payload = await response.json().catch(async () => ({ raw: await response.text() }));
  assert(
    response.ok(),
    `/api/media/upload 请求失败：${response.status()} ${JSON.stringify(payload)}`,
  );
  assert(
    payload && payload.asset && typeof payload.asset.id === "string",
    `上传结果缺少 asset.id：${JSON.stringify(payload)}`,
  );
  return payload.asset;
}

async function waitForAssetRow(page, assetId) {
  const selector = `[data-testid="filetree-asset-row"][data-asset-id="${assetId}"]`;
  const row = page.locator(selector).first();
  await row.waitFor({ state: "visible", timeout: UI_TIMEOUT_MS });
  return row;
}

async function waitForOnlyOfficeReady(page) {
  page.on("console", (message) => {
    popupConsoleMessages.push({
      type: message.type(),
      text: message.text().slice(0, 1000),
    });
  });
  page.on("pageerror", (error) => {
    popupConsoleMessages.push({
      type: "pageerror",
      text: error.message.slice(0, 1000),
    });
  });
  page.on("response", (response) => {
    if (response.status() >= 400) {
      popupNetworkFailures.push({
        status: response.status(),
        url: response.url().slice(0, 1000),
      });
    }
  });
  try {
    await page.waitForFunction(
      () => window.__MNOTE_ONLYOFFICE_READY__ === true,
      undefined,
      { timeout: ONLYOFFICE_READY_TIMEOUT_MS },
    );
    await page.waitForFunction(
      () => {
        const root = document.getElementById("onlyoffice-frame");
        if (root && root.querySelector("iframe,canvas")) return true;
        return Boolean(document.querySelector("iframe,canvas"));
      },
      undefined,
      { timeout: ONLYOFFICE_READY_TIMEOUT_MS },
    );
  } catch (error) {
    const debug = await page.evaluate(() => ({
      url: window.location.href,
      title: document.title,
      readyState: document.readyState,
      ready: window.__MNOTE_ONLYOFFICE_READY__ === true,
      debug: window.__MNOTE_ONLYOFFICE_DEBUG__ || null,
      errors: window.__MNOTE_ONLYOFFICE_ERRLOG__ || [],
      hasFrameRoot: Boolean(document.getElementById("onlyoffice-frame")),
      bodyText: document.body ? document.body.innerText.slice(0, 800) : "",
      html: document.body ? document.body.innerHTML.slice(0, 1600) : "",
      scriptCount: document.scripts.length,
      frameCount: document.querySelectorAll("iframe,canvas").length,
      hasFrameRoot: Boolean(document.getElementById("onlyoffice-frame")),
    })).catch((debugError) => ({
      evaluateError: debugError instanceof Error ? debugError.message : String(debugError),
      isClosed: page.isClosed(),
    }));
    debug.consoleMessages = popupConsoleMessages.slice(-20);
    debug.networkFailures = popupNetworkFailures.slice(-30);
    throw new Error(
      `OnlyOffice ready 超时：${error instanceof Error ? error.message : String(error)} ${JSON.stringify(debug)}`,
    );
  }
}

async function readOnlyOfficeDebug(page) {
  return await page.evaluate(() => {
    const debug = window.__MNOTE_ONLYOFFICE_DEBUG__ || null;
    return {
      debug,
      docKey: debug && typeof debug.docKey === "string" ? debug.docKey : "",
      ready: window.__MNOTE_ONLYOFFICE_READY__ === true,
      frameCount: document.querySelectorAll("iframe,canvas").length,
    };
  });
}

async function main() {
  const browser = await chromium.launch({ headless: process.env.HEADFUL !== "1" });
  const context = await browser.newContext();
  const page = await context.newPage();
  let target = null;
  try {
    await forceTestAccountLogin(context);
    await ensureAuthenticated(page, context.request);
    target = await createTempDocument(context.request, null);
    const asset = await uploadProbeDocx(context.request, target);

    await openDocument(page, target.workspaceId, target.documentId);
    await openFilesystemView(page);
    const row = await waitForAssetRow(page, asset.id);

    const [popup] = await Promise.all([
      page.waitForEvent("popup", { timeout: UI_TIMEOUT_MS }),
      row.locator('[data-rust-action="open"]').first().click({ timeout: UI_TIMEOUT_MS }),
    ]);
    await popup.waitForLoadState("domcontentloaded", { timeout: UI_TIMEOUT_MS }).catch(() => undefined);

    const opened = new URL(popup.url());
    assert(opened.pathname === "/onlyoffice", `附件应在新窗口打开 /onlyoffice，实际为：${popup.url()}`);
    assert(opened.searchParams.get("assetId") === asset.id, "OnlyOffice URL 缺少正确 assetId");
    assert(
      opened.searchParams.get("documentId") === target.documentId,
      "OnlyOffice URL 缺少正确 documentId",
    );
    assert(opened.searchParams.get("fileType") === "docx", "OnlyOffice URL fileType 应为 docx");
    assert(opened.searchParams.get("mode") === "edit", "OnlyOffice URL mode 应为 edit");
    await waitForOnlyOfficeReady(popup);
    const firstDebug = await readOnlyOfficeDebug(popup);
    assert(firstDebug.ready, "OnlyOffice ready flag 应为 true");
    assert(firstDebug.frameCount > 0, "OnlyOffice 应创建 iframe/canvas");
    assert(
      /^[0-9A-Za-z_.=-]{1,128}$/.test(firstDebug.docKey),
      `OnlyOffice document.key 不符合安全字符集或长度：${firstDebug.docKey}`,
    );
    assert(
      firstDebug.docKey === asset.id || firstDebug.docKey.startsWith(`${asset.id}_`),
      `OnlyOffice document.key 应绑定 assetId，实际为：${firstDebug.docKey}`,
    );

    const reopened = await context.newPage();
    await reopened.goto(popup.url(), { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS });
    await waitForOnlyOfficeReady(reopened);
    const secondDebug = await readOnlyOfficeDebug(reopened);
    assert(
      secondDebug.docKey === firstDebug.docKey,
      `同一附件重复打开 document.key 应稳定：first=${firstDebug.docKey} second=${secondDebug.docKey}`,
    );
    await reopened.close().catch(() => undefined);

    console.log(
      JSON.stringify(
        {
          ok: true,
          documentId: target.documentId,
          workspaceId: target.workspaceId,
          assetId: asset.id,
          openedUrl: popup.url(),
          docKey: firstDebug.docKey,
        },
        null,
        2,
      ),
    );
  } finally {
    if (target && target.documentId) {
      await purgeDocument(context.request, target.documentId).catch((error) => {
        console.warn(`清理临时页面失败：${error instanceof Error ? error.message : String(error)}`);
      });
    }
    await context.close().catch(() => undefined);
    await browser.close().catch(() => undefined);
  }
}

main().catch((error) => {
  console.error(error);
  process.exit(1);
});
