import { Notification, app, ipcMain } from "electron";
import { exec, spawn } from "child_process";
import { createWriteStream, mkdirSync, writeFileSync } from "fs";
import { dirname, join } from "path";
import { tmpdir } from "os";

import { showUpdateWindow, setUpdateProgress, setUpdateReady, setUpdateError } from "./update-window";

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);

    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}`);
      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.`);
      return;
    }

    console.log(`[updater] update available: ${current} → ${latest}, downloading ${asset.name}`);
    showUpdateWindow();
    await downloadAndInstall(asset.browser_download_url, latest);
  } catch (err) {
    console.error("[updater] update check failed:", err);
    setUpdateError(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");
  const installDir = dirname(process.execPath);

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

  const total = Number(res.headers.get("content-length")) || 0;
  let downloaded = 0;
  const writer = createWriteStream(zipPath);
  const reader = res.body.getReader();

  while (true) {
    const { done, value } = await reader.read();
    if (done) break;
    writer.write(value);
    downloaded += value.length;
    if (total > 0) setUpdateProgress(Math.round((downloaded / total) * 90));
  }
  await new Promise<void>(resolve => writer.end(resolve));

  setUpdateProgress(95);
  console.log(`[updater] download complete, extracting to ${installDir}`);

  if (process.platform === "win32") {
    // Extract zip while the app is still running (no locked files yet)
    await new Promise<void>((resolve, reject) => {
      const cmd = `powershell -Command "Expand-Archive -Force -Path '${zipPath}' -DestinationPath '${extractDir}'"`;
      exec(cmd, (err, _stdout, stderr) => {
        if (err) { console.error("[updater] extract failed:", stderr); reject(err); }
        else resolve();
      });
    });

    // Write a batch script that runs after we exit: waits, copies, relaunches
    const batchPath = join(tmpDir, "apply-update.bat");
    const bat = [
      "@echo off",
      "timeout /t 3 /nobreak >nul",
      `for /d %%D in ("${extractDir}\\*") do set SUB=%%D`,
      `xcopy /E /Y /I "%SUB%\\*" "${installDir}\\"`,
      `start "" "${join(installDir, "sanctum.exe")}"`,
      `del "%~f0"`,
    ].join("\r\n");
    writeFileSync(batchPath, bat);

    // Spawn batch truly detached so it outlives this process
    const child = spawn("cmd.exe", ["/C", batchPath], {
      detached: true,
      stdio: "ignore",
      windowsHide: false,
    });
    child.unref();
    setUpdateReady(version); // restart button will just app.exit(0) — batch relaunches
  } else {
    await new Promise<void>((resolve, reject) => {
      const cmd = `unzip -o "${zipPath}" -d "${extractDir}" && SUBDIR=$(ls "${extractDir}" | head -1) && rm -f "${installDir}/sanctum" && cp -rT "${extractDir}/$SUBDIR" "${installDir}"`;
      exec(cmd, { shell: "/bin/bash" }, (err, _stdout, stderr) => {
        if (err) { console.error("[updater] extract failed:", stderr); reject(err); }
        else resolve();
      });
    });
    setUpdateReady(version);
  }
}

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;
}
