import { createHash } from "node:crypto";
import { spawnSync } from "node:child_process";
import { createReadStream } from "node:fs";
import { copyFile, mkdir, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
import { basename, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";

async function sha256(path) {
  const hash = createHash("sha256");
  for await (const chunk of createReadStream(path)) hash.update(chunk);
  return hash.digest("hex");
}

function requireValue(condition, code) {
  if (!condition) throw new Error(code);
}

async function verifyArtifact(candidateDirectory, artifact, expectedFile, expectedSigning) {
  requireValue(artifact?.file === expectedFile, "public_download_artifact_name_mismatch");
  requireValue(basename(artifact.file) === artifact.file, "public_download_artifact_path_rejected");
  requireValue(artifact.signing === expectedSigning, "public_download_signing_mismatch");
  requireValue(Number.isSafeInteger(artifact.bytes) && artifact.bytes > 0, "public_download_bytes_invalid");
  requireValue(/^[a-f0-9]{64}$/.test(artifact.sha256 ?? ""), "public_download_sha256_invalid");
  const path = join(candidateDirectory, artifact.file);
  const metadata = await stat(path);
  requireValue(metadata.isFile() && metadata.size === artifact.bytes, "public_download_bytes_mismatch");
  requireValue(await sha256(path) === artifact.sha256, "public_download_sha256_mismatch");
  return path;
}

export async function stagePublicDownload(candidatePath, outputPath) {
  const candidateDirectory = resolve(candidatePath);
  const outputDirectory = resolve(outputPath);
  const manifestText = await readFile(join(candidateDirectory, "release-manifest.json"), "utf8");
  const manifest = JSON.parse(manifestText);
  requireValue(manifest.schemaVersion === 2, "public_download_manifest_schema_invalid");
  requireValue(manifest.product === "Code Remote Manager", "public_download_product_invalid");
  requireValue(/^\d+\.\d+\.\d+$/.test(manifest.version ?? ""), "public_download_version_invalid");
  requireValue(
    manifest.distribution?.windows?.channel === "public-unsigned"
      && manifest.distribution.windows.signature === "unsigned",
    "public_download_channel_invalid",
  );
  const installerName = "CodeRemoteManagerSetup.exe";
  const installerArchiveName = "CodeRemoteManagerSetup.zip";
  const sourceName = `CodeRemoteManager-${manifest.version}-source.zip`;
  const installer = await verifyArtifact(candidateDirectory, manifest.artifacts?.windows, installerName, "unsigned");
  const source = await verifyArtifact(candidateDirectory, manifest.artifacts?.source, sourceName, "not-applicable");

  const downloadDirectory = join(outputDirectory, "download");
  await mkdir(downloadDirectory, { recursive: true });
  const previousArtifacts = (await readdir(downloadDirectory, { withFileTypes: true }))
    .filter((entry) => entry.isFile() && (
      entry.name === installerName
      || entry.name === installerArchiveName
      || entry.name === "release-manifest.json"
      || /^CodeRemoteManager-\d+\.\d+\.\d+-source\.zip$/.test(entry.name)
    ));
  await Promise.all(previousArtifacts.map((entry) => rm(join(downloadDirectory, entry.name), { force: true })));
  const archive = spawnSync("tar.exe", [
    "-a",
    "-cf",
    join(downloadDirectory, installerArchiveName),
    "-C",
    candidateDirectory,
    installerName,
  ], { encoding: "utf8" });
  requireValue(archive.status === 0, "public_download_installer_archive_failed");
  await Promise.all([
    copyFile(source, join(downloadDirectory, sourceName)),
    writeFile(join(downloadDirectory, "release-manifest.json"), `${JSON.stringify(manifest, null, 2)}\n`),
  ]);
  console.log(`Staged verified public Windows ${manifest.version} downloads.`);
}

if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
  if (!process.argv[2] || !process.argv[3]) throw new Error("Use: stage-public-download CANDIDATE_DIR WEBSITE_OUTPUT");
  await stagePublicDownload(process.argv[2], process.argv[3]);
}
