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

@Injectable()
export class MpcPostgresBackupHandler implements BackupHandler {
  readonly target = BackupTarget.MPC_POSTGRES;
  private readonly logger = new Logger(MpcPostgresBackupHandler.name);

  private readonly host: string;
  private readonly port: string;
  private readonly user: string;
  private readonly password: string;

  constructor(private readonly configService: ConfigService) {
    this.host = this.configService.get<string>('MPC_PG_HOST', 'localhost');
    this.port = this.configService.get<string>('MPC_PG_PORT', '5432');
    this.user = this.configService.get<string>('MPC_PG_USER', 'mpc_user');
    this.password = this.configService.get<string>('MPC_PG_PASSWORD', '');
  }

  /** 查询 MPC PostgreSQL 所有非模板数据库的总大小（字节） */
  private getTotalDatabaseSize(): Promise<number> {
    return new Promise((resolve) => {
      const psql = spawn('psql', [
        '-h', this.host,
        '-p', this.port,
        '-U', this.user,
        '-t', '-A',
        '-c', 'SELECT COALESCE(sum(pg_database_size(datname)), 0) FROM pg_database WHERE datistemplate = false',
      ], {
        env: { ...process.env, PGPASSWORD: this.password },
      });

      let output = '';
      psql.stdout.on('data', (data: Buffer) => { output += data.toString(); });
      psql.on('close', (code) => {
        const size = parseInt(output.trim(), 10);
        if (code === 0 && !isNaN(size) && size > 0) {
          this.logger.log(`MPC PostgreSQL 总数据库大小: ${(size / 1024 / 1024).toFixed(1)} MB`);
          resolve(size);
        } else {
          this.logger.warn(`无法获取 MPC 数据库大小 (exit=${code}), 使用默认估算 500MB`);
          resolve(500 * 1024 * 1024);
        }
      });
      psql.on('error', () => {
        this.logger.warn('psql 不可用, 使用默认估算 500MB');
        resolve(500 * 1024 * 1024);
      });
    });
  }

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

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

    onProgress(0, 'MPC PostgreSQL 查询数据库大小...');
    const totalSize = await this.getTotalDatabaseSize();

    onProgress(0, 'MPC pg_dumpall 开始...');

    return new Promise<BackupResult>((resolve, reject) => {
      const dumpProc = spawn('pg_dumpall', [
        '-h', this.host,
        '-p', this.port,
        '-U', this.user,
        '--clean',
        '--if-exists',
      ], {
        env: { ...process.env, PGPASSWORD: this.password },
      });

      const gzipProc = spawn('gzip', ['-6']);
      const outputStream = fs.createWriteStream(filePath);

      let bytesRead = 0;
      let lastReportedMB = 0;

      dumpProc.stdout.on('data', (chunk: Buffer) => {
        bytesRead += chunk.length;
        const readMB = Math.floor(bytesRead / (1024 * 1024));
        if (readMB > lastReportedMB) {
          lastReportedMB = readMB;
          const totalMB = Math.floor(totalSize / (1024 * 1024));
          const percent = Math.min(99, parseFloat(((bytesRead / totalSize) * 100).toFixed(2)));
          onProgress(percent, `MPC PostgreSQL 备份中... ${readMB}MB / ~${totalMB}MB`);
        }
      });

      // pipe: pg_dumpall stdout → gzip stdin → file
      dumpProc.stdout.pipe(gzipProc.stdin);
      gzipProc.stdout.pipe(outputStream);

      let stderrBuffer = '';
      dumpProc.stderr.on('data', (data: Buffer) => {
        stderrBuffer += data.toString();
      });

      let dumpExitCode: number | null = null;
      let gzipExitCode: number | null = null;

      const checkDone = () => {
        if (dumpExitCode === null || gzipExitCode === null) return;

        if (dumpExitCode === 0 && gzipExitCode === 0) {
          const stat = fs.statSync(filePath);
          this.logger.log(`MPC PostgreSQL 备份完成: ${fileName}, 压缩后: ${stat.size} bytes, 原始输出: ${bytesRead} bytes`);
          onProgress(100, 'MPC PostgreSQL 备份完成');
          resolve({ fileName, filePath, fileSize: stat.size });
        } else {
          const error = `MPC pg_dumpall 退出码: ${dumpExitCode}, gzip 退出码: ${gzipExitCode}, stderr: ${stderrBuffer.slice(-500)}`;
          this.logger.error(error);
          if (fs.existsSync(filePath)) fs.unlinkSync(filePath);
          reject(new Error(error));
        }
      };

      dumpProc.on('close', (code) => { dumpExitCode = code; checkDone(); });
      gzipProc.on('close', (code) => { gzipExitCode = code; checkDone(); });

      dumpProc.on('error', (err) => {
        this.logger.error(`MPC pg_dumpall 启动失败: ${err.message}`);
        if (fs.existsSync(filePath)) fs.unlinkSync(filePath);
        reject(new Error(`MPC pg_dumpall 启动失败: ${err.message}`));
      });

      gzipProc.on('error', (err) => {
        this.logger.error(`gzip 启动失败: ${err.message}`);
        if (fs.existsSync(filePath)) fs.unlinkSync(filePath);
        reject(new Error(`gzip 启动失败: ${err.message}`));
      });
    });
  }
}
