/**
 * ClamAV Scan Wrapper
 * Low-level ClamAV communication using TCP mode via clamscan npm package.
 * Scans files, extracts virus signatures, and pings daemon health.
 * 
 * IMPORTANT: Do not cache the scanner instance — each scan creates a fresh
 * connection to avoid stale TCP socket issues.
 */

import net from 'net';
import { getToggleStatus } from './clamavToggleManager';
import { logSecurityEvent, SecurityEventType } from '../logging/securityEventLogger';

// ── Types ──

export interface ClamScanResult {
    isInfected: boolean;
    virusNames: string[];
    scanned: boolean;
    skipped: boolean;
    scanDuration: number;
    rawOutput: string;
    error?: string;
}

export interface ClamDaemonStatus {
    available: boolean;
    host: string;
    port: number;
    responseTime: number;
    error?: string;
}

// ── Configuration ──

const CLAMD_HOST = process.env.CLAMD_HOST || 'localhost';
const CLAMD_PORT = parseInt(process.env.CLAMD_PORT || '3310', 10);
const CLAMD_TIMEOUT = parseInt(process.env.CLAMD_TIMEOUT_MS || '30000', 10);

// ── Scanner Factory (fresh instance per scan) ──

async function createScanner(): Promise<any> {
    const NodeClamscan = (await import('clamscan')).default;
    const clamscan = await new NodeClamscan().init({
        removeInfected: false,
        debugMode: false,
        scanRecursively: false,
        clamdscan: {
            host: CLAMD_HOST,
            port: CLAMD_PORT,
            timeout: CLAMD_TIMEOUT,
            localFallback: false,
            multiscan: false,
            active: true,
        },
        preference: 'clamdscan',
    });
    return clamscan;
}

// ── Core Functions ──

/**
 * Scan a file for malware using ClamAV daemon.
 * Creates a fresh scanner connection for each scan to avoid stale sockets.
 */
export async function scanFile(filePath: string): Promise<ClamScanResult> {
    const startTime = Date.now();

    // Check if scanning is enabled via admin toggle
    const toggleStatus = getToggleStatus();
    if (!toggleStatus.enabled) {
        console.log('[ClamAV] Scanning disabled via toggle — skipping scan');
        return {
            isInfected: false,
            virusNames: [],
            scanned: false,
            skipped: true,
            scanDuration: 0,
            rawOutput: 'Scanning disabled by administrator',
        };
    }

    // Check if ClamAV is enabled via env
    if (process.env.ENABLE_CLAMAV === 'false') {
        console.log('[ClamAV] Disabled via ENABLE_CLAMAV=false — skipping scan');
        return {
            isInfected: false,
            virusNames: [],
            scanned: false,
            skipped: true,
            scanDuration: 0,
            rawOutput: 'ClamAV disabled via ENABLE_CLAMAV env',
        };
    }

    try {
        console.log(`[ClamAV] Scanning file: ${filePath}`);
        const scanner = await createScanner();
        const scanResult = await scanner.isInfected(filePath);
        const scanDuration = Date.now() - startTime;

        console.log(`[ClamAV] Raw scan result:`, JSON.stringify({
            isInfected: scanResult.isInfected,
            file: scanResult.file,
            viruses: scanResult.viruses,
        }));

        // IMPORTANT: clamscan can return null for isInfected when the daemon
        // fails to respond properly. Treat null as a scan failure, not clean.
        if (scanResult.isInfected === null || scanResult.isInfected === undefined) {
            console.error('[ClamAV] Scan returned null/undefined — treating as scan failure (fail-secure)');
            logSecurityEvent(SecurityEventType.MALWARE_SCAN_ERROR, {
                filePath,
                error: 'ClamAV returned null result — possible daemon connection issue',
                scanDuration,
            });
            return {
                isInfected: false,
                virusNames: [],
                scanned: false,
                skipped: false,
                scanDuration,
                rawOutput: 'ClamAV returned null/undefined isInfected',
                error: 'Scan result was inconclusive — ClamAV daemon may be unreachable',
            };
        }

        const result: ClamScanResult = {
            isInfected: scanResult.isInfected === true,
            virusNames: scanResult.viruses || [],
            scanned: true,
            skipped: false,
            scanDuration,
            rawOutput: `File: ${scanResult.file}, Infected: ${scanResult.isInfected}, Viruses: ${(scanResult.viruses || []).join(', ')}`,
        };

        // Log the scan event
        if (result.isInfected) {
            console.log(`[ClamAV] ⛔ MALWARE DETECTED in ${filePath}: ${result.virusNames.join(', ')}`);
            logSecurityEvent(SecurityEventType.MALWARE_DETECTED, {
                filePath,
                virusNames: result.virusNames,
                scanDuration,
            });
        } else {
            console.log(`[ClamAV] ✅ File clean: ${filePath} (${scanDuration}ms)`);
            logSecurityEvent(SecurityEventType.MALWARE_SCAN_CLEAN, {
                filePath,
                scanDuration,
            });
        }

        return result;
    } catch (error: any) {
        const scanDuration = Date.now() - startTime;
        console.error(`[ClamAV] ❌ Scan error for ${filePath}:`, error.message);

        logSecurityEvent(SecurityEventType.MALWARE_SCAN_ERROR, {
            filePath,
            error: error.message,
            scanDuration,
        });

        return {
            isInfected: false,
            virusNames: [],
            scanned: false,
            skipped: false,
            scanDuration,
            rawOutput: '',
            error: error.message,
        };
    }
}

/**
 * Ping ClamAV daemon via TCP socket to check availability
 */
export async function pingDaemon(): Promise<ClamDaemonStatus> {
    const startTime = Date.now();

    return new Promise((resolve) => {
        const socket = new net.Socket();
        const timeout = 5000;

        socket.setTimeout(timeout);

        socket.connect(CLAMD_PORT, CLAMD_HOST, () => {
            // Send PING command
            socket.write('zPING\0');
        });

        socket.on('data', (data) => {
            const response = data.toString().trim();
            socket.destroy();
            resolve({
                available: response === 'PONG',
                host: CLAMD_HOST,
                port: CLAMD_PORT,
                responseTime: Date.now() - startTime,
            });
        });

        socket.on('error', (error: any) => {
            socket.destroy();
            resolve({
                available: false,
                host: CLAMD_HOST,
                port: CLAMD_PORT,
                responseTime: Date.now() - startTime,
                error: error.message,
            });
        });

        socket.on('timeout', () => {
            socket.destroy();
            resolve({
                available: false,
                host: CLAMD_HOST,
                port: CLAMD_PORT,
                responseTime: Date.now() - startTime,
                error: 'Connection timed out',
            });
        });
    });
}
