/**
 * Malware Scan Middleware
 * Express middleware that intercepts file uploads, triggers ClamAV scan,
 * and blocks infected files. Uses temp file approach to work with memory storage.
 *
 * Flow:
 *   multer (memory storage) → malwareScanMiddleware → controller
 *     ↓
 *   Write buffer to temp file → ClamAV scan → Delete temp file
 *     ↓
 *   Clean → attach result to req, call next()
 *   Infected → return 403
 *   Scan error → return 503 (fail-secure)
 *   Skipped (disabled) → log, call next()
 */

import { Request, Response, NextFunction } from 'express';
import fs from 'fs';
import path from 'path';
import os from 'os';
import { v4 as uuidv4 } from 'uuid';
import { scanFile, ClamScanResult } from '../services/clamav/clamavScanWrapper';
import { scanContentForXSS, ContentScanResult } from '../services/fileUpload/contentXSSScanner';
import { logSecurityEvent, SecurityEventType } from '../services/logging/securityEventLogger';
import { validateFile } from '../services/fileUpload/fileValidationService';

// ── Extend Express Request ──

declare global {
    namespace Express {
        interface Request {
            malwareScanResult?: ClamScanResult;
            contentScanResult?: ContentScanResult;
            scanEventId?: string;
        }
    }
}

// ── Temp file helpers ──

function writeTempFile(buffer: Buffer, originalName: string): string {
    const tempDir = path.join(os.tmpdir(), 'clamav-scan');
    if (!fs.existsSync(tempDir)) {
        fs.mkdirSync(tempDir, { recursive: true });
    }

    const ext = path.extname(originalName);
    const tempPath = path.join(tempDir, `${uuidv4()}${ext}`);
    fs.writeFileSync(tempPath, buffer);
    return tempPath;
}

function deleteTempFile(tempPath: string): void {
    try {
        if (fs.existsSync(tempPath)) {
            fs.unlinkSync(tempPath);
        }
    } catch (error) {
        console.error('[MalwareScan] Failed to delete temp file:', tempPath, error);
    }
}

// ── Middleware ──

/**
 * Malware scan middleware for single file uploads (multer.single)
 * Works with memory storage — writes buffer to temp → scans → deletes temp
 */
export async function malwareScanMiddleware(req: Request, res: Response, next: NextFunction): Promise<void> {
    // Skip if no file uploaded
    const file = req.file;
    if (!file) {
        console.log('[MalwareScan] No file attached — skipping scan');
        return next();
    }

    console.log(`[MalwareScan] 🔒 Scanning single file: ${file.originalname} (${file.size} bytes, ${file.mimetype})`);
    const scanEventId = uuidv4();
    req.scanEventId = scanEventId;

    // Handle the async scan
    await performScan(file, scanEventId, req, res, next);
}

/**
 * Malware scan middleware for multiple file uploads (multer.array / multer.fields)
 * Scans all files and blocks if ANY file is infected
 */
export async function malwareScanMultipleMiddleware(req: Request, res: Response, next: NextFunction): Promise<void> {
    // Handle multer.array()
    const files = req.files;
    if (!files || (Array.isArray(files) && files.length === 0)) {
        console.log('[MalwareScan] No files attached — skipping multi-scan');
        return next();
    }
    console.log(`[MalwareScan] 🔒 Multi-file scan started`);

    const scanEventId = uuidv4();
    req.scanEventId = scanEventId;

    // Handle array of files
    if (Array.isArray(files)) {
        await performMultiScan(files, scanEventId, req, res, next);
        return;
    }

    // Handle multer.fields() — object with field names as keys
    const allFiles: Express.Multer.File[] = [];
    const filesObj = files as { [fieldname: string]: Express.Multer.File[] };
    for (const fieldFiles of Object.values(filesObj)) {
        allFiles.push(...fieldFiles);
    }

    if (allFiles.length === 0) {
        return next();
    }

    await performMultiScan(allFiles, scanEventId, req, res, next);
}

// ── Core scan logic ──

async function performScan(
    file: Express.Multer.File,
    scanEventId: string,
    req: Request,
    res: Response,
    next: NextFunction
): Promise<void> {
    let tempPath: string | null = null;

    try {
        // Step 0: Pre-scan file validation (extension, MIME, magic bytes, blocked patterns)
        const maxSizeMB = parseInt(process.env.MAX_FILE_SIZE_MB || '50', 10);
        const validation = await validateFile(
            file.originalname,
            file.mimetype,
            file.buffer || null,
            file.size,
            maxSizeMB,
        );

        if (!validation.valid) {
            console.log(`[MalwareScan] ⛔ File validation FAILED for "${file.originalname}": ${validation.errors.join('; ')}`);
            logSecurityEvent(SecurityEventType.FILE_UPLOAD_BLOCKED, {
                scanEventId,
                originalName: file.originalname,
                size: file.size,
                mimeType: file.mimetype,
                reason: 'FILE_VALIDATION_FAILED',
                errors: validation.errors,
            });

            res.status(403).json({
                success: false,
                error: 'FILE_VALIDATION_FAILED',
                message: `File rejected: ${validation.errors[0]}`,
                scanEventId,
                details: {
                    errors: validation.errors,
                    warnings: validation.warnings,
                },
            });
            return;
        }

        if (validation.warnings.length > 0) {
            console.log(`[MalwareScan] ⚠️ File validation warnings for "${file.originalname}": ${validation.warnings.join('; ')}`);
        }

        // 🟢 SANITIZATION: Update originalname with the sanitized version
        if (validation.sanitizedFilename) {
            file.originalname = validation.sanitizedFilename;
        }

        // Check if we have a buffer (memory storage) or path (disk storage)
        if (file.buffer) {
            tempPath = writeTempFile(file.buffer, file.originalname);
        } else if (file.path) {
            tempPath = file.path;
        } else {
            console.warn('[MalwareScan] No file buffer or path available, skipping scan');
            return next();
        }

        // 1. ClamAV Malware Scan
        const malwareResult = await scanFile(tempPath);
        req.malwareScanResult = malwareResult;

        // If infected, block immediately
        if (malwareResult.isInfected) {
            logSecurityEvent(SecurityEventType.FILE_UPLOAD_BLOCKED, {
                scanEventId,
                originalName: file.originalname,
                size: file.size,
                mimeType: file.mimetype,
                virusNames: malwareResult.virusNames,
                scanDuration: malwareResult.scanDuration,
            });

            // Delete temp file
            if (tempPath && file.buffer) {
                deleteTempFile(tempPath);
            }

            res.status(403).json({
                success: false,
                error: 'MALWARE_DETECTED',
                message: 'File contains malware and was blocked',
                scanEventId,
                details: {
                    scanEngine: 'ClamAV',
                    signatures: malwareResult.virusNames,
                },
            });
            return;
        }

        // If ClamAV had an error (not skipped, but failed to scan), fail-secure
        if (!malwareResult.scanned && !malwareResult.skipped && malwareResult.error) {
            if (tempPath && file.buffer) {
                deleteTempFile(tempPath);
            }

            res.status(503).json({
                success: false,
                error: 'SCAN_UNAVAILABLE',
                message: 'Antivirus scanning is temporarily unavailable. Please try again later.',
                scanEventId,
            });
            return;
        }

        // 2. Content XSS Scan
        const contentToScan = file.buffer || fs.readFileSync(tempPath);
        const contentResult = scanContentForXSS(contentToScan, file.originalname, file.mimetype);
        req.contentScanResult = contentResult;

        // If XSS threats found, block
        if (!contentResult.safe) {
            logSecurityEvent(SecurityEventType.CONTENT_XSS_DETECTED, {
                scanEventId,
                originalName: file.originalname,
                threats: contentResult.threats.map(t => t.description),
                severity: contentResult.severity,
                scanType: contentResult.scanType,
            });

            if (tempPath && file.buffer) {
                deleteTempFile(tempPath);
            }

            res.status(403).json({
                success: false,
                error: 'CONTENT_THREAT_DETECTED',
                message: 'File contains potentially malicious content and was blocked',
                scanEventId,
                details: {
                    scanType: contentResult.scanType,
                    threats: contentResult.threats.map(t => ({
                        description: t.description,
                        severity: t.severity,
                    })),
                    overallSeverity: contentResult.severity,
                },
            });
            return;
        }

        // Clean temp file (only if we created it)
        if (tempPath && file.buffer) {
            deleteTempFile(tempPath);
        }

        // All clear — proceed to controller
        next();
    } catch (error: any) {
        console.error('[MalwareScan] Unexpected error during scan:', error.message);

        if (tempPath && file.buffer) {
            deleteTempFile(tempPath);
        }

        // Fail-secure on unexpected errors
        res.status(503).json({
            success: false,
            error: 'SCAN_ERROR',
            message: 'An error occurred during security scanning. Please try again.',
            scanEventId,
        });
    }
}

async function performMultiScan(
    files: Express.Multer.File[],
    scanEventId: string,
    req: Request,
    res: Response,
    next: NextFunction
): Promise<void> {
    try {
        for (const file of files) {
            let tempPath: string | null = null;

            // Step 0: Pre-scan file validation
            const maxSizeMB = parseInt(process.env.MAX_FILE_SIZE_MB || '50', 10);
            const validation = await validateFile(
                file.originalname,
                file.mimetype,
                file.buffer || null,
                file.size,
                maxSizeMB,
            );

            if (!validation.valid) {
                console.log(`[MalwareScan] ⛔ File validation FAILED for "${file.originalname}": ${validation.errors.join('; ')}`);
                logSecurityEvent(SecurityEventType.FILE_UPLOAD_BLOCKED, {
                    scanEventId,
                    originalName: file.originalname,
                    reason: 'FILE_VALIDATION_FAILED',
                    errors: validation.errors,
                });

                res.status(403).json({
                    success: false,
                    error: 'FILE_VALIDATION_FAILED',
                    message: `File "${file.originalname}" rejected: ${validation.errors[0]}`,
                    scanEventId,
                    details: { errors: validation.errors, warnings: validation.warnings },
                });
                return;
            }

            // 🟢 SANITIZATION: Update originalname with the sanitized version for each file in the array
            if (validation.sanitizedFilename) {
                file.originalname = validation.sanitizedFilename;
            }

            // Write to temp if memory storage
            if (file.buffer) {
                tempPath = writeTempFile(file.buffer, file.originalname);
            } else if (file.path) {
                tempPath = file.path;
            } else {
                continue; // Skip files without buffer or path
            }

            // ClamAV scan
            const malwareResult = await scanFile(tempPath);

            if (malwareResult.isInfected) {
                logSecurityEvent(SecurityEventType.FILE_UPLOAD_BLOCKED, {
                    scanEventId,
                    originalName: file.originalname,
                    virusNames: malwareResult.virusNames,
                });

                if (tempPath && file.buffer) deleteTempFile(tempPath);

                res.status(403).json({
                    success: false,
                    error: 'MALWARE_DETECTED',
                    message: `File "${file.originalname}" contains malware and was blocked`,
                    scanEventId,
                    details: {
                        scanEngine: 'ClamAV',
                        signatures: malwareResult.virusNames,
                        infectedFile: file.originalname,
                    },
                });
                return;
            }

            // ClamAV error — fail-secure
            if (!malwareResult.scanned && !malwareResult.skipped && malwareResult.error) {
                if (tempPath && file.buffer) deleteTempFile(tempPath);

                res.status(503).json({
                    success: false,
                    error: 'SCAN_UNAVAILABLE',
                    message: 'Antivirus scanning is temporarily unavailable.',
                    scanEventId,
                });
                return;
            }

            // Content XSS scan
            const contentToScan = file.buffer || fs.readFileSync(tempPath);
            const contentResult = scanContentForXSS(contentToScan, file.originalname, file.mimetype);

            if (!contentResult.safe) {
                logSecurityEvent(SecurityEventType.CONTENT_XSS_DETECTED, {
                    scanEventId,
                    originalName: file.originalname,
                    threats: contentResult.threats.map(t => t.description),
                    severity: contentResult.severity,
                });

                if (tempPath && file.buffer) deleteTempFile(tempPath);

                res.status(403).json({
                    success: false,
                    error: 'CONTENT_THREAT_DETECTED',
                    message: `File "${file.originalname}" contains potentially malicious content`,
                    scanEventId,
                    details: {
                        scanType: contentResult.scanType,
                        threats: contentResult.threats.map(t => ({
                            description: t.description,
                            severity: t.severity,
                        })),
                    },
                });
                return;
            }

            // Clean temp file
            if (tempPath && file.buffer) deleteTempFile(tempPath);
        }

        // All files clean
        next();
    } catch (error: any) {
        console.error('[MalwareScan] Error during multi-file scan:', error.message);

        res.status(503).json({
            success: false,
            error: 'SCAN_ERROR',
            message: 'An error occurred during security scanning.',
            scanEventId,
        });
    }
}
