import {
  createWriteStream,
  existsSync,
  mkdirSync,
  readdirSync,
  unlinkSync,
} from "node:fs";
import { spawnSync } from "node:child_process";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { getReleaseArch } from "./release-artifact-utils.mjs";
import { fixMacosHeaderpad } from "./fix-macos-headerpad.mjs";

const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const electrobunDir = path.join(rootDir, "node_modules/electrobun");
const electrobunVersion = JSON.parse(
  await Bun.file(path.join(electrobunDir, "package.json")).text(),
).version;

function getPlatformPaths(targetOS, targetArch) {
  const binExt = targetOS === "win" ? ".exe" : "";
  const platformDistDir = path.join(
    electrobunDir,
    `dist-${targetOS}-${targetArch}`,
  );

  return {
    platformDistDir,
    bunBinary: path.join(platformDistDir, "bun") + binExt,
    bsdiff: path.join(platformDistDir, "bsdiff") + binExt,
    bspatch: path.join(platformDistDir, "bspatch") + binExt,
    launcher: path.join(platformDistDir, "launcher") + binExt,
    nativeWrapperMacos: path.join(platformDistDir, "libNativeWrapper.dylib"),
  };
}

async function downloadFile(url, destination) {
  const response = await fetch(url);
  if (!response.ok) {
    throw new Error(`Failed to download ${url}: ${response.status} ${response.statusText}`);
  }

  mkdirSync(path.dirname(destination), { recursive: true });
  const fileStream = createWriteStream(destination);
  const reader = response.body?.getReader();
  if (!reader) {
    throw new Error(`No response body for ${url}`);
  }

  while (true) {
    const { done, value } = await reader.read();
    if (done) break;
    fileStream.write(Buffer.from(value));
  }

  await new Promise((resolve, reject) => {
    fileStream.end((error) => (error ? reject(error) : resolve()));
  });
}

function extractTarGz(tarPath, destination) {
  mkdirSync(destination, { recursive: true });
  const result = spawnSync("tar", ["-xzf", tarPath, "-C", destination], {
    stdio: "inherit",
  });
  if (result.status !== 0) {
    throw new Error(`tar extraction failed for ${tarPath}`);
  }
}

async function ensureCoreDependencies(targetOS, targetArch) {
  const paths = getPlatformPaths(targetOS, targetArch);
  const required = [
    paths.bunBinary,
    paths.bsdiff,
    paths.bspatch,
    paths.launcher,
    paths.nativeWrapperMacos,
  ];

  if (required.every((filePath) => existsSync(filePath))) {
    console.log(`ensure-electrobun-core: ${targetOS}-${targetArch} already present`);
    return;
  }

  const platformName =
    targetOS === "macos" ? "darwin" : targetOS === "win" ? "win" : "linux";
  const url = `https://github.com/blackboardsh/electrobun/releases/download/v${electrobunVersion}/electrobun-core-${platformName}-${targetArch}.tar.gz`;
  const tempFile = path.join(
    electrobunDir,
    `core-${targetOS}-${targetArch}-temp.tar.gz`,
  );

  console.log(`ensure-electrobun-core: downloading ${targetOS}-${targetArch}`);
  console.log(url);

  await downloadFile(url, tempFile);
  extractTarGz(tempFile, paths.platformDistDir);

  if (existsSync(tempFile)) {
    unlinkSync(tempFile);
  }

  const missing = required.filter((filePath) => !existsSync(filePath));
  if (missing.length > 0) {
    const extracted = existsSync(paths.platformDistDir)
      ? readdirSync(paths.platformDistDir)
      : [];
    throw new Error(
      `Missing ${targetOS}-${targetArch} binaries after extract: ${missing.join(", ")}; extracted=${extracted.join(", ")}`,
    );
  }

  console.log(`ensure-electrobun-core: ${targetOS}-${targetArch} ready`);
}

const targetOS = "macos";
const hostArch = getReleaseArch(process.arch);
const targetArch = getReleaseArch(
  process.env.ELECTROBUN_TARGET_ARCH ||
    process.env.ELECTROBUN_FORCE_ARCH ||
    process.arch,
);

const archesToEnsure = new Set([hostArch, targetArch]);
for (const arch of archesToEnsure) {
  await ensureCoreDependencies(targetOS, arch);
  // electrobun#485: x64 core binaries lack Mach-O headerpad and get corrupted
  // by codesign; free a load-command slot before anything copies or signs them.
  fixMacosHeaderpad(getPlatformPaths(targetOS, arch).platformDistDir);
}
