import { Injectable, Logger, BadRequestException } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { exec } from 'child_process';
import { promisify } from 'util';
import * as fs from 'fs';
import * as path from 'path';
import * as moment from 'moment';

const execPromise = promisify(exec);

@Injectable()
export class BackupService {
  private readonly logger = new Logger(BackupService.name);
  private readonly backupDir: string;
  private readonly maxBackupAge: number = 24 * 60 * 60 * 1000; // 24 hours in milliseconds

  constructor(private configService: ConfigService) {
    // Create backups directory if it doesn't exist
    this.backupDir = path.join(process.cwd(), 'backups');
    if (!fs.existsSync(this.backupDir)) {
      fs.mkdirSync(this.backupDir, { recursive: true });
    }

    // Start cleanup interval - clean old backups every hour
    setInterval(() => {
      this.cleanupOldBackups();
    }, 60 * 60 * 1000); // 1 hour
  }

  /**
   * Create MongoDB backup and return file info for immediate download
   */
  async createBackupForDownload() {
    const timestamp = moment().format('YYYY-MM-DD-HH-mm-ss');
    const backupFileName = `backup-${timestamp}.tar.gz`;
    const backupPath = path.join(this.backupDir, backupFileName);
    const tempDumpDir = path.join(this.backupDir, `dump-${timestamp}`);

    try {
      this.logger.log('Starting database backup process for immediate download...');

      // Step 1: Get MongoDB connection details
      const mongoUri = this.configService.get<string>('MONGO_URI') || 'mongodb://localhost:27017';

      // Step 2: Backup only the 'chat-app' database
      const dbName = 'chat-app';

      this.logger.log(`Creating backup for database: ${dbName}`);

      // Step 3: Run mongodump command - backup only chat-app database
      const dumpCommand = `mongodump --uri="${mongoUri}" --db="${dbName}" --out="${tempDumpDir}"`;

      this.logger.log('Running mongodump for chat-app database...');
      await execPromise(dumpCommand);
      this.logger.log('Mongodump completed successfully');

      // Step 4: Create tar.gz archive
      this.logger.log('Creating compressed archive...');
      // Change to dump directory and tar only the chat-app folder
      const tarCommand = `cd "${tempDumpDir}" && tar -czf "${backupPath}" "${dbName}"`;
      await execPromise(tarCommand);
      this.logger.log('Archive created successfully');

      // Step 5: Get file stats
      const stats = fs.statSync(backupPath);

      // Step 6: Cleanup temporary dump directory (keep the final backup file for download)
      this.logger.log('Cleaning up temporary dump directory...');
      await this.cleanupDumpDir(tempDumpDir);
      this.logger.log('Cleanup completed');

      // Step 7: Return file info for streaming
      return {
        filePath: backupPath,
        fileName: backupFileName,
        database: dbName,
        size: stats.size,
        createdAt: new Date().toISOString(),
      };

    } catch (error) {
      this.logger.error('Backup creation failed:', error);

      // Cleanup on error
      try {
        await this.cleanupDumpDir(tempDumpDir);
        if (fs.existsSync(backupPath)) {
          fs.unlinkSync(backupPath);
        }
      } catch (cleanupError) {
        this.logger.error('Cleanup failed:', cleanupError);
      }

      throw error;
    }
  }

  /**
   * Create MongoDB backup and make it available for download from server (legacy method)
   */
  async createAndDownloadBackup() {
    const result = await this.createBackupForDownload();

    // Generate download URL for compatibility
    const baseUrl = this.configService.get<string>('BASE_URL') || 'http://localhost:3011';
    const downloadUrl = `${baseUrl}/backup/download/${result.fileName}`;

    return {
      success: true,
      message: 'Database backup created successfully',
      database: result.database,
      backup_file: result.fileName,
      download_url: downloadUrl,
      size: `${(result.size / (1024 * 1024)).toFixed(2)} MB`,
      created_at: result.createdAt,
      expires_in: '24 hours', // Backups auto-delete after 24 hours
    };
  }

  /**
   * List all backups available on the server
   */
  async listBackups() {
    try {
      const baseUrl = this.configService.get<string>('BASE_URL') || 'http://localhost:3011';

      // Read all .tar.gz files from backups directory
      const files = fs.readdirSync(this.backupDir)
        .filter(file => file.endsWith('.tar.gz'))
        .map(file => {
          const filePath = path.join(this.backupDir, file);
          const stats = fs.statSync(filePath);

          return {
            file_name: file,
            size: `${(stats.size / (1024 * 1024)).toFixed(2)} MB`,
            created_at: stats.birthtime,
            last_modified: stats.mtime,
            download_url: `${baseUrl}/backup/download/${file}`,
            expires_in: this.getTimeUntilExpiry(stats.mtime),
          };
        })
        .sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime());

      return files;

    } catch (error) {
      this.logger.error('Failed to list backups:', error);
      throw error;
    }
  }

  /**
   * Download a specific backup file
   */
  async downloadBackup(fileName: string) {
    try {
      const filePath = path.join(this.backupDir, fileName);

      // Check if file exists
      if (!fs.existsSync(filePath)) {
        throw new BadRequestException('Backup file not found');
      }

      // Check if file is a .tar.gz file
      if (!fileName.endsWith('.tar.gz')) {
        throw new BadRequestException('Invalid backup file');
      }

      // Check if file is not too old (24 hours)
      const stats = fs.statSync(filePath);
      const age = Date.now() - stats.mtime.getTime();

      if (age > this.maxBackupAge) {
        // Delete old file
        fs.unlinkSync(filePath);
        throw new BadRequestException('Backup file has expired and was deleted');
      }

      return {
        filePath,
        fileName,
        size: stats.size,
        shouldDeleteAfterDownload: false, // Don't delete - let cleanup handle it
      };

    } catch (error) {
      this.logger.error(`Failed to download backup ${fileName}:`, error);
      throw error;
    }
  }

  /**
   * Calculate time until backup expires
   */
  private getTimeUntilExpiry(modifiedTime: Date): string {
    const age = Date.now() - modifiedTime.getTime();
    const remaining = this.maxBackupAge - age;

    if (remaining <= 0) {
      return 'Expired';
    }

    const hours = Math.floor(remaining / (1000 * 60 * 60));
    const minutes = Math.floor((remaining % (1000 * 60 * 60)) / (1000 * 60));

    if (hours > 0) {
      return `${hours}h ${minutes}m`;
    } else {
      return `${minutes}m`;
    }
  }

  /**
   * Cleanup temporary dump directory only (keep final backup file)
   */
  private async cleanupDumpDir(tempDir: string) {
    try {
      if (fs.existsSync(tempDir)) {
        await execPromise(`rm -rf "${tempDir}"`);
        this.logger.log(`Cleaned up temporary directory: ${tempDir}`);
      }
    } catch (error) {
      this.logger.warn('Cleanup warning:', error);
    }
  }

  /**
   * Cleanup old backup files (called automatically every hour)
   */
  private cleanupOldBackups() {
    try {
      const files = fs.readdirSync(this.backupDir)
        .filter(file => file.endsWith('.tar.gz'))
        .map(file => ({
          name: file,
          path: path.join(this.backupDir, file),
          stats: fs.statSync(path.join(this.backupDir, file))
        }));

      let deletedCount = 0;
      for (const file of files) {
        const age = Date.now() - file.stats.mtime.getTime();
        if (age > this.maxBackupAge) {
          fs.unlinkSync(file.path);
          deletedCount++;
          this.logger.log(`Deleted expired backup: ${file.name}`);
        }
      }

      if (deletedCount > 0) {
        this.logger.log(`Cleanup completed: deleted ${deletedCount} expired backups`);
      }

    } catch (error) {
      this.logger.error('Failed to cleanup old backups:', error);
    }
  }

}

