import archiveData from "./app-archive.tar.gz" with { type: "file" };
import metadataJson from "./metadata.json" with { type: "file" };
import { join } from "path";
import { existsSync, mkdirSync, chmodSync, readdirSync } from "fs";

interface Metadata {
	identifier: string;
	name: string;
	channel: string;
	hash: string;
	requireAdmin?: boolean;
}

const metadata: Metadata = await Bun.file(metadataJson).json();
const appName = metadata.name;
const exeName = metadata.name.replace(/ /g, "");
const installDir = getInstallDir(metadata);

try {
	console.log();
	console.log(`Installing ${appName}...`);

	const archiveBytes = await Bun.file(archiveData).bytes();
	const archive = new Bun.Archive(archiveBytes);
	mkdirSync(installDir, { recursive: true });
	await archive.extract(installDir);
	console.log(`Extracted to ${installDir}`);

	let appDir = installDir;
	// Look for the app bundle folder inside the extraction dir
	const entries = readdirSync(installDir);
	for (const entry of entries) {
		const entryPath = join(installDir, entry);
		if (existsSync(join(entryPath, "bin"))) {
			appDir = entryPath;
			break;
		}
	}

	if (process.platform === "win32") {
		await createWindowsShortcuts(appDir, metadata);
	} else if (process.platform === "linux") {
		fixPermissions(appDir);
		await createLinuxDesktopEntry(appDir, metadata);
	}

	// Launch the app
	const launcherName = process.platform === "win32" ? `${exeName}.exe` : exeName;
	const launcherPath = join(appDir, "bin", launcherName);

	if (existsSync(launcherPath)) {
		console.log(`Launching ${appName}...`);
		if (process.platform === "win32" && metadata.requireAdmin) {
			const { dlopen, ptr } = await import("bun:ffi");
			const shell32 = dlopen("shell32.dll", {
				ShellExecuteW: {
					args: ["ptr", "ptr", "ptr", "ptr", "ptr", "i32"],
					returns: "ptr",
				},
			});
			const encode = (s: string) => ptr(new Uint8Array(Buffer.from(s + "\0", "utf-16le")));
			shell32.symbols.ShellExecuteW(
				null,
				encode("runas"),
				encode(launcherPath),
				null,
				encode(join(appDir, "bin")),
				1,
			);
			shell32.close();
		} else {
			Bun.spawn([launcherPath], { cwd: join(appDir, "bin") });
		}
		await Bun.sleep(1000);
	}
} catch (e: any) {
	console.error(`Installation failed: ${e.message}`);
	if (process.platform === "win32") {
		console.log("Press Enter to exit...");
		for await (const _ of console) { break; }
	}
	process.exit(1);
}

function getInstallDir(meta: Metadata): string {
	if (process.platform === "win32") {
		const localAppData = process.env.LOCALAPPDATA || join(process.env.USERPROFILE || "", "AppData", "Local");
		return join(localAppData, meta.identifier, meta.channel, "app");
	} else {
		const dataHome = process.env.XDG_DATA_HOME || join(process.env.HOME || "", ".local", "share");
		return join(dataHome, meta.identifier, meta.channel, "app");
	}
}

// Create a .lnk via WScript.Shell. windowsHide is essential: the installer is a
// GUI-subsystem exe, so without it each PowerShell child allocates its own
// visible console window mid-install. PowerShell is called by absolute path
// (PATH isn't reliable in every launch context), and the paths are passed
// through the environment ($env:SB_*) rather than interpolated into the script
// text, so an app name containing quotes can't break or inject into the command.
function createShortcut(lnkPath: string, targetPath: string, workingDir: string) {
	const systemRoot = process.env.SystemRoot || process.env.windir || "C:\\Windows";
	const powershell = join(systemRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
	const script =
		"$ws = New-Object -ComObject WScript.Shell;" +
		" $s = $ws.CreateShortcut($env:SB_LNK);" +
		" $s.TargetPath = $env:SB_TARGET;" +
		" $s.WorkingDirectory = $env:SB_WORKDIR;" +
		" $s.Save()";
	try {
		Bun.spawnSync([powershell, "-NoProfile", "-Command", script], {
			stdout: "ignore",
			stderr: "ignore",
			windowsHide: true,
			env: {
				...process.env,
				SB_LNK: lnkPath,
				SB_TARGET: targetPath,
				SB_WORKDIR: workingDir,
			},
		});
	} catch {}
}

async function createWindowsShortcuts(appDir: string, meta: Metadata) {
	const binDir = join(appDir, "bin");
	const exePath = join(binDir, `${meta.name.replace(/ /g, "")}.exe`);

	if (!existsSync(exePath)) return;

	// Start Menu shortcut
	const startMenu = join(process.env.APPDATA || "", "Microsoft", "Windows", "Start Menu", "Programs");
	createShortcut(join(startMenu, meta.name + ".lnk"), exePath, binDir);

	// Desktop shortcut
	const desktop = join(process.env.USERPROFILE || "", "Desktop");
	createShortcut(join(desktop, meta.name + ".lnk"), exePath, binDir);

	console.log("Created Start Menu and Desktop shortcuts");
}

function fixPermissions(appDir: string) {
	const exeBaseName = appDir.split("/").pop()?.replace(/ /g, "") || "app";
	const executables = [`bin/${exeBaseName}`];
	for (const exe of executables) {
		const path = join(appDir, exe);
		if (existsSync(path)) {
			try { chmodSync(path, 0o755); } catch {}
		}
	}
}

async function createLinuxDesktopEntry(appDir: string, meta: Metadata) {
	const launcherPath = join(appDir, "bin", meta.name.replace(/ /g, ""));
	if (!existsSync(launcherPath)) return;

	// Find icon
	let iconPath = "";
	const resourcesDir = join(appDir, "Resources");
	if (existsSync(resourcesDir)) {
		const pngs = readdirSync(resourcesDir).filter(f => f.endsWith(".png"));
		if (pngs.length > 0) iconPath = join(resourcesDir, pngs[0]);
	}

	// If requireAdmin, install polkit policy and use pkexec in Exec line
	if (meta.requireAdmin) {
		await installPolkitPolicy(launcherPath, meta);
	}

	const execLine = meta.requireAdmin
		? `pkexec "${launcherPath}"`
		: `"${launcherPath}"`;

	const desktopContent = `[Desktop Entry]
Version=1.0
Type=Application
Name=${meta.name}
Exec=${execLine}
Icon=${iconPath}
Terminal=false
StartupWMClass=${meta.name}
Categories=Utility;Application;
`;

	// Write to XDG applications directory
	const dataHome = process.env.XDG_DATA_HOME || join(process.env.HOME || "", ".local", "share");
	const applicationsDir = join(dataHome, "applications");
	mkdirSync(applicationsDir, { recursive: true });

	const desktopFilePath = join(applicationsDir, `${meta.name}.desktop`);
	await Bun.write(desktopFilePath, desktopContent);
	chmodSync(desktopFilePath, 0o644);

	// Desktop shortcut
	const desktopDir = join(process.env.HOME || "", "Desktop");
	if (existsSync(desktopDir)) {
		const desktopShortcut = join(desktopDir, `${meta.name}.desktop`);
		await Bun.write(desktopShortcut, desktopContent);
		chmodSync(desktopShortcut, 0o755);
		try {
			Bun.spawnSync(["gio", "set", desktopShortcut, "metadata::trusted", "true"]);
		} catch {}
	}

	console.log("Created desktop entries");
}

async function installPolkitPolicy(launcherPath: string, meta: Metadata) {
	const policyId = meta.identifier.replace(/-/g, ".");
	const policyContent = `<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE policyconfig PUBLIC
 "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
 "http://www.freedesktop.org/standards/PolicyKit/1/policyconfig.dtd">
<policyconfig>
  <action id="${policyId}.run">
    <description>Run ${meta.name}</description>
    <message>Authentication is required to run ${meta.name}</message>
    <defaults>
      <allow_any>auth_admin</allow_any>
      <allow_inactive>auth_admin</allow_inactive>
      <allow_active>auth_admin_keep</allow_active>
    </defaults>
    <annotate key="org.freedesktop.policykit.exec.path">${launcherPath}</annotate>
    <annotate key="org.freedesktop.policykit.exec.allow_gui">true</annotate>
  </action>
</policyconfig>
`;

	const policyDir = "/usr/share/polkit-1/actions";
	const policyPath = join(policyDir, `${policyId}.policy`);

	try {
		await Bun.write(policyPath, policyContent);
		console.log(`Installed polkit policy: ${policyPath}`);
	} catch {
		console.log("Note: Could not install polkit policy (may need root). The app will still prompt for authentication via pkexec.");
	}
}
