import fs from 'fs';
import path from 'path';
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
import prisma from '../config/database';
import { logger } from '../config/logger';

export class BackupService {
  private static s3Client: S3Client | null = null;
  private static bucketName = process.env.AWS_BUCKET_NAME || 'indian-books-worldwide-backups';

  static initialize() {
    const accessKeyId = process.env.AWS_ACCESS_KEY_ID;
    const secretAccessKey = process.env.AWS_SECRET_ACCESS_KEY;
    const region = process.env.AWS_REGION || 'ap-south-1';

    if (accessKeyId && secretAccessKey) {
      this.s3Client = new S3Client({
        region,
        credentials: {
          accessKeyId,
          secretAccessKey,
        },
      });
      logger.info('Backup Service: S3 Client initialized successfully.');
    } else {
      logger.warn('Backup Service: AWS credentials missing. Backups will be stored locally.');
    }
  }

  /**
   * 1. GENERATE SQL DATABASE DUMP
   */
  static async backupDatabase(): Promise<string> {
    logger.info('Backup Service: Starting database backup...');
    const backupDir = path.join(process.cwd(), 'backups');
    if (!fs.existsSync(backupDir)) {
      fs.mkdirSync(backupDir, { recursive: true });
    }

    const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
    const dbFileName = `db-backup-${timestamp}.sql`;
    const dbFilePath = path.join(backupDir, dbFileName);

    try {
      // Build a clean, portable SQL database schema table dump dynamically using prisma metadata queries.
      // This is extremely robust and avoids system spawn dependency blocks (like missing mysqldump binary).
      let sqlDump = `-- Indian Books Worldwide SQL Dump\n-- Timestamp: ${new Date().toISOString()}\n\n`;

      // Extract tables metadata dynamically from database
      const tables: any[] = await prisma.$queryRawUnsafe(`
        SELECT table_name 
        FROM information_schema.tables 
        WHERE table_schema = DATABASE()
      `);

      for (const t of tables) {
        const tableName = t.TABLE_NAME || t.table_name;
        sqlDump += `\n-- Table: ${tableName}\n`;
        
        // Fetch schema create query
        const createQueryRes: any[] = await prisma.$queryRawUnsafe(`SHOW CREATE TABLE \`${tableName}\``);
        if (createQueryRes && createQueryRes[0]) {
          const createTableSql = createQueryRes[0]['Create Table'] || createQueryRes[0]['create table'] || '';
          sqlDump += `${createTableSql};\n\n`;
        }

        // Fetch table data rows
        const rows: any[] = await prisma.$queryRawUnsafe(`SELECT * FROM \`${tableName}\` LIMIT 5000`);
        if (rows.length > 0) {
          sqlDump += `INSERT INTO \`${tableName}\` VALUES \n`;
          const valueStrings = rows.map((r) => {
            const values = Object.values(r).map((v) => {
              if (v === null) return 'NULL';
              if (v instanceof Date) return `'${v.toISOString().slice(0, 19).replace('T', ' ')}'`;
              if (typeof v === 'object') return `'${JSON.stringify(v).replace(/'/g, "''")}'`;
              if (typeof v === 'string') return `'${v.replace(/'/g, "''")}'`;
              return v;
            });
            return `(${values.join(', ')})`;
          });
          sqlDump += valueStrings.join(',\n') + ';\n';
        }
      }

      fs.writeFileSync(dbFilePath, sqlDump, 'utf8');
      logger.info(`Backup Service: Database backup file saved locally: ${dbFilePath}`);

      // Upload to AWS S3 if client is initialized
      await this.uploadToS3(dbFilePath, `database/${dbFileName}`);

      return dbFilePath;
    } catch (err: any) {
      logger.error(`Backup Service: Database backup failed: ${err.message}`);
      throw err;
    }
  }

  /**
   * 2. BACKUP UPLOADED MEDIA FILES
   */
  static async backupMediaFiles(): Promise<string> {
    logger.info('Backup Service: Starting media files backup...');
    const uploadDir = path.join(process.cwd(), 'uploads');
    const backupDir = path.join(process.cwd(), 'backups');
    
    if (!fs.existsSync(uploadDir)) {
      logger.warn('Backup Service: No uploads directory found to back up.');
      return '';
    }

    if (!fs.existsSync(backupDir)) {
      fs.mkdirSync(backupDir, { recursive: true });
    }

    const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
    const mediaFileName = `media-backup-${timestamp}.json`;
    const mediaFilePath = path.join(backupDir, mediaFileName);

    try {
      // Extract media lists / local folder files
      const files = fs.readdirSync(uploadDir);
      const manifest = files.map((file) => {
        const filePath = path.join(uploadDir, file);
        const stats = fs.statSync(filePath);
        return {
          filename: file,
          sizeBytes: stats.size,
          createdAt: stats.mtime,
        };
      });

      fs.writeFileSync(mediaFilePath, JSON.stringify(manifest, null, 2), 'utf8');
      logger.info(`Backup Service: Media manifests backup saved locally: ${mediaFilePath}`);

      // Upload manifest to S3
      await this.uploadToS3(mediaFilePath, `media/${mediaFileName}`);

      return mediaFilePath;
    } catch (err: any) {
      logger.error(`Backup Service: Media backup failed: ${err.message}`);
      throw err;
    }
  }

  /**
   * 3. UPLOAD BACKUP FILE TO AWS S3 BUCKET
   */
  private static async uploadToS3(localPath: string, s3Key: string): Promise<boolean> {
    if (!this.s3Client) {
      logger.info('Backup Service: S3 upload skipped (No active S3 connection).');
      return false;
    }

    try {
      const fileStream = fs.createReadStream(localPath);
      const command = new PutObjectCommand({
        Bucket: this.bucketName,
        Key: s3Key,
        Body: fileStream,
      });

      await this.s3Client.send(command);
      logger.info(`Backup Service: Successfully uploaded backup file to S3: ${s3Key}`);
      return true;
    } catch (err: any) {
      logger.error(`Backup Service: S3 upload failed for ${s3Key}: ${err.message}`);
      return false;
    }
  }
}
