import { Notification, app, ipcMain } from "electron";
import { exec } from "child_process";
import { createWriteStream, mkdirSync } from "fs";
import { dirname, join } from "path";
import { tmpdir } from "os";
import { pipeline } from "stream/promises";
import { Readable } from "stream";

ipcMain.handle("checkForUpdates", () => checkForUpdates());

const RELEASES_URL =
  "https://git.mithraic.cloud/api/v1/repos/ad3laid3/sanctum/releases/latest";

interface Asset {
  name: string;
  browser_download_url: string;
}

interface Release {
  tag_name: string;
  html_url: string;
  assets: Asset[];
}

export async function checkForUpdates() {
  try {
    console.log("[updater] checking:", RELEASES_URL);
    notify("Checking for Updates", "Looking for a new version of Sanctum…");

    const res = await fetch(RELEASES_URL);
    if (!res.ok) {
      console.error("[updater] releases API returned", res.status, res.statusText);
      notify("Update Check Failed", `API returned ${res.status} — check console.`);
      return;
    }

    const release = (await res.json()) as Release;
    const latest = release.tag_name.replace(/^v/, "");
    const current = app.getVersion();

    console.log(`[updater] current=${current} latest=${latest}`);

    if (!isNewer(latest, current)) {
      notify("Already Up to Date", `You're on Sanctum ${current}.`);
      return;
    }

    const asset = findAsset(release.assets);
    if (!asset) {
      const names = release.assets.map(a => a.name).join(", ");
      console.error("[updater] no matching asset for platform:", process.platform, names);
      notify("Update Failed", `No ${process.platform} asset found. Assets: ${names}`);
      return;
    }

    console.log(`[updater] update available: ${current} → ${latest}, downloading ${asset.name}`);
    notify("Update Downloading", `Sanctum ${latest} is downloading in the background…`);
    await downloadAndInstall(asset.browser_download_url, latest);
  } catch (err) {
    console.error("[updater] update check failed:", err);
    notify("Update Check Failed", String(err));
  }
}

function findAsset(assets: Asset[]): Asset | undefined {
  if (process.platform === "linux")
    return assets.find((a) => a.name.includes("linux") && a.name.endsWith(".zip"));
  if (process.platform === "win32")
    return assets.find((a) => a.name.includes("win32") && a.name.endsWith(".zip"));
}

async function downloadAndInstall(url: string, version: string) {
  const tmpDir = join(tmpdir(), `sanctum-update-${version}`);
  mkdirSync(tmpDir, { recursive: true });
  const zipPath = join(tmpDir, "update.zip");
  const extractDir = join(tmpDir, "extracted");

  console.log(`[updater] downloading from ${url}`);
  const res = await fetch(url);
  if (!res.ok) throw new Error(`Download failed: ${res.status}`);

  const installDir = dirname(process.execPath);

  await pipeline(Readable.fromWeb(res.body as Parameters<typeof Readable.fromWeb>[0]), createWriteStream(zipPath));
  console.log(`[updater] download complete, extracting to ${installDir}`);

  await new Promise<void>((resolve, reject) => {
    const cmd =
      process.platform === "win32"
        ? `powershell -Command "Expand-Archive -Force -Path '${zipPath}' -DestinationPath '${extractDir}'; $sub = (Get-ChildItem '${extractDir}' | Select-Object -First 1).FullName; Copy-Item -Recurse -Force \\"$sub\\*\\" '${installDir}'"`
        : `unzip -o "${zipPath}" -d "${extractDir}" && SUBDIR=$(ls "${extractDir}" | head -1) && cp -rT "${extractDir}/$SUBDIR" "${installDir}"`;

    exec(cmd, { shell: process.platform === "win32" ? undefined : "/bin/bash" }, (err, _stdout, stderr) => {
      if (err) { console.error("[updater] extract failed:", stderr); reject(err); }
      else resolve();
    });
  });

  const n = notify("Update Ready", `Sanctum ${version} is ready — click to restart.`);
  n.on("click", () => { app.relaunch(); app.exit(0); });
}

function notify(title: string, body: string) {
  const n = new Notification({ title, body, silent: true });
  n.show();
  return n;
}

function isNewer(latest: string, current: string): boolean {
  const parse = (v: string) => v.split(".").map(p => Number(p) || 0);
  const [lA, lB, lC] = parse(latest);
  const [cA, cB, cC] = parse(current);
  if (lA !== cA) return lA > cA;
  if (lB !== cB) return lB > cB;
  return lC > cC;
}
