import * as plugins from '../plugins.js';
import { logger } from '../logger.js';
import type { DcRouter } from '../classes.dcrouter.js';

/**
 * Background start/retry/stop lifecycle for the DcRouter-owned SmartAcme
 * instance. SmartAcme startup can hit ACME rate limits, so startup runs in
 * the background with generation-guarded exponential retry, and certificate
 * provisioning is re-triggered once DNS-01 becomes ready.
 */
export class SmartAcmeLifecycle {
  /** True once the SmartAcme DNS-01 provider finished starting. */
  public ready = false;
  /** Tracks whether the taskbuffer SmartAcme service is started, so SmartProxy rebuilds can re-kick startup. */
  public serviceStarted = false;

  private startGeneration = 0;
  private startPromise?: Promise<void>;
  private retryTimer?: ReturnType<typeof setTimeout>;
  private retryAttempt = 0;

  constructor(private dcRouterRef: DcRouter) {}

  public startInBackground(): void {
    if (!this.dcRouterRef.smartAcme) {
      this.ready = false;
      return;
    }

    const generation = ++this.startGeneration;
    this.ready = false;
    this.retryAttempt = 0;
    this.clearRetryTimer();
    this.scheduleStart(generation, 0);
  }

  public async stop(): Promise<void> {
    this.startGeneration++;
    this.ready = false;
    this.retryAttempt = 0;
    this.clearRetryTimer();

    const smartAcme = this.dcRouterRef.smartAcme;
    if (!smartAcme) {
      return;
    }

    try {
      await smartAcme.stop();
    } catch (err) {
      logger.log('error', 'Error stopping SmartAcme', { error: String(err) });
    } finally {
      if (this.dcRouterRef.smartAcme === smartAcme) {
        this.dcRouterRef.smartAcme = undefined;
      }
    }
  }

  private scheduleStart(generation: number, delayMs: number): void {
    this.clearRetryTimer();
    const retryTimer = setTimeout(() => {
      this.retryTimer = undefined;
      this.runStartAttempt(generation).catch((err) => {
        logger.log('error', `Unexpected SmartAcme startup error: ${(err as Error).message}`);
      });
    }, delayMs);
    this.retryTimer = retryTimer;
    const unrefableTimer = retryTimer as any;
    if (typeof unrefableTimer?.unref === 'function') {
      unrefableTimer.unref();
    }
  }

  private async runStartAttempt(generation: number): Promise<void> {
    const smartAcme = this.dcRouterRef.smartAcme;
    if (!smartAcme || generation !== this.startGeneration) {
      return;
    }

    const startPromise = smartAcme.start();
    this.startPromise = startPromise;

    try {
      await startPromise;
      if (generation !== this.startGeneration || this.dcRouterRef.smartAcme !== smartAcme) {
        await smartAcme.stop().catch((err) => {
          logger.log('warn', `Failed to stop stale SmartAcme instance: ${(err as Error).message}`);
        });
        return;
      }

      this.ready = true;
      this.retryAttempt = 0;
      logger.log('info', 'SmartAcme DNS-01 provider is now ready');
      this.retriggerCertificateProvisioning();
    } catch (err) {
      if (generation !== this.startGeneration || this.dcRouterRef.smartAcme !== smartAcme) {
        return;
      }

      this.ready = false;
      await smartAcme.stop().catch((stopErr) => {
        logger.log('warn', `Failed to clean up SmartAcme after startup failure: ${(stopErr as Error).message}`);
      });
      this.retryAttempt++;
      if (this.retryAttempt > 20) {
        logger.log('error', `SmartAcme DNS-01 provider failed after 20 startup attempts: ${(err as Error).message}`);
        return;
      }

      const baseDelayMs = 5000;
      const maxDelayMs = 3_600_000;
      const delayMs = Math.min(baseDelayMs * Math.pow(2, this.retryAttempt - 1), maxDelayMs);
      const jitter = 0.8 + Math.random() * 0.4;
      const actualDelayMs = Math.floor(delayMs * jitter);
      logger.log('warn', `SmartAcme DNS-01 provider startup failed: ${(err as Error).message}; retrying in ${actualDelayMs}ms (attempt ${this.retryAttempt}/20)`);
      this.scheduleStart(generation, actualDelayMs);
    } finally {
      if (this.startPromise === startPromise) {
        this.startPromise = undefined;
      }
    }
  }

  private retriggerCertificateProvisioning(): void {
    // During startup, certProvisionFunction returns 'http01' while SmartAcme is not ready,
    // but Rust ACME is disabled when certProvisionFunction is set. Re-applying routes
    // retries provisioning now that DNS-01 is available.
    if (this.dcRouterRef.routeConfigManager) {
      logger.log('info', 'Re-triggering certificate provisioning via RouteConfigManager');
      this.dcRouterRef.routeConfigManager.applyRoutes().catch((err: any) => {
        logger.log('warn', `Failed to re-trigger cert provisioning: ${err?.message || err}`);
      });
      return;
    }

    if (this.dcRouterRef.smartProxy) {
      if (this.dcRouterRef.certProvisionScheduler) {
        this.dcRouterRef.certProvisionScheduler.clear();
      }
      const currentRoutes = this.dcRouterRef.smartProxy.routeManager.getRoutes();
      logger.log('info', `Re-triggering certificate provisioning for ${currentRoutes.length} routes`);
      this.dcRouterRef.smartProxy.updateRoutes(currentRoutes).catch((err: any) => {
        logger.log('warn', `Failed to re-trigger cert provisioning: ${err?.message || err}`);
      });
    }
  }

  private clearRetryTimer(): void {
    if (this.retryTimer) {
      clearTimeout(this.retryTimer);
      this.retryTimer = undefined;
    }
  }
}
