#!/usr/bin/env tsx
/**
 * Export ADB (Android Debug Bridge) resources to a specified path
 *
 * This script downloads ADB platform tools from Google and exports them
 * to a target directory. The resources can then be used by applications
 * that depend on adb-kit.
 *
 * Usage:
 *   npx tsx export-resources.ts <target-path>
 *
 * Example:
 *   npx tsx export-resources.ts /path/to/my-app/resources/adb-kit
 *
 * Platform support:
 *   - macOS: Downloads darwin platform-tools from Google
 *   - Windows: Downloads windows platform-tools from Google
 *   - Linux: Downloads linux platform-tools from Google
 */

import { exec } from "node:child_process";
import {
	chmodSync,
	copyFileSync,
	createWriteStream,
	existsSync,
	mkdirSync,
	writeFileSync,
} from "node:fs";
import path from "node:path";
import { promisify } from "node:util";
import { createLoggers } from "@mcesystems/tool-debug-g4";

const {
	logInfo,
	logDetail,
	logError,
	logWarning,
	logDataObject,
	setNamespace,
	setLogLevel,
	logErrorObject,
} = createLoggers("adb-kit");

const execAsync = promisify(exec);

// ============================================================================
// Configuration
// ============================================================================

// ADB download URLs from Google
const ADB_URLS: Record<string, string> = {
	windows: "https://dl.google.com/android/repository/platform-tools-latest-windows.zip",
	darwin: "https://dl.google.com/android/repository/platform-tools-latest-darwin.zip",
	linux: "https://dl.google.com/android/repository/platform-tools-latest-linux.zip",
};

// Files to extract from platform-tools per platform
const PLATFORM_FILES: Record<string, string[]> = {
	windows: ["adb.exe", "AdbWinApi.dll", "AdbWinUsbApi.dll"],
	darwin: ["adb"],
	linux: ["adb"],
};

// ============================================================================
// Utility Functions
// ============================================================================

function printUsage(): void {
	logInfo(`
Usage: npx tsx export-resources.ts <target-path>

Arguments:
  target-path    Directory where resources will be exported

Examples:
  npx tsx export-resources.ts ./my-app/resources/adb-kit
  npx tsx export-resources.ts /absolute/path/to/resources

The script will create the following structure:
  <target-path>/
    bin/
      darwin/     (macOS ADB binary)
      windows/    (Windows ADB binaries and DLLs)
      linux/      (Linux ADB binary)
    licenses/
      APACHE-2.0.txt
`);
}

async function downloadFile(url: string, destPath: string): Promise<void> {
	logInfo(`Downloading ${url}...`);

	let response: Response;
	try {
		response = await fetch(url);
	} catch {
		// Fallback to https module if fetch is not available
		const https = await import("node:https");
		const http = await import("node:http");
		const urlObj = new URL(url);
		const client = urlObj.protocol === "https:" ? https : http;

		return new Promise((resolve, reject) => {
			const fileStream = createWriteStream(destPath);
			const request = client.get(url, (res) => {
				if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
					// Handle redirect
					fileStream.close();
					return downloadFile(res.headers.location, destPath).then(resolve).catch(reject);
				}
				if (res.statusCode && res.statusCode !== 200) {
					fileStream.close();
					reject(new Error(`Failed to download ${url}: ${res.statusCode} ${res.statusMessage}`));
					return;
				}
				res.pipe(fileStream);
				fileStream.on("finish", () => {
					fileStream.close();
					resolve();
				});
			});
			request.on("error", (err) => {
				fileStream.close();
				reject(err);
			});
		});
	}

	if (!response.ok) {
		throw new Error(`Failed to download ${url}: ${response.statusText}`);
	}

	const fileStream = createWriteStream(destPath);
	const reader = response.body?.getReader();
	if (!reader) {
		throw new Error(`Failed to get response body for ${url}`);
	}

	try {
		while (true) {
			const { done, value } = await reader.read();
			if (done) break;
			fileStream.write(value);
		}
		fileStream.end();
		await new Promise<void>((resolve, reject) => {
			fileStream.on("finish", resolve);
			fileStream.on("error", reject);
		});
		logInfo(`✓ Downloaded to ${destPath}`);
	} finally {
		reader.releaseLock();
	}
}

async function extractZip(zipPath: string, extractDir: string): Promise<void> {
	logInfo(`Extracting ${zipPath}...`);

	if (process.platform === "win32") {
		// On Windows, use PowerShell Expand-Archive
		try {
			const escapedZipPath = zipPath.replace(/'/g, "''");
			const escapedExtractDir = extractDir.replace(/'/g, "''");
			await execAsync(
				`powershell -Command "Expand-Archive -Path '${escapedZipPath}' -DestinationPath '${escapedExtractDir}' -Force"`
			);
		} catch (error) {
			const err = error as { message?: string };
			throw new Error(
				`Failed to extract zip file. Please ensure PowerShell is available. Error: ${err.message || error}`
			);
		}
	} else {
		// On Unix-like systems, use unzip command
		try {
			await execAsync(`unzip -o "${zipPath}" -d "${extractDir}"`);
		} catch (error) {
			const err = error as { message?: string };
			throw new Error(
				`Failed to extract zip file. Please ensure 'unzip' is installed. Error: ${err.message || error}`
			);
		}
	}
	logDetail(`✓ Extracted to ${extractDir}`);
}

async function copyFiles(sourceDir: string, targetDir: string, files: string[]): Promise<void> {
	mkdirSync(targetDir, { recursive: true });

	for (const file of files) {
		const sourcePath = path.join(sourceDir, "platform-tools", file);
		const targetPath = path.join(targetDir, file);

		if (!existsSync(sourcePath)) {
			logWarning(`Warning: ${file} not found in ${sourcePath}`);
			continue;
		}

		copyFileSync(sourcePath, targetPath);
		logDetail(`✓ Copied ${file}`);
	}
}

function makeExecutable(filePath: string): void {
	if (process.platform !== "win32") {
		chmodSync(filePath, 0o755);
	}
}

async function cleanupDir(dirPath: string): Promise<void> {
	try {
		const { rimraf } = await import("rimraf");
		await rimraf(dirPath);
	} catch {
		// Ignore cleanup errors
	}
}

// ============================================================================
// Export Functions
// ============================================================================

type PlatformType = "windows" | "darwin" | "linux";

async function exportPlatformResources(platform: PlatformType, targetDir: string): Promise<void> {
	logDataObject(`Exporting ${platform} Resources`, { Target: targetDir });

	const binDir = path.join(targetDir, "bin", platform);
	const files = PLATFORM_FILES[platform];
	const url = ADB_URLS[platform];

	// Create temp directory for extraction
	const tempDir = path.join(targetDir, ".temp", platform);
	mkdirSync(tempDir, { recursive: true });
	const zipPath = path.join(tempDir, `platform-tools-${platform}.zip`);

	try {
		// Download
		await downloadFile(url, zipPath);

		// Extract
		await extractZip(zipPath, tempDir);

		// Copy files to target
		await copyFiles(tempDir, binDir, files);

		// Make executable on Unix
		if (platform !== "windows") {
			for (const file of files) {
				makeExecutable(path.join(binDir, file));
			}
		}

		logDetail(`✓ ${platform} resources exported to: ${binDir}`);
	} finally {
		// Clean up temp directory
		await cleanupDir(tempDir);
	}
}

// ============================================================================
// License
// ============================================================================

function createLicenseFile(targetDir: string): void {
	const licensesDir = path.join(targetDir, "licenses");
	mkdirSync(licensesDir, { recursive: true });

	const apacheLicense = `Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/

Android Debug Bridge (ADB) is licensed under the Apache License 2.0.

For full license text, see:
https://www.apache.org/licenses/LICENSE-2.0

These binaries are downloaded from Google's official Android SDK repository:
https://developer.android.com/studio/releases/platform-tools

Source code is available at:
https://android.googlesource.com/platform/packages/modules/adb/
`;

	const licensePath = path.join(licensesDir, "APACHE-2.0.txt");
	writeFileSync(licensePath, apacheLicense);
	logInfo(`✓ Created license file: ${licensePath}`);
}

// ============================================================================
// Main
// ============================================================================

async function main(): Promise<void> {
	const args = process.argv.slice(2);

	setNamespace("adb-resources");

	if (args.includes("-v") || args.includes("--verbose")) {
		setLogLevel("debug");
	} else {
		setLogLevel("info");
	}

	if (args.length === 0 || args.includes("--help") || args.includes("-h")) {
		printUsage();
		process.exit(args.length === 0 ? 1 : 0);
	}

	const targetPath = path.resolve(args[0]);

	// Check for optional platform argument
	let platforms: PlatformType[];
	if (args.includes("--all")) {
		platforms = ["windows", "darwin", "linux"];
	} else if (args.includes("--platform")) {
		const platformIdx = args.indexOf("--platform");
		const platformArg = args[platformIdx + 1] as PlatformType;
		if (!["windows", "darwin", "linux"].includes(platformArg)) {
			logError(`Invalid platform: ${platformArg}`);
			logError("Valid platforms: windows, darwin, linux");
			process.exit(1);
		}
		platforms = [platformArg];
	} else {
		// Default to current platform
		const currentPlatform = process.platform === "win32" ? "windows" : process.platform;
		if (!["windows", "darwin", "linux"].includes(currentPlatform)) {
			logError(`Unsupported platform: ${currentPlatform}`);
			process.exit(1);
		}
		platforms = [currentPlatform as PlatformType];
	}

	logDataObject("ADB Resources Export", { Target: targetPath, Platforms: platforms.join(", ") });

	// Create target directory
	mkdirSync(targetPath, { recursive: true });

	// Export resources for each platform
	for (const platform of platforms) {
		await exportPlatformResources(platform, targetPath);
	}

	// Create license file
	logInfo("");
	createLicenseFile(targetPath);

	// Clean up temp directory
	await cleanupDir(path.join(targetPath, ".temp"));

	logDataObject("Export Complete", { ResourcesExportedTo: targetPath });
}

main().catch((error) => {
	logErrorObject(error, "Export failed");
	process.exit(1);
});
