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

const assert = require("node:assert");
const { spawn } = require("node:child_process");
const net = require("node:net");
const { chromium } = require("playwright");

const UI_TIMEOUT_MS = Number(process.env.MNOTE_UI_TIMEOUT_MS || 45000);

function findFreePort() {
  return new Promise((resolve, reject) => {
    const server = net.createServer();
    server.listen(0, "127.0.0.1", () => {
      const port = server.address().port;
      server.close(() => resolve(port));
    });
    server.on("error", reject);
  });
}

async function waitForGateway(baseUrl, timeoutMs = 120000) {
  const deadline = Date.now() + timeoutMs;
  let lastError = "";
  while (Date.now() < deadline) {
    try {
      const response = await fetch(`${baseUrl}/`, {
        headers: {
          "x-mnote-actor-id": "mnote-e2e",
          "x-mnote-actor-type": "user",
        },
      });
      if (response.status === 200 || response.status === 303) return;
      lastError = `${response.status} ${await response.text().catch(() => "")}`;
    } catch (error) {
      lastError = error && error.message ? error.message : String(error);
    }
    await new Promise((resolve) => setTimeout(resolve, 500));
  }
  throw new Error(`mnote-web 未就绪: ${lastError}`);
}

function startGateway(port, devHot) {
  const env = {
    ...process.env,
    MNOTE_WEB_BIND: `127.0.0.1:${port}`,
    MNOTE_WEB_PUBLIC_BIND: `127.0.0.1:${port}`,
    MNOTE_WEB_ALLOW_DEV_FIXTURES: "1",
    MNOTE_WEB_LEGACY_NEXT_BASE_URL: "http://127.0.0.1:3100",
    MNOTE_WEB_ENABLE_LEGACY_NEXT_COMPAT: "1",
  };
  delete env.MNOTE_WEB_DEV_HOT_RELOAD;
  if (devHot) env.MNOTE_WEB_DEV_HOT_RELOAD = "1";
  const child = spawn("cargo", ["run", "-q", "-p", "mnote-web", "--bin", "mnote-web"], {
    cwd: "/mnt/Data1T/mnote/rust",
    env,
    stdio: ["ignore", "pipe", "pipe"],
  });
  let stderr = "";
  child.stderr.on("data", (chunk) => {
    stderr += chunk.toString("utf8");
  });
  child.stdout.on("data", () => {});
  return {
    child,
    stderr: () => stderr,
  };
}

async function stopGateway(gateway) {
  if (!gateway || gateway.child.killed) return;
  gateway.child.kill("SIGTERM");
  await new Promise((resolve) => setTimeout(resolve, 500));
  if (!gateway.child.killed) gateway.child.kill("SIGKILL");
}

async function verifyCase(devHot) {
  const port = await findFreePort();
  const baseUrl = `http://127.0.0.1:${port}`;
  const gateway = startGateway(port, devHot);
  try {
    await waitForGateway(baseUrl);
    const browser = await chromium.launch({ headless: true });
    const context = await browser.newContext({
      extraHTTPHeaders: {
        "x-mnote-actor-id": "mnote-e2e",
        "x-mnote-actor-type": "user",
      },
    });
    const page = await context.newPage();
    const hotReloadRequests = [];
    const networkFailures = [];
    const consoleErrors = [];
    page.on("request", (request) => {
      if (request.url().includes("/api/dev/hot-reload")) hotReloadRequests.push(request.url());
    });
    page.on("requestfailed", (request) => {
      networkFailures.push({ url: request.url(), failure: request.failure()?.errorText || "" });
    });
    page.on("console", (message) => {
      if (message.type() === "error") consoleErrors.push(message.text());
    });
    await page.goto(`${baseUrl}/`, { waitUntil: "domcontentloaded", timeout: UI_TIMEOUT_MS });
    await page.locator('body[data-mnote-shell="workspace"]').waitFor({ state: "attached", timeout: UI_TIMEOUT_MS });
    await page.waitForTimeout(1800);
    const html = await page.content();
    const shell = await page.locator("body").getAttribute("data-mnote-shell");
    const devHotAttr = await page.locator("html").getAttribute("data-mnote-dev-hot-reload").catch(() => null);
    await browser.close();
    return {
      devHot,
      baseUrl,
      shell,
      scriptHasDevHot: html.includes("sidebar-tree-runtime.js?devHot="),
      hotReloadRequestCount: hotReloadRequests.length,
      hotReloadAttr: devHotAttr || "",
      networkFailures,
      consoleErrors,
    };
  } finally {
    await stopGateway(gateway);
  }
}

async function main() {
  const normal = await verifyCase(false);
  const hot = await verifyCase(true);
  assert.equal(normal.shell, "workspace", JSON.stringify(normal));
  assert.equal(normal.scriptHasDevHot, false, JSON.stringify(normal));
  assert.equal(normal.hotReloadRequestCount, 0, JSON.stringify(normal));
  assert.equal(hot.shell, "workspace", JSON.stringify(hot));
  assert.equal(hot.scriptHasDevHot, true, JSON.stringify(hot));
  assert(hot.hotReloadRequestCount >= 1, JSON.stringify(hot));
  assert.equal(hot.hotReloadAttr, "enabled", JSON.stringify(hot));
  console.log(JSON.stringify({ ok: true, task: "task514-sidebar-dev-hot-reload-gating-smoke", normal, hot }, null, 2));
}

main().catch((error) => {
  console.error(error && error.stack ? error.stack : error);
  process.exit(1);
});
