import { Injectable, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import * as Minio from 'minio';
import * as fs from 'fs';
import * as path from 'path';
import * as archiver from 'archiver';
import { BackupTarget } from '@/domain/enums';
import {
  BackupHandler,
  BackupResult,
  ProgressCallback,
} from '@/domain/interfaces/backup-handler.interface';

@Injectable()
export class MinioBackupHandler implements BackupHandler {
  readonly target = BackupTarget.MINIO;
  private readonly logger = new Logger(MinioBackupHandler.name);
  private readonly minioClient: Minio.Client;

  constructor(private readonly configService: ConfigService) {
    const endpoint = this.configService.get<string>('MINIO_ENDPOINT', 'localhost');
    const port = parseInt(this.configService.get<string>('MINIO_PORT', '9000'), 10);
    const useSSL = this.configService.get<string>('MINIO_USE_SSL', 'false') === 'true';
    const accessKey = this.configService.get<string>('MINIO_ACCESS_KEY', 'admin');
    const secretKey = this.configService.get<string>('MINIO_SECRET_KEY', '');

    this.minioClient = new Minio.Client({
      endPoint: endpoint,
      port,
      useSSL,
      accessKey,
      secretKey,
    });
  }

  async execute(outputDir: string, onProgress: ProgressCallback): Promise<BackupResult> {
    const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
    const fileName = `minio-${timestamp}.tar.gz`;
    const filePath = path.join(outputDir, fileName);
    const tempDownloadDir = path.join(outputDir, `minio-temp-${timestamp}`);

    fs.mkdirSync(outputDir, { recursive: true });
    fs.mkdirSync(tempDownloadDir, { recursive: true });

    onProgress(0, 'MinIO 桶列表获取中...');

    try {
      // 1. 列出所有桶
      const buckets = await this.minioClient.listBuckets();
      const backupBucket = this.configService.get<string>('MINIO_BACKUP_BUCKET', 'snapshots');
      // 排除备份桶自身
      const bucketsToBackup = buckets.filter((b) => b.name !== backupBucket);

      if (bucketsToBackup.length === 0) {
        throw new Error('没有可备份的 MinIO 桶');
      }

      this.logger.log(`发现 ${bucketsToBackup.length} 个桶待备份`);

      // 2. 下载所有桶的所有对象到临时目录
      let totalObjects = 0;
      let downloadedObjects = 0;

      // 先统计对象总数
      for (const bucket of bucketsToBackup) {
        const objects = await this.listAllObjects(bucket.name);
        totalObjects += objects.length;
      }

      onProgress(5, `共 ${totalObjects} 个对象待下载...`);

      for (const bucket of bucketsToBackup) {
        const bucketName = bucket.name;
        const bucketDir = path.join(tempDownloadDir, bucketName);
        fs.mkdirSync(bucketDir, { recursive: true });

        const objects = await this.listAllObjects(bucketName);

        for (const objName of objects) {
          const objectDir = path.join(bucketDir, path.dirname(objName));
          fs.mkdirSync(objectDir, { recursive: true });

          const objectPath = path.join(bucketDir, objName);
          const dataStream = await this.minioClient.getObject(bucketName, objName);
          const writeStream = fs.createWriteStream(objectPath);

          await new Promise<void>((resolve, reject) => {
            dataStream.pipe(writeStream);
            writeStream.on('finish', resolve);
            writeStream.on('error', reject);
            dataStream.on('error', reject);
          });

          downloadedObjects++;
          const percent = 5 + Math.floor((downloadedObjects / totalObjects) * 70);
          onProgress(percent, `下载对象 ${downloadedObjects}/${totalObjects}: ${bucketName}/${objName}`);
        }
      }

      onProgress(75, 'MinIO 对象下载完成，开始打包...');

      // 3. 打包为 tar.gz
      const result = await new Promise<BackupResult>((resolve, reject) => {
        const output = fs.createWriteStream(filePath);
        const archive = archiver('tar', { gzip: true });

        output.on('close', () => {
          const fileSize = archive.pointer();
          onProgress(100, 'MinIO 备份完成');
          resolve({ fileName, filePath, fileSize });
        });

        archive.on('error', reject);
        archive.pipe(output);
        archive.directory(tempDownloadDir, 'minio');
        archive.finalize();
      });

      this.logger.log(`MinIO 备份完成: ${fileName}, 大小: ${result.fileSize} bytes`);
      return result;
    } finally {
      // 清理临时下载目录
      fs.rmSync(tempDownloadDir, { recursive: true, force: true });
    }
  }

  private async listAllObjects(bucket: string): Promise<string[]> {
    return new Promise((resolve, reject) => {
      const names: string[] = [];
      const stream = this.minioClient.listObjects(bucket, '', true);
      stream.on('data', (obj) => { if (obj.name) names.push(obj.name); });
      stream.on('end', () => resolve(names));
      stream.on('error', reject);
    });
  }
}
