import {
  cpSync,
  existsSync,
  mkdirSync,
  readdirSync,
  readFileSync,
  rmSync,
  statSync,
} from "node:fs";
import path from "node:path";
import { spawnSync } from "node:child_process";
import { fileURLToPath } from "node:url";
import {
  getReleaseArch,
  getReleaseArtifactName,
  getReleaseVersion,
} from "./release-artifact-utils.mjs";

const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const packageJson = JSON.parse(
  readFileSync(path.join(rootDir, "package.json"), "utf8"),
);
const version = getReleaseVersion(packageJson);
const artifactDir = path.join(rootDir, "app_dist");
const identifier = "com.tombutcher.farmcontrolserver";

function walkFiles(dir) {
  const files = [];
  if (!existsSync(dir)) {
    return files;
  }

  for (const entry of readdirSync(dir)) {
    const fullPath = path.join(dir, entry);
    const stats = statSync(fullPath);
    if (stats.isDirectory()) {
      files.push(...walkFiles(fullPath));
    } else {
      files.push(fullPath);
    }
  }

  return files;
}

function findByExtension(root, extension) {
  return walkFiles(root).find((filePath) =>
    filePath.toLowerCase().endsWith(extension.toLowerCase()),
  );
}

function findMacAppBundle() {
  const buildDir = path.join(rootDir, "build");
  if (!existsSync(buildDir)) {
    return null;
  }

  for (const entry of readdirSync(buildDir)) {
    if (!entry.startsWith("stable-macos-")) {
      continue;
    }

    const platformDir = path.join(buildDir, entry);
    for (const child of readdirSync(platformDir)) {
      if (child.endsWith(".app")) {
        return path.join(platformDir, child);
      }
    }
  }

  return null;
}

function findWindowsSetupExe() {
  const buildDir = path.join(rootDir, "build");
  if (!existsSync(buildDir)) {
    return null;
  }

  for (const entry of readdirSync(buildDir)) {
    if (!entry.startsWith("stable-win-")) {
      continue;
    }

    const platformDir = path.join(buildDir, entry);
    const setupExe = walkFiles(platformDir).find((filePath) =>
      filePath.toLowerCase().endsWith("-setup.exe"),
    );
    if (setupExe) {
      return setupExe;
    }
  }

  return findByExtension(buildDir, "-Setup.exe");
}

function publishArtifact(sourcePath, arch, ext) {
  if (!sourcePath || !existsSync(sourcePath)) {
    throw new Error(`Missing source artifact for ${arch}.${ext}: ${sourcePath ?? "not found"}`);
  }

  mkdirSync(artifactDir, { recursive: true });
  const destination = path.join(
    artifactDir,
    getReleaseArtifactName(version, arch, ext),
  );
  cpSync(sourcePath, destination);
  console.log(`Published ${destination}`);
  return destination;
}

function cleanArtifactDir(keepNames) {
  if (!existsSync(artifactDir)) {
    return;
  }

  for (const entry of readdirSync(artifactDir)) {
    if (keepNames.includes(entry)) {
      continue;
    }

    rmSync(path.join(artifactDir, entry), { recursive: true, force: true });
  }
}

function buildMacPkg(appBundlePath, arch) {
  const pkgPath = path.join(
    artifactDir,
    getReleaseArtifactName(version, arch, "pkg"),
  );

  const result = spawnSync(
    "pkgbuild",
    [
      "--component",
      appBundlePath,
      "--install-location",
      "/Applications",
      "--identifier",
      identifier,
      "--version",
      version,
      pkgPath,
    ],
    { stdio: "inherit" },
  );

  if (result.status !== 0) {
    throw new Error(`pkgbuild failed with exit code ${result.status ?? 1}`);
  }

  console.log(`Published ${pkgPath}`);
  return pkgPath;
}

function buildWindowsMsi(setupExePath, arch) {
  const scriptPath = path.join(rootDir, "scripts/build-windows-msi.ps1");
  const msiPath = path.join(
    artifactDir,
    getReleaseArtifactName(version, arch, "msi"),
  );
  const powershell =
    process.env.SystemRoot
      ? path.join(
          process.env.SystemRoot,
          "System32",
          "WindowsPowerShell",
          "v1.0",
          "powershell.exe",
        )
      : "powershell.exe";

  const result = spawnSync(
    powershell,
    [
      "-NoProfile",
      "-ExecutionPolicy",
      "Bypass",
      "-File",
      scriptPath,
      "-SetupExe",
      setupExePath,
      "-OutputMsi",
      msiPath,
      "-Version",
      version,
    ],
    { stdio: "inherit" },
  );

  if (result.status !== 0) {
    throw new Error(`build-windows-msi.ps1 failed with exit code ${result.status ?? 1}`);
  }

  console.log(`Published ${msiPath}`);
  return msiPath;
}

const published = [];

if (process.platform === "darwin") {
  const arch = getReleaseArch();
  const dmgSource =
    findByExtension(artifactDir, ".dmg") ?? findByExtension(path.join(rootDir, "build"), ".dmg");
  const appBundle = findMacAppBundle();

  if (!dmgSource) {
    throw new Error("Could not find a macOS DMG artifact to publish");
  }
  if (!appBundle) {
    throw new Error("Could not find a macOS .app bundle to build a PKG");
  }

  published.push(publishArtifact(dmgSource, arch, "dmg"));
  published.push(buildMacPkg(appBundle, arch));
} else if (process.platform === "win32") {
  const arch = "x64";
  const setupExe = findWindowsSetupExe();

  if (!setupExe) {
    throw new Error("Could not find the Windows setup executable to publish");
  }

  published.push(publishArtifact(setupExe, arch, "exe"));
  published.push(buildWindowsMsi(setupExe, arch));
} else {
  console.log("finalize-desktop-artifacts: skipping unsupported platform", process.platform);
  process.exit(0);
}

cleanArtifactDir(published.map((filePath) => path.basename(filePath)));
