/**
 * Crypto Fallback for Old Linux Kernels
 *
 * The getrandom() syscall was added in Linux 3.17. On older kernels (like 3.10.x),
 * Node.js crypto functions may fail with "getrandom() failed to provide entropy".
 *
 * This module provides fallback implementations that read from /dev/urandom directly
 * when running on kernels older than 3.17.
 */

import { existsSync, openSync, readSync, closeSync } from 'node:fs';
import os from 'node:os';
import { randomBytes } from 'node:crypto';

// Cache kernel version check result
let needsFallback: boolean | null = null;
let fallbackInitialized = false;

/**
 * Parse Linux kernel version string (e.g., "3.10.108" -> { major: 3, minor: 10, patch: 108 })
 */
function parseKernelVersion(release: string): { major: number; minor: number; patch: number } | null {
	const match = release.match(/^(\d+)\.(\d+)\.(\d+)/);
	if (!match) return null;
	return {
		major: parseInt(match[1], 10),
		minor: parseInt(match[2], 10),
		patch: parseInt(match[3], 10)
	};
}

/**
 * Check if kernel version is older than 3.17 (when getrandom() was added)
 */
function isOldKernel(): boolean {
	const release = os.release();
	const version = parseKernelVersion(release);

	if (!version) {
		// Can't parse version, assume modern kernel
		return false;
	}

	// getrandom() was added in Linux 3.17
	if (version.major < 3) return true;
	if (version.major === 3 && version.minor < 17) return true;

	return false;
}

/**
 * Check if we're on Linux (only Linux has kernel version concerns)
 */
function isLinux(): boolean {
	return os.platform() === 'linux';
}

/**
 * Determine if we need to use the fallback (cached)
 */
function checkNeedsFallback(): boolean {
	if (needsFallback !== null) return needsFallback;

	if (!isLinux()) {
		needsFallback = false;
		return false;
	}

	const oldKernel = isOldKernel();
	if (oldKernel) {
		console.log(`[Crypto] Detected old Linux kernel (${os.release()}), using /dev/urandom fallback`);
		needsFallback = true;
	} else {
		needsFallback = false;
	}

	return needsFallback;
}

/**
 * Read random bytes from /dev/urandom (synchronous)
 */
function readFromUrandom(size: number): Buffer {
	const buffer = Buffer.alloc(size);
	const fd = openSync('/dev/urandom', 'r');
	try {
		readSync(fd, buffer, 0, size, null);
	} finally {
		closeSync(fd);
	}
	return buffer;
}

/**
 * Initialize the crypto fallback - call this early at startup
 * Returns true if fallback is needed, false otherwise
 */
export function initCryptoFallback(): boolean {
	if (fallbackInitialized) return needsFallback ?? false;

	const release = os.release();
	const platform = os.platform();
	const useFallback = checkNeedsFallback();

	if (useFallback) {
		console.log(`[Crypto] Kernel: ${release} (old kernel detected, using /dev/urandom fallback)`);

		// Verify /dev/urandom exists
		if (!existsSync('/dev/urandom')) {
			console.error('[Crypto] FATAL: /dev/urandom not found, cannot provide entropy');
			throw new Error('/dev/urandom not available');
		}

		// Test that we can read from it
		try {
			const testBytes = readFromUrandom(8);
			if (testBytes.length !== 8) {
				throw new Error('Failed to read expected bytes');
			}
			console.log('[Crypto] /dev/urandom fallback initialized successfully');
		} catch (err) {
			const errorMsg = err instanceof Error ? err.message : String(err);
			console.error('[Crypto] FATAL: Failed to read from /dev/urandom:', errorMsg);
			throw err;
		}
	} else {
		console.log(`[Crypto] Kernel: ${platform === 'linux' ? release : platform} (using native crypto)`);
	}

	fallbackInitialized = true;
	return useFallback;
}

/**
 * Generate cryptographically secure random bytes
 * Uses /dev/urandom on old kernels, native crypto otherwise
 */
export function secureRandomBytes(size: number): Buffer {
	if (checkNeedsFallback()) {
		return readFromUrandom(size);
	}

	// Use native crypto on modern kernels
	return randomBytes(size);
}

/**
 * Fill a Uint8Array with cryptographically secure random values
 * Compatible with crypto.getRandomValues() API
 */
export function secureGetRandomValues<T extends ArrayBufferView>(array: T): T {
	if (checkNeedsFallback()) {
		const bytes = readFromUrandom(array.byteLength);
		const target = new Uint8Array(array.buffer, array.byteOffset, array.byteLength);
		target.set(bytes);
		return array;
	}

	// Use native crypto on modern kernels
	return crypto.getRandomValues(array);
}

/**
 * Generate a random UUID (v4)
 * Compatible with crypto.randomUUID() API
 */
export function secureRandomUUID(): string {
	if (checkNeedsFallback()) {
		// Generate 16 random bytes
		const bytes = readFromUrandom(16);

		// Set version (4) and variant (RFC 4122)
		bytes[6] = (bytes[6] & 0x0f) | 0x40; // Version 4
		bytes[8] = (bytes[8] & 0x3f) | 0x80; // Variant 10

		// Convert to UUID string
		const hex = bytes.toString('hex');
		return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;
	}

	// Use native crypto on modern kernels
	return crypto.randomUUID();
}

/**
 * Check if running on an old kernel that needs the fallback
 */
export function usingFallback(): boolean {
	return checkNeedsFallback();
}

/**
 * Get kernel version info (useful for diagnostics)
 */
export function getKernelInfo(): { release: string; needsFallback: boolean } {
	return {
		release: os.release(),
		needsFallback: checkNeedsFallback()
	};
}
