/**
 * Sanitization Middleware
 * Sanitizes string inputs in req.body and req.query to prevent stored XSS.
 * 
 * Uses TWO strategies:
 * 1. STRICT — strips ALL HTML tags (for normal text fields like names, emails, titles)
 * 2. PERMISSIVE — allows safe formatting tags (for rich text fields like description, message, comments)
 * 
 * This middleware runs AFTER body parsing and BEFORE route handlers.
 * File upload routes (multipart) are skipped — those are handled
 * by the malwareScan middleware pipeline.
 */

import { Request, Response, NextFunction } from 'express';
import sanitizeHtml from 'sanitize-html';

/**
 * Fields that intentionally store HTML from rich text editors.
 * These get PERMISSIVE sanitization (safe formatting tags allowed).
 * All other string fields get STRICT sanitization (all tags stripped).
 */
const RICH_TEXT_FIELDS = new Set([
    'description',
    'requestDescription',
    'message',
    'content',
    'comments',
    'rejectionReason',
    'pauseReason',
    'conclusionRemark',
    'aiGeneratedRemark',
    'finalRemark',
    'closingRemarks',
    'effectiveFinalRemark',
    'keyDiscussionPoints',
    'keyPoints',
    'remarksText',
    'remark',
    'remarks',
    'feedback',
    'note',
    'notes',
    'skipReason',
]);

// Strict config: zero allowed tags, zero allowed attributes
const strictSanitizeConfig: sanitizeHtml.IOptions = {
    allowedTags: [],
    allowedAttributes: {},
    allowedIframeHostnames: [],
    disallowedTagsMode: 'discard',
    nonTextTags: ['script', 'style', 'iframe', 'embed', 'object'],
};

// Permissive config: allow safe formatting tags from rich text editors
// Blocks dangerous elements (script, iframe, object, embed, form, input)
const permissiveSanitizeConfig: sanitizeHtml.IOptions = {
    allowedTags: [
        // Text formatting
        'p', 'br', 'b', 'i', 'u', 'em', 'strong', 's', 'strike', 'del', 'sub', 'sup', 'mark', 'small',
        // Headings
        'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
        // Lists
        'ul', 'ol', 'li',
        // Block elements
        'blockquote', 'pre', 'code', 'hr', 'div', 'span',
        // Tables
        'table', 'thead', 'tbody', 'tfoot', 'tr', 'th', 'td', 'caption', 'colgroup', 'col',
        // Links (href checked below)
        'a',
        // Images (src checked below)
        'img',
    ],
    allowedAttributes: {
        'a': ['href', 'title', 'target', 'rel'],
        'img': ['src', 'alt', 'title', 'width', 'height'],
        'td': ['colspan', 'rowspan', 'style'],
        'th': ['colspan', 'rowspan', 'style'],
        'span': ['class', 'style'],
        'div': ['class', 'style'],
        'pre': ['class', 'style'],
        'code': ['class', 'style'],
        'p': ['class', 'style'],
        'h1': ['class', 'style'],
        'h2': ['class', 'style'],
        'h3': ['class', 'style'],
        'h4': ['class', 'style'],
        'h5': ['class', 'style'],
        'h6': ['class', 'style'],
        'ul': ['class', 'style'],
        'ol': ['class', 'style', 'start', 'type'],
        'li': ['class', 'style'],
        'blockquote': ['class', 'style'],
        'table': ['class', 'style'],
    },
    allowedSchemes: ['http', 'https', 'mailto'],
    allowedIframeHostnames: [],
    disallowedTagsMode: 'discard',
    nonTextTags: ['script', 'style', 'iframe', 'embed', 'object', 'applet', 'form', 'input', 'textarea', 'select', 'button'],
};

/**
 * Recursively sanitize all string values in an object or array
 * Uses the field key to decide strict vs permissive sanitization
 */
function sanitizeValue(value: any, fieldKey?: string): any {
    if (typeof value === 'string') {
        const isRichTextField = fieldKey && RICH_TEXT_FIELDS.has(fieldKey);
        const config = isRichTextField ? permissiveSanitizeConfig : strictSanitizeConfig;
        return sanitizeHtml(value, config);
    }
    if (Array.isArray(value)) {
        return value.map((item) => sanitizeValue(item, fieldKey));
    }
    if (value !== null && typeof value === 'object') {
        return sanitizeObject(value);
    }
    return value;
}

/**
 * Sanitize all string properties of an object (recursively)
 * Passes the key name to sanitizeValue so it can choose the right config
 */
function sanitizeObject(obj: Record<string, any>): Record<string, any> {
    const sanitized: Record<string, any> = {};
    for (const key of Object.keys(obj)) {
        sanitized[key] = sanitizeValue(obj[key], key);
    }
    return sanitized;
}

/**
 * Express middleware that sanitizes req.body and req.query
 * Skips multipart/form-data requests (file uploads handled by malwareScan)
 */
export const sanitizationMiddleware = (req: Request, _res: Response, next: NextFunction): void => {
    try {
        // Skip multipart requests — file uploads are sanitized by the malware scan pipeline
        const contentType = req.headers['content-type'] || '';
        if (contentType.includes('multipart/form-data')) {
            return next();
        }

        // Sanitize req.body (POST/PUT/PATCH payloads)
        if (req.body && typeof req.body === 'object' && Object.keys(req.body).length > 0) {
            req.body = sanitizeObject(req.body);
        }

        // Sanitize req.query (GET query parameters) — always strict (no HTML in query params)
        if (req.query && typeof req.query === 'object' && Object.keys(req.query).length > 0) {
            const strictQuery: Record<string, any> = {};
            for (const key of Object.keys(req.query)) {
                const val = req.query[key];
                if (typeof val === 'string') {
                    strictQuery[key] = sanitizeHtml(val, strictSanitizeConfig);
                } else {
                    strictQuery[key] = val;
                }
            }
            req.query = strictQuery as any;
        }

        next();
    } catch (error) {
        // If sanitization fails for any reason, don't block the request —
        // downstream validation (Zod) will catch malformed input
        console.warn('Sanitization middleware warning:', error instanceof Error ? error.message : 'Unknown error');
        next();
    }
};
