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

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

const REQUEST_TIMEOUT_MS = Number(process.env.MNOTE_SMOKE_TIMEOUT_MS || 20_000);
const EXTERNAL_BASE_URL = (process.env.MNOTE_UI_BASE_URL || "").replace(/\/+$/, "");

function findFreePort() {
  return new Promise((resolve, reject) => {
    const server = net.createServer();
    server.listen(0, "127.0.0.1", () => {
      const address = server.address();
      if (!address || typeof address === "string") {
        server.close(() => reject(new Error("无法分配测试端口")));
        return;
      }
      const port = address.port;
      server.close(() => resolve(port));
    });
    server.on("error", reject);
  });
}

async function fetchWithTimeout(url, init = {}) {
  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(new Error(`请求超时: ${url}`)), REQUEST_TIMEOUT_MS);
  try {
    return await fetch(url, {
      redirect: "manual",
      cache: "no-store",
      ...init,
      signal: controller.signal,
    });
  } finally {
    clearTimeout(timer);
  }
}

async function waitForGateway(baseUrl) {
  const deadline = Date.now() + REQUEST_TIMEOUT_MS;
  let lastError = null;
  while (Date.now() < deadline) {
    try {
      const response = await fetchWithTimeout(`${baseUrl}/api/gateway/health`);
      if (response.ok && response.headers.get("x-mnote-web-owner") === "mnote-web") {
        return;
      }
      lastError = new Error(`gateway health 未就绪: ${response.status}`);
    } catch (error) {
      lastError = error;
    }
    await new Promise((resolve) => setTimeout(resolve, 250));
  }
  throw lastError || new Error("gateway health 未就绪");
}

function startGateway(port) {
  const env = {
    ...process.env,
    ...buildDocumentFixtureEnv(),
    MNOTE_WEB_BIND: `127.0.0.1:${port}`,
    MNOTE_WEB_PUBLIC_BIND: "127.0.0.1:3000",
    MNOTE_WEB_LEGACY_NEXT_BASE_URL: "http://127.0.0.1:3100",
    MNOTE_WEB_ENABLE_LEGACY_NEXT_COMPAT: "1",
  };
  return spawn("cargo", ["run", "-q", "-p", "mnote-web", "--bin", "mnote-web"], {
    cwd: "/mnt/Data1T/mnote/rust",
    env,
    stdio: ["ignore", "pipe", "pipe"],
  });
}

function buildDocumentFixtureEnv() {
  return {
    MNOTE_WEB_ALLOW_DEV_FIXTURES: "1",
    MNOTE_WEB_QUERY_FIXTURES_JSON: JSON.stringify({
      "documents:getMeta": {
        id: "doc_1",
        workspace_id: "ws_demo",
        title: "服务端页面",
        updated_at: "2026-04-18T09:30:00Z",
        can_edit: true,
        word_count: 42,
        character_count: 128,
        block_count: 3,
      },
      "documents:getContent": {
        content: [{ id: "block_1", type: "paragraph", content: [] }],
        revision: 7,
        conflict_detection_key: "doc_1:7",
        pageSubtree: { rootNodeId: "doc_1", outline: [] },
      },
    }),
  };
}

async function validateGateway(baseUrl) {
  const health = await fetchWithTimeout(`${baseUrl}/api/gateway/health`);
  const healthPayload = await health.json();
  assert.equal(health.status, 200, `gateway health 失败: ${health.status}`);
  assert.equal(health.headers.get("x-mnote-web-owner"), "mnote-web");
  assert.equal(healthPayload.owner, "mnote-web");
  assert.equal(healthPayload.publicEntry, "127.0.0.1:3000");

  const auth = await fetchWithTimeout(`${baseUrl}/auth`);
  const authText = await auth.text();
  assert.equal(auth.status, 200, `/auth 失败: ${auth.status}`);
  assert.equal(auth.headers.get("x-mnote-web-owner"), "mnote-web");
  assert.match(auth.headers.get("content-type") || "", /text\/html/);
  assert.equal(auth.headers.get("x-mnote-legacy-upstream"), null, "/auth 不应再代理 3100");
  assert.match(authText, /data-mnote-shell="auth"/);
  assert.match(authText, /邮箱登录/);
  assert.match(authText, /测试账号快速登录/);
  assert.doesNotMatch(authText, /隐私政策|使用\s*Google|使用\s*GitHub|第三方快捷/iu);

  const root = await fetchWithTimeout(`${baseUrl}/`);
  const rootText = await root.text();
  assert.equal(root.status, 303, `/ 未登录应跳转 /auth: ${root.status} ${rootText.slice(0, 120)}`);
  assert.equal(root.headers.get("x-mnote-web-owner"), "mnote-web");
  assert.equal(root.headers.get("location"), "/auth");

  const authedRoot = await fetchWithTimeout(`${baseUrl}/`, {
    headers: {
      "x-mnote-actor-id": "user_real",
      "x-mnote-actor-type": "user",
    },
  });
  const authedRootText = await authedRoot.text();
  assert.equal(authedRoot.status, 200, `/ 已登录入口失败: ${authedRoot.status}`);
  assert.equal(authedRoot.headers.get("x-mnote-web-owner"), "mnote-web");
  assert.match(authedRootText, /data-mnote-shell="workspace"/);
}

async function main() {
  let gateway = null;
  let baseUrl = EXTERNAL_BASE_URL;

  if (!baseUrl) {
    const port = await findFreePort();
    baseUrl = `http://127.0.0.1:${port}`;
    gateway = startGateway(port);
    await waitForGateway(baseUrl);
  }

  try {
    await validateGateway(baseUrl);
    console.log(
      JSON.stringify(
        {
          ok: true,
          baseUrl,
          owner: "mnote-web",
          publicEntry: "3000",
          legacy3104: "not-required",
        },
        null,
        2,
      ),
    );
  } finally {
    if (gateway && gateway.pid) {
      gateway.kill("SIGTERM");
      setTimeout(() => {
        if (!gateway.killed) {
          gateway.kill("SIGKILL");
        }
      }, 2000).unref();
    }
  }
}

module.exports = {
  buildDocumentFixtureEnv,
  fetchWithTimeout,
  findFreePort,
  startGateway,
  validateGateway,
  waitForGateway,
};

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