import { UserRole } from '@prisma/client';
import { upgradeService } from '../modules/upgrade/upgrade.service';
import { siteSettingsService } from '../modules/settings/settings.service';
import { emailService } from './email.service';
import { getAdminEmailsByRole } from './notification.helper';
import { logger } from '../utils/logger';

/**
 * Auto-upgrade scheduler — checks for updates on a configurable schedule,
 * and triggers upgrades automatically when the repo is behind.
 *
 * Pattern: same setInterval-based scheduling as Listmonk sync, canvass cleanup, etc.
 */

type SchedulePreset = 'daily-3am' | 'daily-4am' | 'daily-5am' | 'weekly-sun-3am' | 'weekly-mon-3am' | '12h' | '24h';

/** Map presets to their repeat interval in ms and initial delay calculation. */
const SCHEDULE_CONFIG: Record<SchedulePreset, { intervalMs: number; targetHour?: number; targetDay?: number }> = {
  'daily-3am':      { intervalMs: 24 * 60 * 60 * 1000, targetHour: 3 },
  'daily-4am':      { intervalMs: 24 * 60 * 60 * 1000, targetHour: 4 },
  'daily-5am':      { intervalMs: 24 * 60 * 60 * 1000, targetHour: 5 },
  'weekly-sun-3am': { intervalMs: 7 * 24 * 60 * 60 * 1000, targetHour: 3, targetDay: 0 },
  'weekly-mon-3am': { intervalMs: 7 * 24 * 60 * 60 * 1000, targetHour: 3, targetDay: 1 },
  '12h':            { intervalMs: 12 * 60 * 60 * 1000 },
  '24h':            { intervalMs: 24 * 60 * 60 * 1000 },
};

const STARTUP_GRACE_MS = 5 * 60 * 1000; // 5 minutes after startup for interval presets
const CHECK_POLL_INTERVAL_MS = 3000;
const CHECK_POLL_TIMEOUT_MS = 60000;

class AutoUpgradeService {
  private initialTimer: ReturnType<typeof setTimeout> | null = null;
  private repeatTimer: ReturnType<typeof setInterval> | null = null;

  /** Read settings and start (or restart) the scheduler if enabled. */
  async start(): Promise<void> {
    this.stop();

    try {
      const settings = await siteSettingsService.get();
      if (!settings.enableAutoUpgrade) {
        logger.info('Auto-upgrade: disabled');
        return;
      }

      const schedule = (settings.autoUpgradeSchedule || 'daily-3am') as SchedulePreset;
      const config = SCHEDULE_CONFIG[schedule];
      if (!config) {
        logger.warn(`Auto-upgrade: unknown schedule "${schedule}", disabling`);
        return;
      }

      const initialDelay = this.calculateInitialDelay(schedule, config);
      const nextRunDate = new Date(Date.now() + initialDelay);

      logger.info(
        `Auto-upgrade: enabled (schedule=${schedule}, ` +
        `interval=${Math.round(config.intervalMs / 3600000)}h, ` +
        `next run=${nextRunDate.toLocaleString()})`,
      );

      this.initialTimer = setTimeout(() => {
        this.runAutoUpgrade().catch(() => {});
        // Then repeat on interval
        this.repeatTimer = setInterval(
          () => this.runAutoUpgrade().catch(() => {}),
          config.intervalMs,
        );
      }, initialDelay);
    } catch (err) {
      logger.warn('Auto-upgrade: failed to start scheduler:', err);
    }
  }

  /** Stop all timers. */
  stop(): void {
    if (this.initialTimer) {
      clearTimeout(this.initialTimer);
      this.initialTimer = null;
    }
    if (this.repeatTimer) {
      clearInterval(this.repeatTimer);
      this.repeatTimer = null;
    }
  }

  /** The scheduled task: check for updates, trigger upgrade if behind. */
  async runAutoUpgrade(): Promise<void> {
    logger.info('Auto-upgrade: starting scheduled check');

    if (upgradeService.isRunning()) {
      logger.info('Auto-upgrade: upgrade already in progress, skipping');
      return;
    }

    try {
      // Write marker so post-restart knows this was an auto-upgrade
      const fs = await import('fs');
      const path = await import('path');
      const markerPath = path.resolve('/app/upgrade/triggered-by.txt');
      fs.mkdirSync(path.dirname(markerPath), { recursive: true });
      fs.writeFileSync(markerPath, 'auto-upgrade', 'utf-8');

      // Trigger update check
      const statusBefore = upgradeService.getStatus();
      const checkedAtBefore = statusBefore?.checkedAt || null;
      upgradeService.triggerCheck('auto-upgrade');

      // Poll for check completion
      const checkComplete = await this.pollForCheckComplete(checkedAtBefore);
      if (!checkComplete) {
        logger.warn('Auto-upgrade: check timed out after 60s');
        return;
      }

      const status = upgradeService.getStatus();
      if (!status || status.commitsBehind === 0) {
        logger.info('Auto-upgrade: already up to date');
        // Clean up marker since no upgrade needed
        upgradeService.clearTriggeredBy();
        return;
      }

      // Refuse to auto-apply releases flagged `BREAKING:` in their Gitea body.
      // The admin UI gate ensures manual confirmation — auto-upgrade holds off
      // and keeps checking, so the block clears once the operator upgrades.
      if (status.breaking) {
        logger.warn(
          `Auto-upgrade: refusing to apply breaking release (${status.remoteCommit}): ${status.breakingReason || '(no reason given)'}. Manual confirmation required via admin UI.`,
        );
        upgradeService.clearTriggeredBy();
        return;
      }

      logger.info(`Auto-upgrade: ${status.commitsBehind} commits behind, triggering upgrade`);

      // Read settings for pullServices and registry options
      const settings = await siteSettingsService.get();
      upgradeService.triggerUpgrade('auto-upgrade', {
        skipBackup: false,
        pullServices: settings.autoUpgradePullServices,
        useRegistry: settings.useRegistryForUpgrade ?? false,
      });
      // API will restart during upgrade — post-restart handler takes over
    } catch (err) {
      logger.error('Auto-upgrade: failed:', err);
      // Clean up marker on error
      upgradeService.clearTriggeredBy();
    }
  }

  /**
   * Called on API startup — archives any pending result to history,
   * sends notification emails if this was an auto-upgrade.
   */
  async handlePostRestartResult(): Promise<void> {
    const result = upgradeService.getResult();
    if (!result) return;

    const triggeredBy = upgradeService.getTriggeredBy();

    // Archive result to history (add triggeredBy info)
    const enrichedResult = { ...result, triggeredBy: triggeredBy || 'manual' };
    upgradeService.archiveResult(enrichedResult);
    logger.info(`Post-restart: archived upgrade result (triggeredBy=${triggeredBy || 'manual'}, success=${result.success})`);

    // Send notification email if auto-upgrade and notifications enabled
    if (triggeredBy === 'auto-upgrade') {
      try {
        const settings = await siteSettingsService.get();
        if (settings.notifyAdminAutoUpgrade) {
          const emails = await getAdminEmailsByRole([UserRole.SUPER_ADMIN]);
          if (emails.length > 0) {
            const orgName = settings.organizationName || 'Changemaker Lite';
            const statusWord = result.success ? 'completed' : 'failed';
            const subject = `[${orgName}] Auto-upgrade ${statusWord}`;
            const warningsHtml = result.warnings.length > 0
              ? `<h3>Warnings</h3><ul>${result.warnings.map((w) => `<li>${w}</li>`).join('')}</ul>`
              : '';
            const html = [
              `<h2>Auto-upgrade ${statusWord}</h2>`,
              `<p>${result.message}</p>`,
              `<table style="border-collapse:collapse">`,
              `<tr><td style="padding:4px 12px 4px 0"><strong>Previous:</strong></td><td><code>${result.previousCommit}</code></td></tr>`,
              `<tr><td style="padding:4px 12px 4px 0"><strong>Current:</strong></td><td><code>${result.newCommit}</code></td></tr>`,
              `<tr><td style="padding:4px 12px 4px 0"><strong>Commits:</strong></td><td>${result.commitCount}</td></tr>`,
              `<tr><td style="padding:4px 12px 4px 0"><strong>Duration:</strong></td><td>${result.durationSeconds}s</td></tr>`,
              `<tr><td style="padding:4px 12px 4px 0"><strong>Completed:</strong></td><td>${result.completedAt}</td></tr>`,
              `</table>`,
              warningsHtml,
              `<p style="color:#888;font-size:12px">You can configure auto-upgrade settings in Settings → System.</p>`,
            ].join('\n');

            for (const email of emails) {
              await emailService.sendEmail({
                to: email,
                subject,
                html,
                text: `Auto-upgrade ${statusWord}: ${result.message}\nPrevious: ${result.previousCommit}\nCurrent: ${result.newCommit}\nCommits: ${result.commitCount}\nDuration: ${result.durationSeconds}s`,
              });
            }
            logger.info(`Auto-upgrade: notification sent to ${emails.length} admin(s)`);
          }
        }
      } catch (err) {
        logger.warn('Auto-upgrade: failed to send notification:', err);
      }
    }

    // Clean up result and marker files
    upgradeService.clearResult();
    upgradeService.clearTriggeredBy();
  }

  /** Calculate ms until the next scheduled run. */
  private calculateInitialDelay(
    schedule: SchedulePreset,
    config: { intervalMs: number; targetHour?: number; targetDay?: number },
  ): number {
    if (config.targetHour === undefined) {
      // Interval presets (12h, 24h) — start after grace period
      return STARTUP_GRACE_MS;
    }

    const now = new Date();
    const target = new Date(now);
    target.setHours(config.targetHour, 0, 0, 0);

    if (config.targetDay !== undefined) {
      // Weekly preset — find the next matching day
      const daysUntil = (config.targetDay - now.getDay() + 7) % 7;
      if (daysUntil === 0 && now >= target) {
        // Same day but already past — next week
        target.setDate(target.getDate() + 7);
      } else {
        target.setDate(target.getDate() + daysUntil);
      }
    } else {
      // Daily preset — if already past today's target, use tomorrow
      if (now >= target) {
        target.setDate(target.getDate() + 1);
      }
    }

    return target.getTime() - now.getTime();
  }

  /** Poll status.json until checkedAt changes (indicating check completed). */
  private pollForCheckComplete(previousCheckedAt: string | null): Promise<boolean> {
    return new Promise((resolve) => {
      const start = Date.now();
      const interval = setInterval(() => {
        if (Date.now() - start > CHECK_POLL_TIMEOUT_MS) {
          clearInterval(interval);
          resolve(false);
          return;
        }
        const status = upgradeService.getStatus();
        if (status && status.checkedAt !== previousCheckedAt) {
          clearInterval(interval);
          resolve(true);
        }
      }, CHECK_POLL_INTERVAL_MS);
    });
  }
}

export const autoUpgradeService = new AutoUpgradeService();
