/**
 * Upload Service for LocalGreenChain
 * Agent 3: File Upload & Storage System
 *
 * Core upload functionality with validation and processing
 */

import { v4 as uuidv4 } from 'crypto';
import {
  FileMetadata,
  FileCategory,
  UploadOptions,
  UploadResult,
  PresignedUrlRequest,
  PresignedUrlResponse,
  StorageProviderInterface,
  ALLOWED_IMAGE_TYPES,
  ALLOWED_DOCUMENT_TYPES,
  DEFAULT_MAX_FILE_SIZE,
  DEFAULT_MAX_IMAGE_SIZE,
  ImageSize,
} from './types';
import { getStorageConfig, storageConfig } from './config';
import { S3StorageProvider } from './providers/s3';
import { LocalStorageProvider } from './providers/local';
import { ImageProcessor } from './imageProcessor';

// Generate a UUID v4
function generateId(): string {
  const bytes = require('crypto').randomBytes(16);
  bytes[6] = (bytes[6] & 0x0f) | 0x40;
  bytes[8] = (bytes[8] & 0x3f) | 0x80;
  return bytes.toString('hex').replace(/(.{8})(.{4})(.{4})(.{4})(.{12})/, '$1-$2-$3-$4-$5');
}

class UploadService {
  private provider: StorageProviderInterface;
  private imageProcessor: ImageProcessor;

  constructor() {
    const config = getStorageConfig();

    switch (config.provider) {
      case 's3':
      case 'r2':
      case 'minio':
        this.provider = new S3StorageProvider(config);
        break;
      case 'local':
      default:
        this.provider = new LocalStorageProvider(config);
        break;
    }

    this.imageProcessor = new ImageProcessor();
  }

  /**
   * Generate a unique file key with path
   */
  private generateFileKey(filename: string, category: FileCategory): string {
    const id = generateId();
    const ext = filename.split('.').pop()?.toLowerCase() || 'bin';
    const date = new Date();
    const year = date.getFullYear();
    const month = String(date.getMonth() + 1).padStart(2, '0');

    return `${category}/${year}/${month}/${id}.${ext}`;
  }

  /**
   * Validate file type and size
   */
  private validateFile(
    buffer: Buffer,
    mimeType: string,
    options: UploadOptions
  ): { valid: boolean; error?: string } {
    const isImage = ALLOWED_IMAGE_TYPES.includes(mimeType);
    const isDocument = ALLOWED_DOCUMENT_TYPES.includes(mimeType);

    // Check allowed types based on category
    if (options.category === 'plant-photo' || options.category === 'avatar') {
      if (!isImage) {
        return { valid: false, error: 'Only image files are allowed for this category' };
      }
    }

    if (options.category === 'document' || options.category === 'certificate') {
      if (!isDocument && !isImage) {
        return { valid: false, error: 'Only documents and images are allowed for this category' };
      }
    }

    // Check custom allowed types
    if (options.allowedMimeTypes && options.allowedMimeTypes.length > 0) {
      if (!options.allowedMimeTypes.includes(mimeType)) {
        return { valid: false, error: `File type ${mimeType} is not allowed` };
      }
    }

    // Check file size
    const maxSize = options.maxSizeBytes ||
      (isImage ? DEFAULT_MAX_IMAGE_SIZE : DEFAULT_MAX_FILE_SIZE);

    if (buffer.length > maxSize) {
      const maxSizeMB = (maxSize / (1024 * 1024)).toFixed(1);
      return { valid: false, error: `File size exceeds maximum of ${maxSizeMB}MB` };
    }

    return { valid: true };
  }

  /**
   * Upload a file with processing
   */
  async upload(
    buffer: Buffer,
    originalName: string,
    mimeType: string,
    options: UploadOptions
  ): Promise<UploadResult> {
    // Validate file
    const validation = this.validateFile(buffer, mimeType, options);
    if (!validation.valid) {
      return { success: false, error: validation.error };
    }

    const isImage = ALLOWED_IMAGE_TYPES.includes(mimeType);
    const fileKey = this.generateFileKey(originalName, options.category);
    const fileId = generateId();

    try {
      let processedBuffer = buffer;
      let width: number | undefined;
      let height: number | undefined;
      let exifData: FileMetadata['exifData'];
      const urls: Partial<Record<ImageSize, string>> = {};

      // Process images
      if (isImage && options.generateThumbnails !== false) {
        // Get image metadata
        const metadata = await this.imageProcessor.getMetadata(buffer);
        width = metadata.width;
        height = metadata.height;

        // Extract EXIF data
        exifData = await this.imageProcessor.extractExif(buffer);

        // Optimize original image
        const optimized = await this.imageProcessor.optimize(buffer);
        processedBuffer = optimized;

        // Generate thumbnails
        const thumbnails = await this.imageProcessor.generateThumbnails(buffer);

        // Upload thumbnails
        for (const [size, thumbBuffer] of Object.entries(thumbnails)) {
          const thumbKey = fileKey.replace(/\.[^.]+$/, `-${size}.webp`);
          urls[size as ImageSize] = await this.provider.upload(thumbKey, thumbBuffer, 'image/webp');
        }
      }

      // Upload main file
      const finalMimeType = isImage ? 'image/webp' : mimeType;
      const finalKey = isImage ? fileKey.replace(/\.[^.]+$/, '.webp') : fileKey;
      const mainUrl = await this.provider.upload(finalKey, processedBuffer, finalMimeType);

      urls.original = mainUrl;

      // Create file metadata
      const fileMetadata: FileMetadata = {
        id: fileId,
        filename: finalKey,
        originalName,
        mimeType: finalMimeType,
        size: processedBuffer.length,
        category: options.category,
        uploadedBy: options.userId,
        plantId: options.plantId,
        farmId: options.farmId,
        url: mainUrl,
        thumbnailUrl: urls.thumbnail,
        urls: urls as Record<ImageSize, string>,
        width,
        height,
        exifData,
        createdAt: new Date(),
        updatedAt: new Date(),
      };

      return { success: true, file: fileMetadata };
    } catch (error) {
      console.error('Upload error:', error);
      return {
        success: false,
        error: error instanceof Error ? error.message : 'Upload failed',
      };
    }
  }

  /**
   * Generate a presigned URL for direct upload
   */
  async getPresignedUploadUrl(request: PresignedUrlRequest): Promise<PresignedUrlResponse> {
    const fileKey = this.generateFileKey(request.filename, request.category);
    const expiresIn = request.expiresIn || 3600; // 1 hour default

    const uploadUrl = await this.provider.getPresignedUploadUrl(
      fileKey,
      request.contentType,
      expiresIn
    );

    return {
      uploadUrl,
      fileKey,
      publicUrl: this.provider.getPublicUrl(fileKey),
      expiresAt: new Date(Date.now() + expiresIn * 1000),
    };
  }

  /**
   * Delete a file and its thumbnails
   */
  async delete(fileKey: string): Promise<boolean> {
    try {
      // Delete main file
      await this.provider.delete(fileKey);

      // Delete thumbnails if they exist
      const sizes: ImageSize[] = ['thumbnail', 'small', 'medium', 'large'];
      for (const size of sizes) {
        const thumbKey = fileKey.replace(/\.[^.]+$/, `-${size}.webp`);
        await this.provider.delete(thumbKey);
      }

      return true;
    } catch (error) {
      console.error('Delete error:', error);
      return false;
    }
  }

  /**
   * Get a signed URL for private file access
   */
  async getSignedUrl(fileKey: string, expiresIn = 3600): Promise<string> {
    return this.provider.getSignedUrl(fileKey, expiresIn);
  }

  /**
   * Check if a file exists
   */
  async exists(fileKey: string): Promise<boolean> {
    return this.provider.exists(fileKey);
  }
}

// Singleton instance
let uploadServiceInstance: UploadService | null = null;

export function getUploadService(): UploadService {
  if (!uploadServiceInstance) {
    uploadServiceInstance = new UploadService();
  }
  return uploadServiceInstance;
}

export { UploadService };
