/**
 * Magic byte detector for identifying actual file types from content
 * Prevents file type spoofing attacks
 */

export interface MagicByteSignature {
  mime: string;
  ext: string;
  bytes: number[];
  offset?: number;
}

export class MagicByteDetector {
  // Common file signatures (magic bytes)
  private static readonly SIGNATURES: MagicByteSignature[] = [
    // PNG: 89 50 4E 47 8D 0A 1A 0A
    { mime: 'image/png', ext: '.png', bytes: [0x89, 0x50, 0x4e, 0x47, 0x8d, 0x0a, 0x1a, 0x0a] },
    
    // JPEG: FF D8 FF
    { mime: 'image/jpeg', ext: '.jpg', bytes: [0xff, 0xd8, 0xff], offset: 0 },
    
    // GIF87a: 47 49 46 38 37 61
    { mime: 'image/gif', ext: '.gif', bytes: [0x47, 0x49, 0x46, 0x38, 0x37, 0x61] },
    
    // GIF89a: 47 49 46 38 39 61
    { mime: 'image/gif', ext: '.gif', bytes: [0x47, 0x49, 0x46, 0x38, 0x39, 0x61] },
    
    // SVG: 3C 3F 78 6D 6C (<?xml) or 3C 73 76 67 (<svg)
    { mime: 'image/svg+xml', ext: '.svg', bytes: [0x3c, 0x3f, 0x78, 0x6d, 0x6c] },
    { mime: 'image/svg+xml', ext: '.svg', bytes: [0x3c, 0x73, 0x76, 0x67] },
    
    // PDF: 25 50 44 46 (%PDF)
    { mime: 'application/pdf', ext: '.pdf', bytes: [0x25, 0x50, 0x44, 0x46] },
    
    // ZIP: 50 4B 03 04 (PK..)
    { mime: 'application/zip', ext: '.zip', bytes: [0x50, 0x4b, 0x03, 0x04] },
    
    // DOCX/XLSX (ZIP-based): 50 4B 03 04
    { mime: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', ext: '.docx', bytes: [0x50, 0x4b, 0x03, 0x04] },
    { mime: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', ext: '.xlsx', bytes: [0x50, 0x4b, 0x03, 0x04] },
  ];

  /**
   * Detect file type from magic bytes
   * @param buffer - File content as Uint8Array
   * @returns Detected MIME type and extension, or null if not recognized
   */
  static detectFileType(buffer: Uint8Array): { mime: string; ext: string } | null {
    if (!buffer || buffer.length === 0) {
      return null;
    }

    // Check each signature
    for (const sig of this.SIGNATURES) {
      const offset = sig.offset || 0;
      
      // Check if buffer has enough bytes
      if (buffer.length < offset + sig.bytes.length) {
        continue;
      }

      // Check if bytes match
      let match = true;
      for (let i = 0; i < sig.bytes.length; i++) {
        if (buffer[offset + i] !== sig.bytes[i]) {
          match = false;
          break;
        }
      }

      if (match) {
        return { mime: sig.mime, ext: sig.ext };
      }
    }

    // Additional check: look for text content that looks like XML/SVG/HTML
    // This catches files that might not have standard magic bytes
    try {
      const textContent = new TextDecoder().decode(buffer.slice(0, 1000));
      
      // Check for SVG content
      if (/<svg[\s>]/i.test(textContent) || /<\?xml[\s>]/i.test(textContent)) {
        return { mime: 'image/svg+xml', ext: '.svg' };
      }
      
      // Check for HTML content
      if (/<html[\s>]/i.test(textContent) || /<!DOCTYPE\s+html/i.test(textContent)) {
        return { mime: 'text/html', ext: '.html' };
      }
    } catch (e) {
      // If text decoding fails, continue
    }

    return null;
  }

  /**
   * Validate that declared MIME type matches actual file content
   * @param buffer - File content
   * @param declaredMime - MIME type declared by browser/user
   * @returns true if types match, false if mismatch or cannot be determined
   */
  static validateMimeTypeMatch(buffer: Uint8Array, declaredMime: string): boolean {
    const detected = this.detectFileType(buffer);
    
    // If we can't detect the type from magic bytes, reject it for image files
    // (images should always have recognizable magic bytes)
    if (!detected) {
      // For image files, if we can't detect magic bytes, it's suspicious
      if (declaredMime.startsWith('image/')) {
        return false;
      }
      // For other types, allow it (might be a valid file we don't recognize)
      return true;
    }

    // Check for exact match
    if (detected.mime === declaredMime) {
      return true;
    }

    // Check for wildcard match (e.g., image/*)
    const [declaredType] = declaredMime.split('/');
    const [detectedType] = detected.mime.split('/');
    
    if (declaredType === detectedType && declaredType !== '*') {
      return true;
    }

    // Mismatch detected
    return false;
  }

  /**
   * Get human-readable file type description
   * @param mime - MIME type
   * @returns Description of file type
   */
  static getFileTypeDescription(mime: string): string {
    const descriptions: Record<string, string> = {
      'image/png': 'PNG Image',
      'image/jpeg': 'JPEG Image',
      'image/gif': 'GIF Image',
      'image/svg+xml': 'SVG Image',
      'application/pdf': 'PDF Document',
      'application/zip': 'ZIP Archive',
      'application/vnd.openxmlformats-officedocument.wordprocessingml.document': 'Word Document',
      'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': 'Excel Spreadsheet',
    };

    return descriptions[mime] || mime;
  }
}
