import Config from '#config';

/**
 * Virus Scan Service
 *
 * Pluggable virus scanning using ClamAV (via `clamscan` npm package).
 * When VIRUS_SCAN_ENABLED=true, scans each uploaded file and rejects infected ones.
 * When disabled (default), the scan is a no-op so the app runs without ClamAV installed.
 *
 * Setup:
 *   - Install ClamAV: `apt-get install clamav clamav-daemon` (Linux) or via Homebrew/Chocolatey
 *   - Set VIRUS_SCAN_ENABLED=true in .env
 *   - Optional: CLAM_SOCKET=/var/run/clamav/clamd.ctl or CLAM_HOST=127.0.0.1 / CLAM_PORT=3310
 *   - Optional: CLAM_SCAN_TIMEOUT_MS (default 15000)
 *
 * SECURITY ARCHITECTURE & FAIL-OPEN MODE:
 *
 * FAIL-CLOSED MODE (RECOMMENDED FOR PRODUCTION):
 * - VIRUS_SCAN_FAIL_OPEN=false (default)
 * - If ClamAV is unavailable or scan fails, file upload is rejected
 * - Pro: Maximum security - ensures all files are scanned
 * - Con: Uploads fail if scanner has issues
 * - Use when: Security is critical and scanner reliability is guaranteed
 *
 * FAIL-OPEN MODE (USE WITH CAUTION):
 * - VIRUS_SCAN_FAIL_OPEN=true
 * - If ClamAV is unavailable or scan fails, file upload is allowed
 * - Pro: Uploads succeed even if scanner has issues
 * - Con: Security risk - malicious files could bypass scanning
 * - Use when: Scanner reliability is poor and availability is critical
 * - Mitigation: Implement secondary defenses (file type validation, magic bytes, content scanning)
 *
 * SECURITY RECOMMENDATIONS:
 * 1. Use fail-closed mode in production whenever possible
 * 2. If using fail-open, implement compensating controls:
 *    - Strict file type/extension validation
 *    - Magic byte verification
 *    - Content size limits
 *    - Post-upload scanning with alerts
 * 3. Monitor ClamAV availability and scan success rates
 * 4. Alert immediately if scanner becomes unavailable in fail-closed mode
 * 5. Consider implementing a queue for re-scanning files that bypassed scanning
 */

export type ScanResult = {
	isClean: boolean;
	virusName?: string;
	error?: string;
	scanUnavailable?: boolean;
};

// Type definition for clamscan optional dependency
interface ClamScanInstance {
	isInfected(filePath: string): Promise<{ isInfected: boolean; viruses?: string[] }>;
}

interface ClamScanStatic {
	new (): ClamScanInstance;
	init(options: Record<string, unknown>): Promise<ClamScanInstance>;
}

let _clamav: ClamScanInstance | null = null;

/** Reset the cached ClamAV instance — used in unit tests only. */
export function _resetForTesting(): void {
	_clamav = null;
}

/** Inject a ClamAV instance directly — used in unit tests only. */
export function _setInstanceForTesting(instance: ClamScanInstance): void {
	_clamav = instance;
}

/**
 * Lazily initialise the clamscan instance. Returns null if not enabled or package missing.
 */
async function getClamAV(): Promise<ClamScanInstance | null> {
	if (!Config.virusScan.enabled) return null;

	if (_clamav) return _clamav;

	try {
		// clamscan is an optional runtime dependency — not in package.json
		// eslint-disable-next-line @typescript-eslint/ban-ts-comment
		// @ts-ignore
		const clamscanModule = await import('clamscan');
		const NodeClam = clamscanModule.default as ClamScanStatic;
		const options: Record<string, unknown> = {
			removeInfected: false, // Let the application decide
			scanLog: null,
			clamdscan: {
				active: true,
				socket: Config.virusScan.clamSocket || null,
				host: Config.virusScan.clamHost || '127.0.0.1',
				port: Config.virusScan.clamPort,
				timeout: Config.virusScan.clamScanTimeoutMs,
				localFallback: true,
			},
			preference: 'clamdscan',
		};
		_clamav = await NodeClam.init(options);
		return _clamav;
	} catch {
		// clamscan package not installed or ClamAV not reachable — degrade gracefully
		return null;
	}
}

/**
 * Scan a file for viruses.
 * Returns `{ isClean: true }` immediately if virus scan is disabled or unavailable.
 */
export async function scanFile(filePath: string): Promise<ScanResult> {
	const clam = await getClamAV();
	if (!clam) {
		return { isClean: true };
	}

	try {
		const { isInfected, viruses } = await clam.isInfected(filePath);
		if (isInfected) {
			const virusName = viruses?.[0] ?? 'unknown';
			return { isClean: false, virusName };
		}
		return { isClean: true };
	} catch (err: any) {
		// CQ-05: configurable fail-open for environments without ClamAV (e.g. local dev)
		// VIRUS_SCAN_FAIL_OPEN=true → allow upload on scan error; default is fail-closed (secure)
		if (Config.virusScan.failOpen) {
			return { isClean: true };
		}
		// Fail-closed: scan errors reject the file to prevent malware upload on scanner failure
		return { isClean: false, error: err?.message ?? 'scan error', scanUnavailable: true } as ScanResult;
	}
}
