import { createHash } from "node:crypto";
import {
  copyFileSync,
  cpSync,
  existsSync,
  mkdirSync,
  readFileSync,
  readdirSync,
  rmSync,
  statSync,
  writeFileSync,
} from "node:fs";
import { basename, dirname, resolve } from "node:path";
import { spawnSync } from "node:child_process";
import { fileURLToPath } from "node:url";

const projectRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
const releaseDirectory = resolve(projectRoot, "release");
const stageDirectory = resolve(releaseDirectory, ".source-stage");
const sourceRootName = "factorio-calculator-source-1.0.0";
const stagedSource = resolve(stageDirectory, sourceRootName);
const sourceArchive = resolve(releaseDirectory, `${sourceRootName}.zip`);
const stratoArchive = resolve(releaseDirectory, "factorio-calculator-strato-1.0.0.zip");

function run(command, args, options = {}) {
  const result = spawnSync(command, args, {
    cwd: projectRoot,
    encoding: "utf8",
    stdio: "inherit",
    ...options,
  });
  if (result.status !== 0) {
    throw new Error(`${command} failed with exit code ${result.status}.`);
  }
}

function copyEntry(relativePath) {
  const source = resolve(projectRoot, relativePath);
  if (!existsSync(source)) throw new Error(`Missing source entry: ${relativePath}`);
  const destination = resolve(stagedSource, relativePath);
  mkdirSync(dirname(destination), { recursive: true });
  if (statSync(source).isDirectory()) {
    cpSync(source, destination, {
      recursive: true,
      force: true,
      filter: (path) => !path.includes("/.sites-runtime/") && !path.includes("/node_modules/"),
    });
  } else {
    copyFileSync(source, destination);
  }
}

function filesBelow(directory) {
  return readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
    const path = resolve(directory, entry.name);
    return entry.isDirectory() ? filesBelow(path) : [path];
  });
}

function sha256(path) {
  return createHash("sha256").update(readFileSync(path)).digest("hex");
}

if (!existsSync(resolve(projectRoot, "release-operator.json"))) {
  throw new Error("Copy release-operator.example.json to release-operator.json and set the public website operator before packaging a hosting release.");
}

rmSync(releaseDirectory, { recursive: true, force: true });
mkdirSync(stagedSource, { recursive: true });

run("npm", ["run", "licenses:collect"]);
run("npm", ["run", "build:strato"]);

for (const entry of [
  "app",
  "build",
  "components",
  "lib",
  "public",
  "scripts",
  "strato",
  "tests",
  "THIRD_PARTY_LICENSES",
  "vendor",
  "windows",
  "worker",
  "AUTHOR.md",
  "BLUEPRINT_GENERATOR.md",
  "LICENSE_REVIEW.md",
  "release-operator.example.json",
  "CHANGELOG.md",
  "GITHUB_PUBLISHING.md",
  "LEGAL.md",
  "LICENSE.md",
  "PRIVACY.md",
  "README.md",
  "RELEASE_CHECKLIST.md",
  "REQUIREMENTS.md",
  "SECURITY.md",
  "THIRD_PARTY_NOTICES.md",
  "WINDOWS_EXECUTABLES.md",
  ".gitignore",
  "cloudflare-env.d.ts",
  "components.json",
  "eslint.config.mjs",
  "next.config.ts",
  "package-lock.json",
  "package.json",
  "postcss.config.mjs",
  "tsconfig.json",
  "vite.config.ts",
  "vite.strato.config.ts",
]) copyEntry(entry);

copyFileSync(
  resolve(projectRoot, "vendor/icons-factorio.GPL-3.0.txt"),
  resolve(stagedSource, "LICENSE"),
);

const stagedText = filesBelow(stagedSource)
  .filter((path) => statSync(path).size < 8_000_000)
  .filter((path) => ![".png", ".ico", ".zip"].includes(path.slice(path.lastIndexOf("."))))
  .map((path) => readFileSync(path, "utf8"))
  .join("\n");

for (const forbidden of [
  ["Florian", "Thomas"].join(" "),
  ["app", "gprj_"].join(""),
  ["PUBLIC", "RELEASE", "INPUTS"].join("_"),
  ["PERMISSION", "RECORD"].join("_"),
  ["IMPRESSUM", "TEMPLATE"].join("_"),
  ["PRIVACY", "TEMPLATE"].join("_"),
  ["UN", "LICENSED"].join(""),
  ["private", "preview"].join(" "),
  ["Blueprint generator", "development build"].join(" "),
  ["Private", "Entwicklungsfassung"].join(" "),
  ["Optimized Biolab mode", "forms one continuous row"].join(" "),
]) {
  if (stagedText.includes(forbidden)) {
    throw new Error(`Source package contains forbidden text: ${forbidden}`);
  }
}

rmSync(sourceArchive, { force: true });
run("zip", ["-q", "-X", "-r", sourceArchive, sourceRootName], {
  cwd: stageDirectory,
});

const downloadsDirectory = resolve(projectRoot, "strato-dist/downloads");
mkdirSync(downloadsDirectory, { recursive: true });
copyFileSync(sourceArchive, resolve(downloadsDirectory, basename(sourceArchive)));
run("npm", ["run", "verify:strato", "--", "--release"]);

rmSync(stratoArchive, { force: true });
run("zip", ["-q", "-X", "-r", stratoArchive, "."], {
  cwd: resolve(projectRoot, "strato-dist"),
});

const uploadReadme = `FACTORIOCALCULATORAPP.COM — STRATO UPLOAD\n\n1. Extract factorio-calculator-strato-1.0.0.zip locally.\n2. Include the .htaccess file, assets, all language folders and downloads. Upload the CONTENTS of the extracted folder to the domain's target directory using FileZilla/SFTP. FileZilla transfers files reliably but does not need to unpack the ZIP on STRATO.\n3. Do not upload the enclosing folder itself unless the domain points to that folder. index.html must be directly inside the assigned target directory.\n4. Confirm HTTPS, then test / (redirects to /en/), /en/, /de/, /es-ES/, /fr/, /it/, /ja/, /ru/, /zh-CN/, /zh-TW/, /robots.txt, /sitemap.xml, /privacy.html, /legal.html, /license.html, /third-party-notices.html and /downloads/factorio-calculator-source-1.0.0.zip.\n5. Keep SHA256SUMS.txt locally to verify the release archives.\n`;
writeFileSync(resolve(releaseDirectory, "STRATO_UPLOAD_README.txt"), uploadReadme);

const checksumLines = [sourceArchive, stratoArchive]
  .map((path) => `${sha256(path)}  ${basename(path)}`)
  .join("\n");
writeFileSync(resolve(releaseDirectory, "SHA256SUMS.txt"), `${checksumLines}\n`);

rmSync(stageDirectory, { recursive: true, force: true });
console.log(`Release packages created at ${releaseDirectory}`);
