/**
 * Comprehensive client-side file upload validation
 * Prevents XSS attacks through file uploads
 */

export interface FileValidationOptions {
  maxSizeMB?: number;
  allowedMimeTypes?: string[];
  allowedExtensions?: string[];
}

export interface FileValidationResult {
  isValid: boolean;
  error?: string;
  warnings?: string[];
}

export class FileUploadValidator {
  private static readonly DEFAULT_MAX_SIZE_MB = 500;
  private static readonly DANGEROUS_EXTENSIONS = [
    ".exe",
    ".bat",
    ".cmd",
    ".com",
    ".pif",
    ".scr",
    ".vbs",
    ".js",
    ".jar",
    ".sh",
    ".bash",
    ".py",
    ".pl",
    ".php",
    ".asp",
    ".aspx",
    ".jsp",
    ".cfm",
    ".cgi",
    ".dll",
    ".so",
  ];

  /**
   * Validate file size
   */
  static validateFileSize(
    file: File,
    maxSizeMB?: number,
  ): FileValidationResult {
    const max = maxSizeMB || this.DEFAULT_MAX_SIZE_MB;
    const fileSizeMB = file.size / (1024 * 1024);

    if (fileSizeMB > max) {
      return {
        isValid: false,
        error: `File size (${fileSizeMB.toFixed(2)}MB) exceeds maximum allowed size (${max}MB)`,
      };
    }

    return { isValid: true };
  }

  /**
   * Validate file extension
   */
  static validateFileExtension(
    file: File,
    allowedExtensions?: string[],
  ): FileValidationResult {
    const fileName = file.name.toLowerCase();
    const fileExtension = fileName.substring(fileName.lastIndexOf("."));

    // Check for dangerous extensions
    if (this.DANGEROUS_EXTENSIONS.includes(fileExtension)) {
      return {
        isValid: false,
        error: `File extension ${fileExtension} is not allowed for security reasons`,
      };
    }

    // Check against allowed extensions if specified
    if (allowedExtensions && allowedExtensions.length > 0) {
      const normalizedAllowed = allowedExtensions.map((ext) =>
        ext.startsWith(".") ? ext.toLowerCase() : `.${ext.toLowerCase()}`,
      );

      if (!normalizedAllowed.includes(fileExtension)) {
        return {
          isValid: false,
          error: `File extension ${fileExtension} is not allowed. Allowed: ${normalizedAllowed.join(", ")}`,
        };
      }
    }

    return { isValid: true };
  }

  /**
   * Validate MIME type
   */
  static validateMimeType(
    file: File,
    allowedMimeTypes?: string[],
  ): FileValidationResult {
    if (!allowedMimeTypes || allowedMimeTypes.length === 0) {
      return { isValid: true };
    }

    // Check exact match
    if (allowedMimeTypes.includes(file.type)) {
      return { isValid: true };
    }

    // Check wildcard match (e.g., image/*)
    const wildcardMatch = allowedMimeTypes.some((allowed) => {
      if (allowed.endsWith("/*")) {
        const prefix = allowed.substring(0, allowed.length - 2);
        return file.type.startsWith(prefix);
      }
      return false;
    });

    if (wildcardMatch) {
      return { isValid: true };
    }

    return {
      isValid: false,
      error: `File type ${file.type} is not allowed. Allowed types: ${allowedMimeTypes.join(", ")}`,
    };
  }

  /**
   * Validate file name for XSS
   */
  static validateFileName(file: File): FileValidationResult {
    const fileName = file.name;

    // Check for suspicious patterns
    const suspiciousPatterns = [
      /[<>:"\/\\|?*]/g, // Invalid filename characters
      /javascript:/i, // JavaScript protocol
      /data:/i, // Data URI
      /vbscript:/i, // VBScript protocol
      /on\w+\s*=/i, // Event handlers
      /<script/i, // Script tags (with < to avoid false positives)
    ];

    for (const pattern of suspiciousPatterns) {
      if (pattern.test(fileName)) {
        return {
          isValid: false,
          error: `File name contains suspicious characters or patterns: ${fileName}`,
        };
      }
    }

    // Check for null bytes
    if (fileName.includes("\0")) {
      return {
        isValid: false,
        error: "File name contains null bytes",
      };
    }

    // Check for excessive length
    if (fileName.length > 255) {
      return {
        isValid: false,
        error: `File name is too long (${fileName.length} > 255 characters)`,
      };
    }

    return { isValid: true };
  }

  /**
   * Comprehensive file validation
   */
  static validateFile(
    file: File,
    options: FileValidationOptions = {},
  ): FileValidationResult {
    // Validate file exists
    if (!file) {
      return {
        isValid: false,
        error: "No file provided",
      };
    }

    // Validate file name
    const nameValidation = this.validateFileName(file);
    if (!nameValidation.isValid) {
      return nameValidation;
    }

    // Validate file size
    const sizeValidation = this.validateFileSize(file, options.maxSizeMB);
    if (!sizeValidation.isValid) {
      return sizeValidation;
    }

    // Validate file extension
    const extensionValidation = this.validateFileExtension(
      file,
      options.allowedExtensions,
    );
    if (!extensionValidation.isValid) {
      return extensionValidation;
    }

    // Validate MIME type
    const mimeValidation = this.validateMimeType(
      file,
      options.allowedMimeTypes,
    );
    if (!mimeValidation.isValid) {
      return mimeValidation;
    }

    return { isValid: true };
  }

  /**
   * Validate multiple files
   */
  static validateFiles(
    files: File[],
    options: FileValidationOptions = {},
  ): { valid: File[]; invalid: Array<{ file: File; error: string }> } {
    const valid: File[] = [];
    const invalid: Array<{ file: File; error: string }> = [];

    for (const file of files) {
      const result = this.validateFile(file, options);
      if (result.isValid) {
        valid.push(file);
      } else {
        invalid.push({
          file,
          error: result.error || "Validation failed",
        });
      }
    }

    return { valid, invalid };
  }

  /**
   * Get file type category
   */
  static getFileCategory(
    file: File,
  ): "image" | "document" | "archive" | "other" {
    const mimeType = file.type.toLowerCase();

    if (mimeType.startsWith("image/")) return "image";
    if (
      mimeType.includes("pdf") ||
      mimeType.includes("word") ||
      mimeType.includes("sheet")
    ) {
      return "document";
    }
    if (
      mimeType.includes("zip") ||
      mimeType.includes("rar") ||
      mimeType.includes("7z")
    ) {
      return "archive";
    }

    return "other";
  }

  /**
   * Sanitize file name for safe display
   */
  static sanitizeFileName(fileName: string): string {
    return fileName
      .replace(/[<>:"\/\\|?*]/g, "_")
      .replace(/\0/g, "")
      .substring(0, 255);
  }
}
