import { Cron } from 'croner';
import type { BackupConfig } from '../config/schema.js';
import type { OutboundMessage } from '../channels/types.js';
import { parseDuration } from '../session/index.js';
import { runBackupSnapshot } from './run.js';
import { initBackupHealth, markBackupFailure, markBackupSuccess } from './status.js';

interface ChannelLookup {
  get(name: string): { send(peerId: string, message: OutboundMessage): Promise<void> } | undefined;
}

export interface BackupSchedulerDeps {
  dataDir: string;
  backupConfig: BackupConfig;
  channelLookup?: ChannelLookup;
  runSnapshot?: typeof runBackupSnapshot;
}

export class BackupScheduler {
  private cronJob: Cron | undefined;
  private intervalJob: ReturnType<typeof setInterval> | undefined;
  private running = false;
  private consecutiveFailures = 0;
  private notifiedFailure = false;
  private readonly deps: BackupSchedulerDeps;

  constructor(deps: BackupSchedulerDeps) {
    this.deps = deps;
  }

  start(): void {
    initBackupHealth(this.deps.backupConfig.enabled);
    if (!this.deps.backupConfig.enabled) {
      return;
    }

    const backupSchedule = this.deps.backupConfig.schedule?.trim();
    const backupIntervalMs = parseDuration(this.deps.backupConfig.interval);

    if (!backupSchedule && !backupIntervalMs) {
      console.warn(`Backup enabled but interval is invalid: ${this.deps.backupConfig.interval}`);
      return;
    }

    if (backupSchedule) {
      try {
        this.cronJob = new Cron(backupSchedule, { paused: false }, () => {
          void this.runScheduledBackup();
        });
        console.log(`Backup scheduler enabled (cron: ${backupSchedule})`);
      } catch (error) {
        const message = error instanceof Error ? error.message : String(error);
        console.warn(`Backup cron schedule is invalid (${backupSchedule}): ${message}`);
      }
    }

    if (!this.cronJob && backupIntervalMs) {
      this.intervalJob = setInterval(() => {
        void this.runScheduledBackup();
      }, backupIntervalMs);
      console.log(`Backup scheduler enabled (interval: ${this.deps.backupConfig.interval})`);
    }

    if (!this.cronJob && !this.intervalJob) {
      console.warn('Backup scheduler disabled: no valid backup.schedule or backup.interval');
      return;
    }

    if (this.deps.backupConfig.run_on_start) {
      void this.runScheduledBackup();
    }
  }

  stop(): void {
    if (this.cronJob) {
      this.cronJob.stop();
      this.cronJob = undefined;
    }
    if (this.intervalJob) {
      clearInterval(this.intervalJob);
      this.intervalJob = undefined;
    }
  }

  private async runScheduledBackup(): Promise<void> {
    if (this.running) {
      return;
    }
    this.running = true;
    try {
      const runner = this.deps.runSnapshot ?? runBackupSnapshot;
      const result = await runner({
        dataDir: this.deps.dataDir,
        backupConfig: this.deps.backupConfig,
      });
      console.log(`Backup completed: ${result.archivePath}${result.uploaded && result.remotePath ? ` -> ${result.remotePath}` : ''}`);
      markBackupSuccess();
      await this.handleSuccess();
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      console.error(`Backup failed: ${message}`);
      markBackupFailure(message);
      await this.handleFailure(message);
    } finally {
      this.running = false;
    }
  }

  private async handleFailure(message: string): Promise<void> {
    this.consecutiveFailures += 1;
    const threshold = this.deps.backupConfig.failure_threshold;
    if (this.consecutiveFailures < threshold || this.notifiedFailure) {
      return;
    }

    this.notifiedFailure = true;
    await this.notify(
      `Backup FAILING (${this.consecutiveFailures} consecutive failures).\nError: ${message}`,
    );
  }

  private async handleSuccess(): Promise<void> {
    if (this.notifiedFailure && this.deps.backupConfig.notify_recovery) {
      await this.notify(
        `Backup RECOVERED after ${this.consecutiveFailures} consecutive failure(s).`,
      );
    }
    this.consecutiveFailures = 0;
    this.notifiedFailure = false;
  }

  private async notify(text: string): Promise<void> {
    const notifyConfig = this.deps.backupConfig.notify;
    if (!notifyConfig || !this.deps.channelLookup) {
      return;
    }

    const adapter = this.deps.channelLookup.get(notifyConfig.channel);
    if (!adapter) {
      console.warn(`BackupScheduler: notification channel '${notifyConfig.channel}' not found`);
      return;
    }

    try {
      await adapter.send(notifyConfig.peer, { text });
    } catch (err) {
      console.error('BackupScheduler: failed to send notification:', err);
    }
  }
}
