/**
 * File handling utilities for Entra VerifiedID and other integrations
 * Provides base64 encoding/decoding, validation, and content type detection
 */

import { createHash } from 'crypto';

/**
 * Supported MIME types for document processing
 */
export const SUPPORTED_MIME_TYPES = {
  // Documents
  PDF: 'application/pdf',
  DOCX: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
  DOC: 'application/msword',
  XLSX: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
  XLS: 'application/vnd.ms-excel',
  // Images
  PNG: 'image/png',
  JPEG: 'image/jpeg',
  JPG: 'image/jpg',
  GIF: 'image/gif',
  WEBP: 'image/webp',
  // Text
  TEXT: 'text/plain',
  JSON: 'application/json',
  XML: 'application/xml',
  // Archives
  ZIP: 'application/zip',
  TAR: 'application/x-tar',
  GZIP: 'application/gzip',
} as const;

/**
 * Maximum file size limits (in bytes)
 */
export const FILE_SIZE_LIMITS = {
  SMALL: 1024 * 1024, // 1 MB
  MEDIUM: 10 * 1024 * 1024, // 10 MB
  LARGE: 100 * 1024 * 1024, // 100 MB
  XLARGE: 500 * 1024 * 1024, // 500 MB
} as const;

/**
 * File validation options
 */
export interface FileValidationOptions {
  maxSize?: number;
  allowedMimeTypes?: string[];
  requireMimeType?: boolean;
}

/**
 * File encoding result
 */
export interface FileEncodingResult {
  base64: string;
  mimeType: string;
  size: number;
  hash: string;
}

/**
 * File validation result
 */
export interface FileValidationResult {
  valid: boolean;
  errors: string[];
  mimeType?: string;
  size?: number;
}

/**
 * Encode a file buffer or string to base64
 */
export function encodeFileToBase64(
  file: Buffer | string | Uint8Array,
  mimeType?: string
): string {
  let buffer: Buffer;
  
  if (typeof file === 'string') {
    // If it's already base64, validate and return
    if (isBase64(file)) {
      return file;
    }
    // Otherwise, convert string to buffer
    buffer = Buffer.from(file, 'utf-8');
  } else if (file instanceof Uint8Array) {
    buffer = Buffer.from(file);
  } else {
    buffer = file;
  }

  const base64 = buffer.toString('base64');
  
  // If MIME type is provided, return as data URL
  if (mimeType) {
    return `data:${mimeType};base64,${base64}`;
  }
  
  return base64;
}

/**
 * Decode base64 string to buffer
 */
export function decodeBase64ToBuffer(base64: string): Buffer {
  // Remove data URL prefix if present
  const base64Data = base64.includes(',') 
    ? (base64.split(',')[1] ?? base64)
    : base64;
  
  return Buffer.from(base64Data, 'base64');
}

/**
 * Check if a string is valid base64
 */
export function isBase64(str: string): boolean {
  if (!str || str.length === 0) {
    return false;
  }

  // Remove data URL prefix if present
  const base64Data = str.includes(',') 
    ? (str.split(',')[1] ?? str)
    : str;

  // Base64 regex pattern
  const base64Regex = /^[A-Za-z0-9+/]*={0,2}$/;
  
  if (!base64Regex.test(base64Data)) {
    return false;
  }

  // Check length is multiple of 4
  if (base64Data.length % 4 !== 0) {
    return false;
  }

  // Try to decode
  try {
    Buffer.from(base64Data, 'base64');
    return true;
  } catch {
    return false;
  }
}

/**
 * Detect MIME type from buffer or file extension
 */
export function detectMimeType(
  data: Buffer | string,
  filename?: string
): string {
  // Try to detect from file extension first
  if (filename) {
    const extension = filename.split('.').pop()?.toLowerCase();
    const mimeType = getMimeTypeFromExtension(extension || '');
    if (mimeType) {
      return mimeType;
    }
  }

  // Try to detect from buffer magic bytes
  if (data instanceof Buffer && data.length > 0) {
    const mimeType = detectMimeTypeFromBuffer(data);
    if (mimeType) {
      return mimeType;
    }
  }

  // Default to application/octet-stream
  return 'application/octet-stream';
}

/**
 * Get MIME type from file extension
 */
function getMimeTypeFromExtension(extension: string): string | null {
  const mimeTypes: Record<string, string> = {
    // Documents
    pdf: SUPPORTED_MIME_TYPES.PDF,
    docx: SUPPORTED_MIME_TYPES.DOCX,
    doc: SUPPORTED_MIME_TYPES.DOC,
    xlsx: SUPPORTED_MIME_TYPES.XLSX,
    xls: SUPPORTED_MIME_TYPES.XLS,
    // Images
    png: SUPPORTED_MIME_TYPES.PNG,
    jpg: SUPPORTED_MIME_TYPES.JPG,
    jpeg: SUPPORTED_MIME_TYPES.JPEG,
    gif: SUPPORTED_MIME_TYPES.GIF,
    webp: SUPPORTED_MIME_TYPES.WEBP,
    // Text
    txt: SUPPORTED_MIME_TYPES.TEXT,
    json: SUPPORTED_MIME_TYPES.JSON,
    xml: SUPPORTED_MIME_TYPES.XML,
    // Archives
    zip: SUPPORTED_MIME_TYPES.ZIP,
    tar: SUPPORTED_MIME_TYPES.TAR,
    gz: SUPPORTED_MIME_TYPES.GZIP,
  };

  return mimeTypes[extension.toLowerCase()] || null;
}

/**
 * Detect MIME type from buffer magic bytes
 */
function detectMimeTypeFromBuffer(buffer: Buffer): string | null {
  // Check magic bytes (file signatures)
  if (buffer.length < 4) {
    return null;
  }

  const header = buffer.slice(0, 12);

  // PDF
  if (header.slice(0, 4).toString() === '%PDF') {
    return SUPPORTED_MIME_TYPES.PDF;
  }

  // PNG
  if (header[0] === 0x89 && header[1] === 0x50 && header[2] === 0x4E && header[3] === 0x47) {
    return SUPPORTED_MIME_TYPES.PNG;
  }

  // JPEG
  if (header[0] === 0xFF && header[1] === 0xD8 && header[2] === 0xFF) {
    return SUPPORTED_MIME_TYPES.JPEG;
  }

  // GIF
  if (header.slice(0, 3).toString() === 'GIF') {
    return SUPPORTED_MIME_TYPES.GIF;
  }

  // ZIP (also detects DOCX, XLSX which are ZIP-based)
  if (header[0] === 0x50 && header[1] === 0x4B) {
    // Check if it's a DOCX or XLSX by checking internal structure
    // For simplicity, return ZIP - caller can refine based on filename
    return SUPPORTED_MIME_TYPES.ZIP;
  }

  // JSON (starts with { or [)
  const text = buffer.slice(0, 100).toString('utf-8').trim();
  if (text.startsWith('{') || text.startsWith('[')) {
    try {
      JSON.parse(text);
      return SUPPORTED_MIME_TYPES.JSON;
    } catch {
      // Not valid JSON
    }
  }

  return null;
}

/**
 * Validate a base64-encoded file
 */
export function validateBase64File(
  base64: string,
  options: FileValidationOptions = {}
): FileValidationResult {
  const errors: string[] = [];

  // Check if it's valid base64
  if (!isBase64(base64)) {
    errors.push('Invalid base64 encoding');
    return { valid: false, errors };
  }

  // Decode to get size
  let buffer: Buffer;
  try {
    buffer = decodeBase64ToBuffer(base64);
  } catch (error) {
    errors.push(`Failed to decode base64: ${error instanceof Error ? error.message : String(error)}`);
    return { valid: false, errors };
  }

  const size = buffer.length;

  // Check size limit
  if (options.maxSize && size > options.maxSize) {
    errors.push(`File size (${size} bytes) exceeds maximum allowed size (${options.maxSize} bytes)`);
  }

  // Detect and validate MIME type
  let mimeType: string | undefined;
  try {
    const detected = detectMimeTypeFromBuffer(buffer);
    mimeType = detected ?? undefined;
  } catch {
    // MIME type detection failed, but not critical
  }

  if (options.requireMimeType && !mimeType) {
    errors.push('Could not detect file MIME type');
  }

  if (options.allowedMimeTypes && mimeType) {
    if (!options.allowedMimeTypes.includes(mimeType)) {
      errors.push(`MIME type ${mimeType} is not allowed. Allowed types: ${options.allowedMimeTypes.join(', ')}`);
    }
  }

  return {
    valid: errors.length === 0,
    errors,
    mimeType,
    size,
  };
}

/**
 * Encode file with full metadata
 */
export function encodeFileWithMetadata(
  file: Buffer | string | Uint8Array,
  filename?: string,
  mimeType?: string
): FileEncodingResult {
  let buffer: Buffer;
  
  if (typeof file === 'string') {
    if (isBase64(file)) {
      buffer = decodeBase64ToBuffer(file);
    } else {
      buffer = Buffer.from(file, 'utf-8');
    }
  } else if (file instanceof Uint8Array) {
    buffer = Buffer.from(file);
  } else {
    buffer = file;
  }

  const detectedMimeType = mimeType || detectMimeType(buffer, filename);
  const base64 = encodeFileToBase64(buffer, detectedMimeType);
  const hash = createHash('sha256').update(buffer).digest('hex');

  return {
    base64,
    mimeType: detectedMimeType,
    size: buffer.length,
    hash,
  };
}

/**
 * Sanitize filename for safe storage
 */
export function sanitizeFilename(filename: string): string {
  // Remove path components
  const basename = filename.split(/[/\\]/).pop() || filename;
  
  // Remove or replace unsafe characters
  return basename
    .replace(/[^a-zA-Z0-9._-]/g, '_')
    .replace(/_{2,}/g, '_')
    .replace(/^_+|_+$/g, '')
    .substring(0, 255); // Limit length
}

/**
 * Calculate file hash for integrity verification
 */
export function calculateFileHash(data: Buffer | string, algorithm: 'sha256' | 'sha512' = 'sha256'): string {
  const buffer = typeof data === 'string' 
    ? Buffer.from(data, 'utf-8')
    : data;
  
  return createHash(algorithm).update(buffer).digest('hex');
}

