import http from "node:http";
import { randomBytes, timingSafeEqual } from "node:crypto";
import { readFile, writeFile, mkdir, stat, realpath } from "node:fs/promises";
import { existsSync } from "node:fs";
import { spawn } from "node:child_process";
import { dirname, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";
const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
const session = randomBytes(32).toString("hex");
const state = resolve(process.env.LVAULT_STATE || join(root, ".local/device"));
const cli = resolve(
  process.env.LVAULT_CLI || join(root, "target/debug/lvault"),
);
const guard = resolve(
  process.env.LVAULT_GUARD || join(root, "target/VaultGuard"),
);
const port = Number(process.env.LVAULT_SETUP_PORT || 3094);
const origin = `http://127.0.0.1:${port}`;
let connection = null,
  pending = null,
  busy = false,
  broker = null,
  brokerError = "";
function checkOrigin(api) {
  const u = new URL(api);
  if (
    (u.protocol !== "https:" &&
      !(
        u.protocol === "http:" &&
        ["127.0.0.1", "localhost"].includes(u.hostname)
      )) ||
    u.username ||
    u.password ||
    u.search ||
    u.hash ||
    u.pathname !== "/"
  )
    throw Error("请输入 HTTPS 云端地址（本机预览允许 loopback HTTP）");
  return u.origin;
}
function authorized(value) {
  const a = Buffer.from(value || ""),
    b = Buffer.from(`Bearer ${session}`);
  return a.length === b.length && timingSafeEqual(a, b);
}
async function config() {
  try {
    return JSON.parse(await readFile(join(state, "config.json"), "utf8"));
  } catch (e) {
    if (e.code === "ENOENT") return null;
    throw e;
  }
}
async function cloud(path, body) {
  if (!connection) throw Error("请先连接云端");
  return provision({ op: "overview", ...connection });
}
function networkEnvironment() {
  const result = { PATH: process.env.PATH || "/usr/bin:/bin" };
  for (const key of [
    "HTTPS_PROXY",
    "HTTP_PROXY",
    "ALL_PROXY",
    "NO_PROXY",
    "https_proxy",
    "http_proxy",
    "all_proxy",
    "no_proxy",
  ])
    if (process.env[key]) result[key] = process.env[key];
  return result;
}
function child(args, input, cwd = root) {
  return new Promise((resolve, reject) => {
    const p = spawn(cli, ["--state", state, ...args], {
      cwd,
      stdio: ["pipe", "pipe", "pipe"],
      env: networkEnvironment(),
    });
    let out = "",
      err = "",
      size = 0;
    const timer = setTimeout(() => p.kill("SIGTERM"), 480000);
    p.stdout.on("data", (d) => {
      size += d.length;
      if (size > 1048576) p.kill("SIGTERM");
      else out += d;
    });
    p.stderr.on("data", (d) => {
      if (err.length < 4096) err += d;
    });
    p.on("error", (e) => {
      clearTimeout(timer);
      reject(e);
    });
    p.on("close", (code) => {
      clearTimeout(timer);
      if (code !== 0) reject(Error(err.trim() || `本机操作退出 ${code}`));
      else resolve(out);
    });
    p.stdin.on("error", () => {});
    p.stdin.end(input ? JSON.stringify(input) : undefined);
  });
}
async function provision(input) {
  return JSON.parse(await child(["setup"], input));
}
async function startBroker() {
  if (broker) return;
  if (!(await config())) return;
  brokerError = "";
  broker = spawn(cli, ["--state", state, "broker"], {
    cwd: root,
    stdio: ["ignore", "ignore", "pipe"],
    env: networkEnvironment(),
  });
  const p = broker;
  p.stderr.on("data", (d) => {
    brokerError = (brokerError + d.toString()).slice(-1000);
  });
  p.on("error", () => {
    brokerError = "本机 Broker 无法启动";
    if (broker === p) broker = null;
  });
  p.on("close", () => {
    if (broker === p) broker = null;
  });
}
const quote = (value) => "'" + value.replaceAll("'", "'\\''") + "'";
export function integrationText(cli, state, p) {
  return `\n<!-- LiWei Vault: ${p.id} -->\n当用户要求部署此项目时，使用已登记的 LiWei Vault 操作，等待 macOS 本人确认：\n\n\`\`\`sh\ncd ${quote(p.workspace)} && ${quote(cli)} --state ${quote(state)} run ${quote(p.id)}\n\`\`\`\n\n不得读取或索取凭据明文，不得绕开 Broker 直接部署。若 Broker 不在线，请打开本机 LiWei Vault 启动器。以真实退出状态和 auditSynced 报告结果。\n<!-- /LiWei Vault: ${p.id} -->\n`;
}
async function route(path, b) {
  if (path === "/api/reopen") {
    if (!process.env.LVAULT_TEST_SESSION_FILE)
      spawn("/usr/bin/open", [`${origin}/#session=${session}`], {
        stdio: "ignore",
      });
    return { opened: true };
  }
  if (path === "/api/status") {
    const c = await config();
    const bootstrapPath = join(root, ".local/deployment.json");
    let deployment = existsSync(join(root, "apps/setup/defaults.json"))
      ? JSON.parse(
          await readFile(join(root, "apps/setup/defaults.json"), "utf8"),
        )
      : null;
    if (existsSync(bootstrapPath))
      deployment = JSON.parse(await readFile(bootstrapPath, "utf8"));
    const defaults = {
      node: await realpath(process.execPath),
      wrangler: existsSync(join(root, "node_modules/wrangler/bin/wrangler.js"))
        ? await realpath(join(root, "node_modules/wrangler/bin/wrangler.js"))
        : "",
      api: c?.api || deployment?.url || deployment?.api || "",
    };
    return {
      device: c ? { id: c.deviceId, api: c.api } : null,
      connected: !!connection,
      broker: !!broker,
      brokerError: broker ? "" : brokerError,
      defaults,
      bootstrapAvailable: !!deployment?.adminTokenFile,
    };
  }
  if (path === "/api/connect") {
    const c = await config();
    let api = b.api,
      adminToken = b.adminToken;
    if (b.bootstrap === true) {
      const d = JSON.parse(
        await readFile(join(root, ".local/deployment.json"), "utf8"),
      );
      api = d.url;
      // The bootstrap path is written by the deployment helper, never by a web request.
      const m = await stat(d.adminTokenFile);
      if (m.mode & 0o077) throw Error("管理令牌文件权限必须为 0600");
      adminToken = (await readFile(d.adminTokenFile, "utf8")).trim();
    }
    api = checkOrigin(api);
    if (c && c.api.replace(/\/$/, "") !== api)
      throw Error("此 Mac 已绑定另一服务，请使用独立设备目录");
    if (typeof adminToken !== "string" || adminToken.length < 32)
      throw Error("管理令牌至少 32 个字符");
    const previous = connection;
    connection = { api, adminToken };
    try {
      const data = await cloud("overview");
      return data;
    } catch (e) {
      connection = previous;
      throw e;
    }
  }
  if (path === "/api/disconnect") {
    connection = null;
    pending = null;
    return { disconnected: true };
  }
  if (path === "/api/overview") return cloud("overview");
  if (path === "/api/enroll") {
    if (!connection) throw Error("请先连接云端");
    const result = await provision({
      op: "enroll",
      ...connection,
      deviceId: b.deviceId,
      guard,
    });
    await startBroker();
    return result;
  }
  if (path === "/api/secret") {
    if (!connection) throw Error("请先连接云端");
    return provision({
      op: "secret",
      id: b.id,
      adminToken: connection.adminToken,
    });
  }
  if (path === "/api/choose") {
    if (!["workspace", "config"].includes(b.kind)) throw Error("无效选择");
    return provision({ op: `choose-${b.kind}` });
  }
  if (path === "/api/plan") {
    pending = null;
    const plan = await provision({
      op: "plan",
      id: b.id,
      workspace: b.workspace,
      node: b.node,
      wrangler: b.wrangler,
      configFile: b.configFile,
      secretId: b.secretId,
    });
    pending = plan;
    return plan;
  }
  if (path === "/api/save") {
    if (!pending || !connection) throw Error("请先检查部署配置");
    const plan = pending;
    pending = null;
    const saved = await provision({
      op: "save",
      policy: plan,
      adminToken: connection.adminToken,
    });
    return { ...saved, instructions: integrationText(cli, state, plan) };
  }
  if (path === "/api/integration") {
    const data = await cloud("overview");
    const cap = data.capabilities.find((c) => c.id === b.id && c.enabled);
    const c = await config();
    if (!cap || cap.device_id !== c?.deviceId)
      throw Error("操作未启用或不属于此 Mac");
    const p = JSON.parse(cap.policy);
    const target = join(p.workspace, "AGENTS.md");
    if (existsSync(target) && (await realpath(target)) !== target)
      throw Error("AGENTS.md 为符号链接，拒绝修改");
    const block = integrationText(cli, state, p);
    const old = existsSync(target) ? await readFile(target, "utf8") : "";
    if (old.includes(`<!-- LiWei Vault: ${p.id} -->`)) {
      if (old.includes(block)) return { path: target, existing: true };
      throw Error("已有同名接入段落，请人工核对后更新");
    }
    await writeFile(target, block, {
      flag: existsSync(target) ? "a" : "wx",
    }).catch((e) => {
      throw e;
    });
    return { path: target };
  }
  if (path === "/api/open-console") {
    if (!connection) throw Error("请先连接云端");
    // Open via stdin to osascript, never put the admin token in a process argument.
    const p = spawn("/usr/bin/osascript", [], {
      stdio: ["pipe", "ignore", "ignore"],
    });
    const url =
      connection.api + "/#admin=" + encodeURIComponent(connection.adminToken);
    p.stdin.end("open location " + JSON.stringify(url) + "\n");
    return { opened: true };
  }
  throw Error("未知本机操作");
}
const headers = {
  "cache-control": "no-store",
  "x-content-type-options": "nosniff",
  "referrer-policy": "no-referrer",
  "content-security-policy":
    "default-src 'self'; script-src 'self'; style-src 'self'; connect-src 'self'; frame-ancestors 'none'; base-uri 'none'; form-action 'self'",
};
export function createSetupServer() {
  return http.createServer(async (req, res) => {
    const reply = (status, data) => {
      res.writeHead(status, { ...headers, "content-type": "application/json" });
      res.end(JSON.stringify(data));
    };
    if (
      req.headers.host !== `127.0.0.1:${port}` ||
      (req.headers.origin && req.headers.origin !== origin)
    )
      return reply(403, { error: "origin_denied" });
    const path = new URL(req.url, origin).pathname;
    if (req.method === "GET" && !path.startsWith("/api/")) {
      const asset = {
        "/": "index.html",
        "/setup.js": "setup.js",
        "/style.css": "../console/style.css",
      }[path];
      if (!asset) return reply(404, { error: "not_found" });
      try {
        res.writeHead(200, {
          ...headers,
          "content-type": path.endsWith(".js")
            ? "text/javascript"
            : path.endsWith(".css")
              ? "text/css"
              : "text/html",
        });
        res.end(await readFile(join(root, "apps/setup", asset)));
      } catch {
        reply(500, { error: "asset_unavailable" });
      }
      return;
    }
    if (req.method !== "POST" || !authorized(req.headers.authorization))
      return reply(401, {
        error: "请双击本机 LiWei Vault 启动器，从它打开的页面配置",
      });
    if (req.headers["content-type"] !== "application/json")
      return reply(415, { error: "json_required" });
    if (busy)
      return reply(409, { error: "本机操作进行中，请完成系统窗口后再试" });
    busy = true;
    try {
      const chunks = [];
      let n = 0;
      for await (const chunk of req) {
        n += chunk.length;
        if (n > 65536) {
          reply(413, { error: "body_too_large" });
          return;
        }
        chunks.push(chunk);
      }
      const b = JSON.parse(Buffer.concat(chunks).toString("utf8") || "{}");
      reply(200, await route(path, b));
    } catch (e) {
      reply(400, { error: e.message });
    } finally {
      busy = false;
    }
  });
}
if (
  process.argv[1] &&
  resolve(process.argv[1]) === fileURLToPath(import.meta.url)
) {
  await mkdir(state, { recursive: true, mode: 0o700 });
  const sessionFile = join(state, "setup-session.json");
  if (existsSync(sessionFile)) {
    const m = await stat(sessionFile);
    if (m.mode & 0o077 || m.uid !== process.getuid())
      throw Error("unsafe setup session permissions");
    const previous = JSON.parse(await readFile(sessionFile, "utf8"));
    if (previous.port === port) {
      const reopened = await new Promise((resolve) => {
        const req = http.request(
          origin + "/api/reopen",
          {
            method: "POST",
            headers: {
              authorization: `Bearer ${previous.session}`,
              "content-type": "application/json",
            },
            timeout: 2000,
          },
          (res) => {
            res.resume();
            resolve(res.statusCode === 200);
          },
        );
        req.on("error", () => resolve(false));
        req.on("timeout", () => {
          req.destroy();
          resolve(false);
        });
        req.end("{}");
      });
      if (reopened) {
        console.log("已重新打开正在运行的 LiWei Vault 向导。");
        process.exit(0);
      }
    }
  }
  const server = createSetupServer();
  server.requestTimeout = 30000;
  server.headersTimeout = 10000;
  server.on("error", (e) => {
    console.error(
      e.code === "EADDRINUSE"
        ? "LiWei Vault 向导已经运行，请返回已打开的页面。"
        : e.message,
    );
    process.exitCode = 1;
  });
  server.listen(port, "127.0.0.1", async () => {
    await writeFile(sessionFile, JSON.stringify({ port, session }), {
      mode: 0o600,
    });
    await startBroker();
    console.log(`LiWei Vault 本机向导已启动（${origin}）。保持此窗口开启。`);
    const url = `${origin}/#session=${session}`;
    if (process.env.LVAULT_TEST_SESSION_FILE)
      await writeFile(process.env.LVAULT_TEST_SESSION_FILE, session, {
        mode: 0o600,
        flag: "wx",
      });
    else spawn("/usr/bin/open", [url], { stdio: "ignore" });
  });
  for (const signal of ["SIGINT", "SIGTERM"])
    process.on(signal, () => {
      broker?.kill("SIGTERM");
      server.close();
      process.exit(0);
    });
}
