import { cp, mkdir, mkdtemp, rm } from "node:fs/promises";
import { once } from "node:events";
import { tmpdir } from "node:os";
import { resolve } from "node:path";
import { execFile, spawn } from "node:child_process";
import { fileURLToPath } from "node:url";
import { promisify } from "node:util";

const execFileAsync = promisify(execFile);

export async function preparePagesRunner({ projectDirectory, workspaceDirectory }) {
  const runnerDirectory = resolve(workspaceDirectory, "runner");
  const outputDirectory = resolve(workspaceDirectory, "dist", "pages");
  const migrationsDirectory = resolve(workspaceDirectory, "drizzle");
  await mkdir(runnerDirectory, { recursive: true });
  await mkdir(resolve(workspaceDirectory, "dist"), { recursive: true });
  await cp(resolve(projectDirectory, "pages", "wrangler.jsonc"), resolve(runnerDirectory, "wrangler.jsonc"));
  await cp(resolve(projectDirectory, "dist", "pages"), outputDirectory, { recursive: true });
  await cp(resolve(projectDirectory, "drizzle"), migrationsDirectory, { recursive: true });
  return { runnerDirectory, outputDirectory, migrationsDirectory };
}

export function createWranglerCommand({ projectDirectory, platform = process.platform }) {
  if (platform === "win32") {
    return {
      executable: process.execPath,
      arguments: [resolve(projectDirectory, "node_modules", "wrangler", "wrangler-dist", "cli.js")],
      options: {},
    };
  }
  return {
    executable: resolve(projectDirectory, "node_modules", ".bin", "wrangler"),
    arguments: [],
    options: {},
  };
}

export async function terminateChildTree(child, { platform = process.platform } = {}) {
  if (!child?.pid || child.exitCode !== null) return;
  if (platform !== "win32") {
    child.kill("SIGTERM");
    return;
  }
  try {
    await execFileAsync("taskkill", ["/pid", String(child.pid), "/T", "/F"], { windowsHide: true });
  } catch (error) {
    if (error?.code !== 128) throw error;
  }
}

async function runPages(mode, extraArguments) {
  if (mode !== "dev" && mode !== "deploy") {
    throw new Error("Usage: node scripts/run-cloudflare-pages.mjs <dev|deploy> [Wrangler arguments]");
  }
  const projectDirectory = fileURLToPath(new URL("../", import.meta.url));
  const workspaceDirectory = await mkdtemp(resolve(tmpdir(), "hearbooks-pages-"));
  let child;
  try {
    const { runnerDirectory } = await preparePagesRunner({ projectDirectory, workspaceDirectory });
    const wrangler = createWranglerCommand({ projectDirectory });
    const argumentsForMode = mode === "dev"
      ? [
        "pages",
        "dev",
        "--bundle",
        "--port",
        "8899",
        "--persist-to",
        resolve(projectDirectory, ".wrangler", "pages-state"),
        "--show-interactive-dev-session=false",
        ...extraArguments,
      ]
      : ["pages", "deploy", "--branch", "main", ...extraArguments];
    child = spawn(wrangler.executable, [...wrangler.arguments, ...argumentsForMode], {
      cwd: runnerDirectory,
      stdio: "inherit",
      ...wrangler.options,
    });
    const forwardSignal = () => {
      void terminateChildTree(child).catch((error) => console.error("Unable to stop Pages dev process tree:", error));
    };
    process.once("SIGINT", forwardSignal);
    process.once("SIGTERM", forwardSignal);
    const [exitCode] = await once(child, "close");
    process.exitCode = exitCode ?? 1;
    process.removeListener("SIGINT", forwardSignal);
    process.removeListener("SIGTERM", forwardSignal);
  } finally {
    await terminateChildTree(child);
    await rm(workspaceDirectory, { recursive: true, force: true });
  }
}

const entryPath = process.argv[1] ? resolve(process.argv[1]) : "";
if (entryPath === fileURLToPath(import.meta.url)) {
  await runPages(process.argv[2], process.argv.slice(3));
}
