#!/usr/bin/env tsx
import { spawn } from "node:child_process";
import { createWriteStream } from "node:fs";
import { access, copyFile, mkdir, rename, rm } from "node:fs/promises";
import https from "node:https";
import path from "node:path";
import { fileURLToPath } from "node:url";

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

const PACKAGE_ROOT = path.resolve(__dirname, "..");
const RESOURCES_DIR = path.join(PACKAGE_ROOT, "resources");
const GO_IOS_DIST_DIR = path.join(PACKAGE_ROOT, "node_modules", "go-ios", "dist");

const WINTUN_VERSION = "0.14.1";
const WINTUN_ZIP_NAME = `wintun-${WINTUN_VERSION}.zip`;
const WINTUN_URL = `https://www.wintun.net/builds/${WINTUN_ZIP_NAME}`;
const WINTUN_ZIP_PATH = path.join(RESOURCES_DIR, WINTUN_ZIP_NAME);
const WINTUN_EXTRACT_DIR = path.join(RESOURCES_DIR, ".wintun-extract");

async function pathExists(targetPath: string): Promise<boolean> {
	try {
		await access(targetPath);
		return true;
	} catch {
		return false;
	}
}

function runCommand(command: string, args: string[]): Promise<void> {
	return new Promise((resolve, reject) => {
		const child = spawn(command, args, { stdio: "inherit" });
		child.on("error", (error) => reject(error));
		child.on("close", (code) => {
			if (code === 0) {
				resolve();
				return;
			}
			reject(new Error(`${command} exited with code ${code ?? "unknown"}`));
		});
	});
}

function downloadFile(url: string, destinationPath: string): Promise<void> {
	return new Promise((resolve, reject) => {
		const tempPath = `${destinationPath}.download`;
		const request = https.get(url, (response) => {
			if (!response.statusCode) {
				reject(new Error("No status code from download response."));
				return;
			}

			if ([301, 302, 307, 308].includes(response.statusCode)) {
				const redirect = response.headers.location;
				if (!redirect) {
					reject(new Error("Redirected without location header."));
					return;
				}
				const redirectUrl = new URL(redirect, url).toString();
				downloadFile(redirectUrl, destinationPath).then(resolve).catch(reject);
				return;
			}

			if (response.statusCode !== 200) {
				reject(new Error(`Download failed with status ${response.statusCode}`));
				return;
			}

			const fileStream = createWriteStream(tempPath);
			response.pipe(fileStream);
			fileStream.on("finish", () => {
				fileStream.close(() => {
					rename(tempPath, destinationPath).then(resolve).catch(reject);
				});
			});
			fileStream.on("error", async (error) => {
				await rm(tempPath, { force: true });
				reject(error);
			});
		});

		request.on("error", async (error) => {
			await rm(`${destinationPath}.download`, { force: true });
			reject(error);
		});
	});
}

async function ensureWintunZip(): Promise<void> {
	if (await pathExists(WINTUN_ZIP_PATH)) {
		console.log(`Wintun zip already exists: ${WINTUN_ZIP_NAME}`);
		return;
	}

	console.log(`Downloading Wintun zip from ${WINTUN_URL}`);
	await downloadFile(WINTUN_URL, WINTUN_ZIP_PATH);
	console.log(`Downloaded: ${WINTUN_ZIP_NAME}`);
}

async function extractWintunZip(): Promise<void> {
	await rm(WINTUN_EXTRACT_DIR, { recursive: true, force: true });
	await mkdir(WINTUN_EXTRACT_DIR, { recursive: true });

	try {
		await runCommand("tar", ["-xf", WINTUN_ZIP_PATH, "-C", WINTUN_EXTRACT_DIR]);
		return;
	} catch (error) {
		if (process.platform === "win32") {
			await runCommand("powershell", [
				"-NoProfile",
				"-Command",
				`Expand-Archive -Path "${WINTUN_ZIP_PATH}" -DestinationPath "${WINTUN_EXTRACT_DIR}" -Force`,
			]);
			return;
		}
		throw error;
	}
}

function getWintunArchFolder(): string {
	if (process.arch === "x64") {
		return "amd64";
	}
	if (process.arch === "arm64") {
		return "arm64";
	}
	if (process.arch === "ia32") {
		return "x86";
	}
	throw new Error(`Unsupported Windows architecture: ${process.arch}`);
}

function getGoIosBinaryPath(): { sourcePath: string; destinationName: string } {
	if (process.platform === "win32") {
		if (process.arch !== "x64") {
			throw new Error(`Unsupported Windows architecture for go-ios: ${process.arch}`);
		}
		return {
			sourcePath: path.join(GO_IOS_DIST_DIR, "go-ios-windows-amd64_windows_amd64", "ios.exe"),
			destinationName: "ios.exe",
		};
	}

	if (process.platform === "darwin") {
		if (process.arch === "arm64") {
			return {
				sourcePath: path.join(GO_IOS_DIST_DIR, "go-ios-darwin-arm64_darwin_arm64", "ios"),
				destinationName: "ios",
			};
		}
		if (process.arch === "x64") {
			return {
				sourcePath: path.join(GO_IOS_DIST_DIR, "go-ios-darwin-amd64_darwin_amd64", "ios"),
				destinationName: "ios",
			};
		}
	}

	if (process.platform === "linux") {
		if (process.arch === "arm64") {
			return {
				sourcePath: path.join(GO_IOS_DIST_DIR, "go-ios-linux-arm64_linux_arm64", "ios"),
				destinationName: "ios",
			};
		}
		if (process.arch === "x64") {
			return {
				sourcePath: path.join(GO_IOS_DIST_DIR, "go-ios-linux-amd64_linux_amd64", "ios"),
				destinationName: "ios",
			};
		}
	}

	throw new Error(`Unsupported platform/arch for go-ios: ${process.platform}/${process.arch}`);
}

async function copyGoIosBinary(): Promise<void> {
	const { sourcePath, destinationName } = getGoIosBinaryPath();
	if (!(await pathExists(sourcePath))) {
		throw new Error(`go-ios binary not found: ${sourcePath}`);
	}
	await copyFile(sourcePath, path.join(RESOURCES_DIR, destinationName));
	console.log(`Copied go-ios binary to resources/${destinationName}`);
}

async function copyWintunDll(): Promise<void> {
	if (process.platform !== "win32") {
		console.log("Skipping Wintun: non-Windows platform detected.");
		return;
	}

	await ensureWintunZip();
	await extractWintunZip();

	const archFolder = getWintunArchFolder();
	const dllPath = path.join(WINTUN_EXTRACT_DIR, "wintun", "bin", archFolder, "wintun.dll");
	const licensePath = path.join(WINTUN_EXTRACT_DIR, "wintun", "LICENSE.txt");

	if (!(await pathExists(dllPath))) {
		throw new Error(`wintun.dll not found after extraction: ${dllPath}`);
	}

	await copyFile(dllPath, path.join(RESOURCES_DIR, "wintun.dll"));
	console.log(`Copied wintun.dll (${archFolder}) to resources/`);

	if (await pathExists(licensePath)) {
		await copyFile(licensePath, path.join(RESOURCES_DIR, "wintun-LICENSE.txt"));
		console.log("Copied Wintun license to resources/wintun-LICENSE.txt");
	}
}

async function main(): Promise<void> {
	await mkdir(RESOURCES_DIR, { recursive: true });
	await copyGoIosBinary();
	await copyWintunDll();
}

main().catch((error) => {
	console.error("Failed to prepare iOS resources:", error);
	process.exit(1);
});
