import { FormatEnum, pgDump, pgRestore } from 'pg-dump-restore'
import { promises as fs } from 'fs'
import path from 'path'
import cron, { type ScheduledTask } from 'node-cron'
import { logger } from '@d8d/shared-utils'
import process from 'node:process'

const BACKUP_DIR = process.env.BACKUP_DIR || './backups'
const RETENTION_DAYS = parseInt(process.env.BACKUP_RETENTION_DAYS || '7')
const SCHEDULE = process.env.BACKUP_SCHEDULE || '0 2 * * *'

export interface BackupOptions {
  host?: string
  port?: number
  database?: string
  username?: string
  password?: string
  format?: 'custom' | 'plain' | 'tar'
  outputFile?: string
}

export class DatabaseBackup {
  private static instance: DatabaseBackup
  private cronJob: ScheduledTask | null = null

  private constructor() {}

  static getInstance(): DatabaseBackup {
    if (!DatabaseBackup.instance) {
      DatabaseBackup.instance = new DatabaseBackup()
    }
    return DatabaseBackup.instance
  }

  private getDbConfig() {
    return {
      host: process.env.DB_HOST || 'localhost',
      port: parseInt(process.env.DB_PORT || '5432'),
      database: process.env.DB_DATABASE || 'postgres',
      username: process.env.DB_USERNAME || 'postgres',
      password: process.env.DB_PASSWORD || '',
    }
  }

  async ensureBackupDir(): Promise<void> {
    try {
      await fs.mkdir(BACKUP_DIR, { recursive: true })
      await fs.chmod(BACKUP_DIR, 0o700)
      logger.db(`备份目录已创建: ${BACKUP_DIR}`)
    } catch (error) {
      logger.error(`创建备份目录失败: ${error}`)
      throw error
    }
  }

  async createBackup(options: Partial<BackupOptions> = {}): Promise<string> {
    const config = { ...this.getDbConfig(), ...options }
    const timestamp = new Date().toISOString().replace(/[:.]/g, '-')
    const backupFile = options.outputFile || path.join(BACKUP_DIR, `backup-${timestamp}.dump`)

    try {
      await this.ensureBackupDir()

      logger.db(`开始创建数据库备份: ${config.database}`)

      await pgDump({
        host: config.host,
        port: config.port,
        database: config.database,
        username: config.username,
        password: config.password,
      }, {
        format: FormatEnum.Custom,
        filePath: backupFile,
      })

      await fs.chmod(backupFile, 0o600)

      const stats = await fs.stat(backupFile)
      logger.db(`备份创建成功: ${backupFile} (${this.formatFileSize(stats.size)})`)

      return backupFile
    } catch (error) {
      logger.error(`备份创建失败: ${error}`)
      throw error
    }
  }

  async restoreBackup(backupFile: string, options: Partial<BackupOptions> = {}): Promise<void> {
    const config = { ...this.getDbConfig(), ...options }

    try {
      if (!await this.backupExists(backupFile)) {
        throw new Error(`备份文件不存在: ${backupFile}`)
      }

      logger.db(`开始恢复数据库备份: ${backupFile}`)

      await pgRestore({
        host: config.host,
        port: config.port,
        database: config.database,
        username: config.username,
        password: config.password,
      }, {
        filePath: backupFile,
      })

      logger.db(`数据库恢复成功: ${config.database}`)
    } catch (error) {
      logger.error(`数据库恢复失败: ${error}`)
      throw error
    }
  }

  async cleanupOldBackups(): Promise<void> {
    try {
      const files = await fs.readdir(BACKUP_DIR)
      const now = Date.now()
      const retentionTime = RETENTION_DAYS * 24 * 60 * 60 * 1000

      for (const file of files) {
        if (file.endsWith('.dump')) {
          const filePath = path.join(BACKUP_DIR, file)
          const stats = await fs.stat(filePath)

          if (now - stats.mtimeMs > retentionTime) {
            await fs.unlink(filePath)
            logger.db(`删除旧备份文件: ${file}`)
          }
        }
      }
    } catch (error) {
      logger.error(`清理旧备份失败: ${error}`)
    }
  }

  async backupExists(backupFile: string): Promise<boolean> {
    try {
      await fs.access(backupFile)
      return true
    } catch {
      return false
    }
  }

  async getBackupInfo(backupFile: string): Promise<{
    size: number
    mtime: Date
    formattedSize: string
  }> {
    try {
      const stats = await fs.stat(backupFile)
      return {
        size: stats.size,
        mtime: stats.mtime,
        formattedSize: this.formatFileSize(stats.size)
      }
    } catch (error) {
      throw new Error(`获取备份信息失败: ${error}`)
    }
  }

  private formatFileSize(bytes: number): string {
    const sizes = ['B', 'KB', 'MB', 'GB']
    if (bytes === 0) return '0 B'
    const i = Math.floor(Math.log(bytes) / Math.log(1024))
    return Math.round(bytes / Math.pow(1024, i) * 100) / 100 + ' ' + sizes[i]
  }

  startScheduledBackups(): void {
    if (this.cronJob) {
      this.cronJob.stop()
    }

    this.cronJob = cron.schedule(SCHEDULE, async () => {
      try {
        logger.db('开始定时备份任务')
        await this.createBackup()
        await this.cleanupOldBackups()
        logger.db('定时备份任务完成')
      } catch (error) {
        logger.error(`定时备份任务失败: ${error}`)
      }
    })

    logger.db(`备份调度已启动: ${SCHEDULE}`)
  }

  stopScheduledBackups(): void {
    if (this.cronJob) {
      this.cronJob.stop()
      logger.db('备份调度已停止')
    }
  }

  getBackupStatus(): {
    scheduled: boolean
    nextRun: Date | null
    lastRun: Date | null
  } {
    return {
      scheduled: this.cronJob !== null,
      nextRun: this.cronJob?.getNextRun() || null,
      lastRun: null
    }
  }
}

export const databaseBackup = DatabaseBackup.getInstance()

if (import.meta.url === `file://${process.argv[1]}`) {
  async function main() {
    const command = process.argv[2]

    switch (command) {
      case 'backup':
        await databaseBackup.createBackup()
        break
      case 'restore':
        const backupFile = process.argv[3]
        if (!backupFile) {
          console.error('请指定要恢复的备份文件')
          process.exit(1)
        }
        await databaseBackup.restoreBackup(backupFile)
        break
      case 'cleanup':
        await databaseBackup.cleanupOldBackups()
        break
      case 'list':
        const files = await fs.readdir(BACKUP_DIR)
        const backupFiles = files.filter(f => f.endsWith('.dump'))
        console.log('可用备份文件:')
        backupFiles.forEach(file => console.log(`  - ${file}`))
        break
      default:
        console.log('用法: node backup.ts [backup|restore <file>|cleanup|list]')
        process.exit(1)
    }
  }

  main().catch(console.error)
}