import { Storage } from '@google-cloud/storage';
import path from 'path';
import fs from 'fs';
import logger from '@utils/logger';
import { UPLOAD_DIR } from '@config/storage';

interface UploadFileOptions {
  filePath?: string;
  buffer?: Buffer;
  originalName: string;
  mimeType: string;
  requestNumber: string; // Request number (e.g., 'REQ-2025-12-0001')
  fileType: 'documents' | 'attachments'; // Type of file: documents or attachments
}

interface UploadResult {
  storageUrl: string;
  filePath: string; // GCS path
  fileName: string; // Generated file name in GCS
}

class GCSStorageService {
  private storage: Storage | null = null;
  private bucketName: string = '';
  private projectId: string = '';

  constructor() {
    // Check if Google Secret Manager should be used
    const useGoogleSecretManager = process.env.USE_GOOGLE_SECRET_MANAGER === 'true';
    
    if (!useGoogleSecretManager) {
      logger.info('[GCS] USE_GOOGLE_SECRET_MANAGER is not enabled. Will use local storage fallback.');
      return;
    }

    this.projectId = process.env.GCP_PROJECT_ID || '';
    this.bucketName = process.env.GCP_BUCKET_NAME || '';
    const keyFilePath = process.env.GCP_KEY_FILE || '';

    if (!this.projectId || !this.bucketName || !keyFilePath) {
      logger.warn('[GCS] GCP configuration missing. File uploads will use local storage fallback.');
      return;
    }

    try {
      // Resolve key file path (can be relative or absolute)
      const resolvedKeyPath = path.isAbsolute(keyFilePath)
        ? keyFilePath
        : path.resolve(process.cwd(), keyFilePath);

      if (!fs.existsSync(resolvedKeyPath)) {
        logger.error(`[GCS] Key file not found at: ${resolvedKeyPath}. Will use local storage fallback.`);
        return;
      }

      this.storage = new Storage({
        projectId: this.projectId,
        keyFilename: resolvedKeyPath,
      });

      logger.info('[GCS] Initialized successfully', {
        projectId: this.projectId,
        bucketName: this.bucketName,
      });
    } catch (error) {
      logger.error('[GCS] Failed to initialize. Will use local storage fallback:', error);
    }
  }

  /**
   * Ensure the bucket exists, create it if it doesn't
   * This is called lazily on first upload
   */
  private async ensureBucketExists(): Promise<void> {
    if (!this.storage) {
      throw new Error('GCS storage not initialized');
    }

    try {
      const bucket = this.storage.bucket(this.bucketName);
      const [exists] = await bucket.exists();

      if (!exists) {
        logger.info(`[GCS] Bucket "${this.bucketName}" does not exist. Creating...`);
        
        // Get region from env or default to asia-south1 (Mumbai)
        const region = process.env.GCP_BUCKET_REGION || 'asia-south1';
        
        // Create bucket with default settings
        // Note: publicAccessPrevention is not set to allow public file access
        // If you need private buckets, set GCP_BUCKET_PUBLIC=false and use signed URLs
        const bucketOptions: any = {
          location: region,
          storageClass: 'STANDARD',
          uniformBucketLevelAccess: true,
        };
        
        // Only enforce public access prevention if explicitly configured
        if (process.env.GCP_BUCKET_PUBLIC === 'false') {
          bucketOptions.publicAccessPrevention = 'enforced';
        }
        
        await bucket.create(bucketOptions);

        logger.info(`[GCS] Bucket "${this.bucketName}" created successfully in region "${region}"`);
      } else {
        logger.debug(`[GCS] Bucket "${this.bucketName}" already exists`);
      }
    } catch (error) {
      logger.error(`[GCS] Failed to check/create bucket "${this.bucketName}":`, error);
      throw error;
    }
  }

  /**
   * Upload a file to Google Cloud Storage
   * @param options File upload options
   * @returns Upload result with storage URL and file path
   */
  async uploadFile(options: UploadFileOptions): Promise<UploadResult> {
    if (!this.storage) {
      throw new Error('GCS storage not initialized. Check GCP configuration.');
    }

    const { filePath, buffer, originalName, mimeType, requestNumber, fileType } = options;

    if (!filePath && !buffer) {
      throw new Error('Either filePath or buffer must be provided');
    }

    if (!requestNumber) {
      throw new Error('Request number is required for file upload');
    }

    try {
      // Ensure bucket exists before uploading
      await this.ensureBucketExists();
      
      // Generate unique file name with original name first for readability
      // Format: originalName-timestamp-hash.ext (e.g., proposal-1766490022228-qjlojs.pdf)
      const timestamp = Date.now();
      const randomHash = Math.random().toString(36).substring(2, 8);
      const safeName = originalName.replace(/[^a-zA-Z0-9._-]/g, '_');
      const extension = path.extname(originalName);
      // Extract name without extension, then add timestamp and hash before extension
      const nameWithoutExt = safeName.substring(0, safeName.length - extension.length);
      const fileName = `${nameWithoutExt}-${timestamp}-${randomHash}${extension}`;
      
      // Build GCS path: requests/{requestNumber}/{fileType}/{fileName}
      // Example: requests/REQ-2025-12-0001/documents/proposal.pdf
      // Example: requests/REQ-2025-12-0001/attachments/approval_note.pdf
      const gcsFilePath = `requests/${requestNumber}/${fileType}/${fileName}`;

      const bucket = this.storage.bucket(this.bucketName);
      const file = bucket.file(gcsFilePath);

      // Upload options
      const uploadOptions: any = {
        metadata: {
          contentType: mimeType,
          metadata: {
            originalName: originalName,
            uploadedAt: new Date().toISOString(),
          },
        },
      };

      // Upload from buffer or file path
      if (buffer) {
        await file.save(buffer, uploadOptions);
      } else if (filePath) {
        await bucket.upload(filePath, {
          destination: gcsFilePath,
          metadata: uploadOptions.metadata,
        });
      }

      // Make file publicly readable (or use signed URLs for private access)
      // Note: This will fail if bucket has publicAccessPrevention enabled
      let publicUrl: string;
      try {
        await file.makePublic();
        // Get public URL
        publicUrl = `https://storage.googleapis.com/${this.bucketName}/${gcsFilePath}`;
      } catch (makePublicError: any) {
        // If making public fails (e.g., public access prevention), use signed URL
        if (makePublicError?.code === 400 || makePublicError?.message?.includes('publicAccessPrevention')) {
          logger.warn('[GCS] Cannot make file public (public access prevention enabled). Using signed URL.');
          publicUrl = await this.getSignedUrl(gcsFilePath, 60 * 24 * 365); // 1 year expiry
        } else {
          throw makePublicError;
        }
      }

      logger.info('[GCS] File uploaded successfully', {
        fileName: originalName,
        gcsPath: gcsFilePath,
        storageUrl: publicUrl,
        size: buffer ? buffer.length : 'unknown',
      });

      return {
        storageUrl: publicUrl,
        filePath: gcsFilePath,
        fileName: fileName,
      };
    } catch (error) {
      logger.error('[GCS] Upload failed:', error);
      throw new Error(`Failed to upload file to GCS: ${error instanceof Error ? error.message : 'Unknown error'}`);
    }
  }

  /**
   * Delete a file from Google Cloud Storage
   * @param gcsFilePath The GCS file path (e.g., 'attachments/file-name.ext')
   */
  async deleteFile(gcsFilePath: string): Promise<void> {
    if (!this.storage) {
      throw new Error('GCS storage not initialized. Check GCP configuration.');
    }

    try {
      const bucket = this.storage.bucket(this.bucketName);
      const file = bucket.file(gcsFilePath);
      await file.delete();

      logger.info('[GCS] File deleted successfully', { gcsPath: gcsFilePath });
    } catch (error) {
      logger.error('[GCS] Delete failed:', error);
      throw new Error(`Failed to delete file from GCS: ${error instanceof Error ? error.message : 'Unknown error'}`);
    }
  }

  /**
   * Get a signed URL for private file access (valid for 1 hour by default)
   * @param gcsFilePath The GCS file path
   * @param expiresInMinutes URL expiration time in minutes (default: 60)
   * @returns Signed URL
   */
  async getSignedUrl(gcsFilePath: string, expiresInMinutes: number = 60): Promise<string> {
    if (!this.storage) {
      throw new Error('GCS storage not initialized. Check GCP configuration.');
    }

    try {
      const bucket = this.storage.bucket(this.bucketName);
      const file = bucket.file(gcsFilePath);

      const [url] = await file.getSignedUrl({
        action: 'read',
        expires: Date.now() + expiresInMinutes * 60 * 1000,
      });

      return url;
    } catch (error) {
      logger.error('[GCS] Failed to generate signed URL:', error);
      throw new Error(`Failed to generate signed URL: ${error instanceof Error ? error.message : 'Unknown error'}`);
    }
  }

  /**
   * Save file to local storage with the same folder structure as GCS
   * This is used as a fallback when GCS is not configured or fails
   * @param options File upload options
   * @returns Upload result with local storage URL and file path
   */
  saveToLocalStorage(options: UploadFileOptions): UploadResult {
    const { buffer, originalName, requestNumber, fileType } = options;

    if (!buffer) {
      throw new Error('Buffer is required for local storage fallback');
    }

    if (!requestNumber) {
      throw new Error('Request number is required for file upload');
    }

    try {
      // Generate unique file name (same format as GCS) - original name first for readability
      // Format: originalName-timestamp-hash.ext (e.g., proposal-1766490022228-qjlojs.pdf)
      const timestamp = Date.now();
      const randomHash = Math.random().toString(36).substring(2, 8);
      const safeName = originalName.replace(/[^a-zA-Z0-9._-]/g, '_');
      const extension = path.extname(originalName);
      // Extract name without extension, then add timestamp and hash before extension
      const nameWithoutExt = safeName.substring(0, safeName.length - extension.length);
      const fileName = `${nameWithoutExt}-${timestamp}-${randomHash}${extension}`;

      // Build local path: uploads/requests/{requestNumber}/{fileType}/{fileName}
      // This matches the GCS structure: requests/{requestNumber}/{fileType}/{fileName}
      const localDir = path.join(UPLOAD_DIR, 'requests', requestNumber, fileType);
      
      // Ensure directory exists
      if (!fs.existsSync(localDir)) {
        fs.mkdirSync(localDir, { recursive: true });
      }

      const localFilePath = path.join(localDir, fileName);
      const relativePath = `requests/${requestNumber}/${fileType}/${fileName}`;

      // Save file to disk
      fs.writeFileSync(localFilePath, buffer);

      // Create URL path (will be served by express.static)
      const storageUrl = `/uploads/${relativePath}`;

      logger.info('[GCS] File saved to local storage (fallback)', {
        fileName: originalName,
        localPath: relativePath,
        storageUrl: storageUrl,
        requestNumber: requestNumber,
      });

      return {
        storageUrl: storageUrl,
        filePath: relativePath, // Store relative path (same format as GCS path)
        fileName: fileName,
      };
    } catch (error) {
      logger.error('[GCS] Local storage save failed:', error);
      throw new Error(`Failed to save file to local storage: ${error instanceof Error ? error.message : 'Unknown error'}`);
    }
  }

  /**
   * Upload file with automatic fallback to local storage
   * If GCS is configured and works, uploads to GCS. Otherwise, saves to local storage.
   * @param options File upload options
   * @returns Upload result with storage URL and file path
   */
  async uploadFileWithFallback(options: UploadFileOptions): Promise<UploadResult> {
    // If GCS is not configured, use local storage directly
    if (!this.isConfigured()) {
      logger.info('[GCS] GCS not configured, using local storage');
      return this.saveToLocalStorage(options);
    }

    // Try GCS upload first
    try {
      return await this.uploadFile(options);
    } catch (gcsError) {
      logger.warn('[GCS] GCS upload failed, falling back to local storage', { error: gcsError });
      // Fallback to local storage
      return this.saveToLocalStorage(options);
    }
  }

  /**
   * Check if GCS is properly configured
   */
  isConfigured(): boolean {
    // Check if Google Secret Manager is enabled
    const useGoogleSecretManager = process.env.USE_GOOGLE_SECRET_MANAGER === 'true';
    if (!useGoogleSecretManager) {
      return false;
    }
    
    return this.storage !== null && this.bucketName !== '' && this.projectId !== '';
  }
}

export const gcsStorageService = new GCSStorageService();
