/**
 * @ai-summary Job for processing scheduled backups
 * @ai-context Runs every minute to check for due scheduled backups
 * @ai-context Uses distributed locking to prevent duplicate backups in blue-green deployments
 */

import { Pool } from 'pg';
import { randomUUID } from 'crypto';
import { logger } from '../../../core/logging/logger';
import { lockService } from '../../../core/config/redis';
import { BackupRepository } from '../data/backup.repository';
import { BackupService } from '../domain/backup.service';
import { BackupRetentionService } from '../domain/backup-retention.service';
import { ScheduledBackupJobResult, BackupFrequency } from '../domain/backup.types';

let pool: Pool | null = null;

/**
 * Sets the database pool for the job
 */
export function setBackupJobPool(dbPool: Pool): void {
  pool = dbPool;
}

// Lock TTL: 5 minutes (backup should complete well within this)
const BACKUP_LOCK_TTL_SECONDS = 300;

/**
 * Processes all scheduled backups that are due
 * Uses distributed locking to prevent duplicate backups across containers
 */
export async function processScheduledBackups(): Promise<ScheduledBackupJobResult> {
  if (!pool) {
    throw new Error('Database pool not initialized for backup job');
  }

  const repository = new BackupRepository(pool);
  const backupService = new BackupService(pool);
  const retentionService = new BackupRetentionService(pool);

  const result: ScheduledBackupJobResult = {
    processed: 0,
    succeeded: 0,
    failed: 0,
    errors: [],
  };

  try {
    // Get all enabled schedules that are due
    const dueSchedules = await repository.getEnabledSchedulesDue();

    if (dueSchedules.length === 0) {
      logger.debug('No scheduled backups due');
      return result;
    }

    logger.info('Processing scheduled backups', { count: dueSchedules.length });

    for (const schedule of dueSchedules) {
      // Generate unique lock value for this execution
      const lockKey = `backup:schedule:${schedule.id}`;
      const lockValue = randomUUID();

      // Try to acquire lock for this schedule
      const lockAcquired = await lockService.acquireLock(lockKey, BACKUP_LOCK_TTL_SECONDS, lockValue);

      if (!lockAcquired) {
        logger.debug('Backup already in progress for schedule, skipping', {
          scheduleId: schedule.id,
          scheduleName: schedule.name,
        });
        continue;
      }

      result.processed++;

      try {
        logger.info('Running scheduled backup', {
          scheduleId: schedule.id,
          scheduleName: schedule.name,
          frequency: schedule.frequency,
        });

        // Create the backup
        const backupResult = await backupService.createBackup({
          name: schedule.name,
          scheduleId: schedule.id,
          backupType: 'scheduled',
          includeDocuments: true,
        });

        if (backupResult.success) {
          result.succeeded++;
          logger.info('Scheduled backup completed', {
            scheduleId: schedule.id,
            backupId: backupResult.backupId,
          });

          // Run retention cleanup for this schedule immediately after successful backup
          try {
            const retentionResult = await retentionService.cleanupScheduleBackups(
              schedule.id,
              schedule.name,
              schedule.retentionCount
            );
            if (retentionResult.deletedCount > 0) {
              logger.info('Retention cleanup completed after backup', {
                scheduleId: schedule.id,
                deletedCount: retentionResult.deletedCount,
                freedBytes: retentionResult.freedBytes,
              });
            }
          } catch (retentionError) {
            logger.error('Retention cleanup failed after backup', {
              scheduleId: schedule.id,
              error: retentionError instanceof Error ? retentionError.message : String(retentionError),
            });
            // Don't fail the overall backup for retention errors
          }
        } else {
          result.failed++;
          result.errors.push({
            scheduleId: schedule.id,
            error: backupResult.error || 'Unknown error',
          });
          logger.error('Scheduled backup failed', {
            scheduleId: schedule.id,
            error: backupResult.error,
          });
        }

        // Update schedule's next run time
        const nextRunAt = calculateNextRun(schedule.frequency);
        await repository.updateScheduleLastRun(schedule.id, nextRunAt);
      } catch (error) {
        result.failed++;
        const errorMessage = error instanceof Error ? error.message : String(error);
        result.errors.push({ scheduleId: schedule.id, error: errorMessage });

        logger.error('Error processing scheduled backup', {
          scheduleId: schedule.id,
          error: errorMessage,
        });

        // Still update next run time to prevent repeated failures
        try {
          const nextRunAt = calculateNextRun(schedule.frequency);
          await repository.updateScheduleLastRun(schedule.id, nextRunAt);
        } catch {
          // Ignore error updating next run
        }
      } finally {
        // Always release the lock
        await lockService.releaseLock(lockKey, lockValue);
      }
    }

    logger.info('Scheduled backup processing completed', {
      processed: result.processed,
      succeeded: result.succeeded,
      failed: result.failed,
    });

    return result;
  } catch (error) {
    logger.error('Failed to process scheduled backups', {
      error: error instanceof Error ? error.message : String(error),
    });
    throw error;
  }
}

/**
 * Calculates the next run time for a schedule
 */
function calculateNextRun(frequency: BackupFrequency): Date {
  const now = new Date();
  const next = new Date(now);

  switch (frequency) {
    case 'hourly':
      next.setHours(next.getHours() + 1);
      next.setMinutes(0, 0, 0);
      break;
    case 'daily':
      next.setDate(next.getDate() + 1);
      next.setHours(3, 0, 0, 0);
      break;
    case 'weekly': {
      const daysUntilSunday = (7 - next.getDay()) % 7 || 7;
      next.setDate(next.getDate() + daysUntilSunday);
      next.setHours(3, 0, 0, 0);
      break;
    }
    case 'monthly':
      next.setMonth(next.getMonth() + 1);
      next.setDate(1);
      next.setHours(3, 0, 0, 0);
      break;
  }

  return next;
}
