import fs from 'fs';
import path from 'path';
import { Storage } from '@google-cloud/storage';
import { config } from '../config/env';
import { logger, StructuredLogger } from '../utils/logger';
import { uploadMonitoringService } from './uploadMonitoringService';

export interface FileInfo {
  originalName: string;
  filename: string;
  path: string;
  size: number;
  mimetype: string;
  uploadedAt: Date;
  url?: string;
  gcsPath?: string;
}

export interface StorageResult {
  success: boolean;
  fileInfo?: FileInfo;
  error?: string;
}

export interface GCSFileInfo {
  name: string;
  size: number;
  contentType: string;
  timeCreated: Date;
  timeUpdated: Date;
  metadata?: { [key: string]: string };
}

class FileStorageService {
  private storage: Storage;
  private bucketName: string;
  private maxRetries: number = 3;
  private retryDelay: number = 1000; // 1 second

  constructor() {
    this.bucketName = config.googleCloud.gcsBucketName;
    
    // Initialize Google Cloud Storage
    this.storage = new Storage({
      keyFilename: config.googleCloud.applicationCredentials,
      projectId: config.googleCloud.projectId,
    });

    logger.info('Google Cloud Storage service initialized', {
      bucketName: this.bucketName,
      projectId: config.googleCloud.projectId,
    });
  }

  /**
   * Store a file using Google Cloud Storage
   */
  async storeFile(file: any, userId: string): Promise<StorageResult> {
    const startTime = Date.now();
    const structuredLogger = new StructuredLogger();
    
    try {
      const result = await this.storeFileGCS(file, userId);
      
      const processingTime = Date.now() - startTime;
      
      // Track storage operation
      const eventData: any = {
        userId,
        fileInfo: {
          originalName: file.originalname,
          size: file.size,
          mimetype: file.mimetype,
        },
        status: result.success ? 'success' : 'failed',
        stage: 'file_storage',
        processingTime,
      };

      if (!result.success) {
        eventData.error = {
          message: result.error || 'Storage operation failed',
          type: 'storage_error',
          code: 'STORAGE_ERROR',
        };
      }

      uploadMonitoringService.trackUploadEvent(eventData);

      structuredLogger.storageOperation(
        'store_file',
        file.originalname,
        result.success,
        result.success ? undefined : new Error(result.error)
      );

      return result;
    } catch (error) {
      const processingTime = Date.now() - startTime;
      
      // Track storage operation failure
      uploadMonitoringService.trackUploadEvent({
        userId,
        fileInfo: {
          originalName: file.originalname,
          size: file.size,
          mimetype: file.mimetype,
        },
        status: 'failed',
        stage: 'file_storage',
        error: {
          message: error instanceof Error ? error.message : 'Unknown error',
          type: 'storage_error',
          code: 'STORAGE_ERROR',
        },
        processingTime,
      });

      structuredLogger.storageOperation(
        'store_file',
        file.originalname,
        false,
        error
      );

      logger.error('File storage error:', error);
      return {
        success: false,
        error: 'Failed to store file in Google Cloud Storage',
      };
    }
  }

  /**
   * Store file in Google Cloud Storage
   */
  private async storeFileGCS(file: any, userId: string): Promise<StorageResult> {
    const bucket = this.storage.bucket(this.bucketName);
    const timestamp = Date.now();
    const gcsFileName = `uploads/${userId}/${timestamp}-${file.originalname}`;
    
    // Use file buffer if available (from memory storage), otherwise read from path
    let fileBuffer: Buffer;
    if (file.buffer) {
      fileBuffer = file.buffer;
    } else if (file.path) {
      fileBuffer = fs.readFileSync(file.path);
    } else {
      return {
        success: false,
        error: 'No file buffer or path available',
      };
    }

    try {
      // Upload file to GCS with retry logic
      await this.retryOperation(
        async () => {
          const fileUpload = bucket.file(gcsFileName);
          await fileUpload.save(fileBuffer, {
            metadata: {
              contentType: file.mimetype,
              metadata: {
                originalName: file.originalname,
                userId: userId,
                uploadedAt: new Date().toISOString(),
                size: file.size.toString(),
              },
            },
            resumable: true,
          });
          return fileUpload;
        },
        'upload file to GCS'
      );

      // Get the public URL
      const publicUrl = `https://storage.googleapis.com/${this.bucketName}/${gcsFileName}`;

      const fileInfo: FileInfo = {
        originalName: file.originalname,
        filename: file.filename || file.originalname,
        path: gcsFileName, // GCS path instead of local path
        size: file.size,
        mimetype: file.mimetype,
        uploadedAt: new Date(),
        url: publicUrl,
        gcsPath: gcsFileName,
      };

      // Clean up local temporary file if it exists
      if (file.path && fs.existsSync(file.path)) {
        fs.unlinkSync(file.path);
      }

      logger.info(`File stored in GCS: ${file.originalname}`, {
        userId,
        gcsPath: gcsFileName,
        fileSize: file.size,
        publicUrl,
      });

      return {
        success: true,
        fileInfo,
      };
    } catch (error) {
      logger.error('GCS file storage error:', error);
      return {
        success: false,
        error: `Failed to store file in GCS: ${error instanceof Error ? error.message : 'Unknown error'}`,
      };
    }
  }

  /**
   * Get file from Google Cloud Storage
   */
  async getFile(filePath: string): Promise<Buffer | null> {
    try {
      const bucket = this.storage.bucket(this.bucketName);
      const file = bucket.file(filePath);

      // Check if file exists
      const [exists] = await file.exists();
      if (!exists) {
        logger.warn(`File not found in GCS: ${filePath}`);
        return null;
      }

      // Download file with retry logic
      const [fileBuffer] = await this.retryOperation(
        async () => file.download(),
        'download file from GCS'
      );

      logger.info(`File retrieved from GCS: ${filePath}`, {
        size: fileBuffer.length,
      });

      return fileBuffer;
    } catch (error) {
      logger.error(`Error reading file from GCS: ${filePath}`, error);
      return null;
    }
  }

  /**
   * Delete file from Google Cloud Storage
   */
  async deleteFile(filePath: string): Promise<boolean> {
    try {
      const bucket = this.storage.bucket(this.bucketName);
      const file = bucket.file(filePath);

      // Check if file exists
      const [exists] = await file.exists();
      if (!exists) {
        logger.warn(`File not found in GCS for deletion: ${filePath}`);
        return false;
      }

      // Delete file with retry logic
      await this.retryOperation(
        async () => file.delete(),
        'delete file from GCS'
      );

      logger.info(`File deleted from GCS: ${filePath}`);
      return true;
    } catch (error) {
      logger.error(`Error deleting file from GCS: ${filePath}`, error);
      return false;
    }
  }

  /**
   * Get file info from Google Cloud Storage
   */
  async getFileInfo(filePath: string): Promise<FileInfo | null> {
    try {
      const bucket = this.storage.bucket(this.bucketName);
      const file = bucket.file(filePath);

      // Check if file exists
      const [exists] = await file.exists();
      if (!exists) {
        return null;
      }

      // Get file metadata with retry logic
      const [metadata] = await this.retryOperation(
        async () => file.getMetadata(),
        'get file metadata from GCS'
      );

      const filename = path.basename(filePath);
      const publicUrl = `https://storage.googleapis.com/${this.bucketName}/${filePath}`;

      return {
        originalName: (metadata.metadata?.['originalName'] as string) || filename,
        filename,
        path: filePath,
        size: parseInt(String(metadata.size || '0')) || 0,
        mimetype: metadata.contentType || 'application/octet-stream',
        uploadedAt: new Date(metadata.timeCreated || new Date()),
        url: publicUrl,
        gcsPath: filePath,
      };
    } catch (error) {
      logger.error(`Error getting file info from GCS: ${filePath}`, error);
      return null;
    }
  }

  /**
   * Check if file exists in Google Cloud Storage
   */
  async fileExists(filePath: string): Promise<boolean> {
    try {
      const bucket = this.storage.bucket(this.bucketName);
      const file = bucket.file(filePath);
      const [exists] = await file.exists();
      return exists;
    } catch (error) {
      logger.error(`Error checking file existence in GCS: ${filePath}`, error);
      return false;
    }
  }

  /**
   * Get file size from Google Cloud Storage
   */
  async getFileSize(filePath: string): Promise<number | null> {
    try {
      const bucket = this.storage.bucket(this.bucketName);
      const file = bucket.file(filePath);

      // Check if file exists
      const [exists] = await file.exists();
      if (!exists) {
        return null;
      }

      // Get file metadata with retry logic
      const [metadata] = await this.retryOperation(
        async () => file.getMetadata(),
        'get file size from GCS'
      );

      return parseInt(String(metadata.size || '0')) || 0;
    } catch (error) {
      logger.error(`Error getting file size from GCS: ${filePath}`, error);
      return null;
    }
  }

  /**
   * List files in Google Cloud Storage directory
   */
  async listFiles(prefix: string = '', maxResults: number = 100): Promise<GCSFileInfo[]> {
    try {
      const bucket = this.storage.bucket(this.bucketName);
      const [files] = await this.retryOperation(
        async () => bucket.getFiles({ prefix, maxResults }),
        'list files from GCS'
      );

      return files.map(file => ({
        name: file.name,
        size: parseInt(String(file.metadata.size || '0')) || 0,
        contentType: file.metadata.contentType || 'application/octet-stream',
        timeCreated: new Date(file.metadata.timeCreated || new Date()),
        timeUpdated: new Date(file.metadata.updated || new Date()),
        metadata: (file.metadata.metadata as { [key: string]: string }) || {},
      }));
    } catch (error) {
      logger.error(`Error listing files from GCS with prefix: ${prefix}`, error);
      return [];
    }
  }

  /**
   * Clean up old files from Google Cloud Storage (older than specified days)
   */
  async cleanupOldFiles(prefix: string = 'uploads/', daysOld: number = 7): Promise<number> {
    try {
      const bucket = this.storage.bucket(this.bucketName);
      const cutoffTime = new Date(Date.now() - (daysOld * 24 * 60 * 60 * 1000));
      let deletedCount = 0;

      // List all files with the prefix
      const [files] = await bucket.getFiles({ prefix });

      for (const file of files) {
        const timeCreated = new Date(file.metadata.timeCreated || new Date());
        
        if (timeCreated < cutoffTime) {
          await this.retryOperation(
            async () => file.delete(),
            'delete old file from GCS'
          );
          deletedCount++;
          logger.info(`Cleaned up old file from GCS: ${file.name}`);
        }
      }

      logger.info(`GCS cleanup completed: ${deletedCount} files deleted with prefix ${prefix}`);
      return deletedCount;
    } catch (error) {
      logger.error(`Error during GCS file cleanup with prefix: ${prefix}`, error);
      return 0;
    }
  }

  /**
   * Get storage statistics from Google Cloud Storage
   */
  async getStorageStats(prefix: string = 'uploads/'): Promise<{
    totalFiles: number;
    totalSize: number;
    averageFileSize: number;
  }> {
    try {
      const bucket = this.storage.bucket(this.bucketName);
      const [files] = await bucket.getFiles({ prefix });

      let totalSize = 0;
      const fileCount = files.length;

      for (const file of files) {
        totalSize += parseInt(String(file.metadata.size || '0')) || 0;
      }

      return {
        totalFiles: fileCount,
        totalSize,
        averageFileSize: fileCount > 0 ? totalSize / fileCount : 0,
      };
    } catch (error) {
      logger.error(`Error getting GCS storage stats with prefix: ${prefix}`, error);
      return {
        totalFiles: 0,
        totalSize: 0,
        averageFileSize: 0,
      };
    }
  }

  /**
   * Generate signed URL for temporary access to private files
   */
  async generateSignedUrl(filePath: string, expirationMinutes: number = 60): Promise<string | null> {
    try {
      const bucket = this.storage.bucket(this.bucketName);
      const file = bucket.file(filePath);

      // Check if file exists
      const [exists] = await file.exists();
      if (!exists) {
        logger.warn(`File not found in GCS for signed URL: ${filePath}`);
        return null;
      }

      // Generate signed URL with retry logic
      const [signedUrl] = await this.retryOperation(
        async () => file.getSignedUrl({
          version: 'v4',
          action: 'read',
          expires: Date.now() + (expirationMinutes * 60 * 1000),
        }),
        'generate signed URL from GCS'
      );

      logger.info(`Generated signed URL for file: ${filePath}`, {
        expirationMinutes,
      });

      return signedUrl;
    } catch (error) {
      logger.error(`Error generating signed URL for file: ${filePath}`, error);
      return null;
    }
  }

  /**
   * Generate signed upload URL for direct client uploads
   */
  async generateSignedUploadUrl(filePath: string, contentType: string, expirationMinutes: number = 60): Promise<string> {
    try {
      const bucket = this.storage.bucket(this.bucketName);
      const file = bucket.file(filePath);

      // Generate signed upload URL with retry logic
      const [signedUrl] = await this.retryOperation(
        async () => file.getSignedUrl({
          version: 'v4',
          action: 'write',
          expires: Date.now() + (expirationMinutes * 60 * 1000),
          contentType: contentType,
        }),
        'generate signed upload URL from GCS'
      );

      logger.info(`Generated signed upload URL for file: ${filePath}`, {
        contentType,
        expirationMinutes,
      });

      return signedUrl;
    } catch (error) {
      logger.error(`Error generating signed upload URL for file: ${filePath}`, error);
      throw new Error(`Failed to generate upload URL: ${error instanceof Error ? error.message : 'Unknown error'}`);
    }
  }

  /**
   * Copy file within Google Cloud Storage
   */
  async copyFile(sourcePath: string, destinationPath: string): Promise<boolean> {
    try {
      const bucket = this.storage.bucket(this.bucketName);
      const sourceFile = bucket.file(sourcePath);
      const destinationFile = bucket.file(destinationPath);

      // Check if source file exists
      const [exists] = await sourceFile.exists();
      if (!exists) {
        logger.warn(`Source file not found in GCS for copy: ${sourcePath}`);
        return false;
      }

      // Copy file with retry logic
      await this.retryOperation(
        async () => sourceFile.copy(destinationFile),
        'copy file in GCS'
      );

      logger.info(`File copied in GCS: ${sourcePath} -> ${destinationPath}`);
      return true;
    } catch (error) {
      logger.error(`Error copying file in GCS: ${sourcePath} -> ${destinationPath}`, error);
      return false;
    }
  }

  /**
   * Move file within Google Cloud Storage (copy + delete)
   */
  async moveFile(sourcePath: string, destinationPath: string): Promise<boolean> {
    try {
      // Copy the file first
      const copySuccess = await this.copyFile(sourcePath, destinationPath);
      if (!copySuccess) {
        return false;
      }

      // Delete the original file
      const deleteSuccess = await this.deleteFile(sourcePath);
      if (!deleteSuccess) {
        // If delete fails, we should clean up the copied file
        await this.deleteFile(destinationPath);
        return false;
      }

      logger.info(`File moved in GCS: ${sourcePath} -> ${destinationPath}`);
      return true;
    } catch (error) {
      logger.error(`Error moving file in GCS: ${sourcePath} -> ${destinationPath}`, error);
      return false;
    }
  }

  /**
   * Retry operation with exponential backoff
   */
  private async retryOperation<T>(
    operation: () => Promise<T>,
    operationName: string,
    retries: number = this.maxRetries
  ): Promise<T> {
    for (let attempt = 1; attempt <= retries; attempt++) {
      try {
        return await operation();
      } catch (error) {
        if (attempt === retries) {
          throw error;
        }

        const delay = this.retryDelay * Math.pow(2, attempt - 1);
        logger.warn(`GCS operation failed (attempt ${attempt}/${retries}): ${operationName}`, {
          error: error instanceof Error ? error.message : 'Unknown error',
          retryDelay: delay,
        });

        await new Promise(resolve => setTimeout(resolve, delay));
      }
    }

    throw new Error(`GCS operation failed after ${retries} attempts: ${operationName}`);
  }

  /**
   * Test GCS connection and bucket access
   */
  async testConnection(): Promise<boolean> {
    try {
      const bucket = this.storage.bucket(this.bucketName);
      const [exists] = await bucket.exists();
      
      if (!exists) {
        logger.error(`GCS bucket does not exist: ${this.bucketName}`);
        return false;
      }

      logger.info(`GCS connection test successful for bucket: ${this.bucketName}`);
      return true;
    } catch (error) {
      logger.error('GCS connection test failed:', error);
      return false;
    }
  }
}

export const fileStorageService = new FileStorageService();
export default fileStorageService; 