import { logger } from '../logger.js';
import { AcmeConfigDoc } from '../db/documents/index.js';
import type { IAcmeConfig } from '../../ts_interfaces/data/acme-config.js';

/**
 * AcmeConfigManager — owns the singleton ACME configuration in the DB.
 *
 * Lifecycle:
 *  - `start()` — loads the DB-backed singleton configuration.
 *  - `getConfig()` — returns the in-memory cached `IAcmeConfig` (or null)
 *  - `updateConfig(args, updatedBy)` — upserts and refreshes the cache
 *
 * Reload semantics: updates take effect on the next dcrouter restart because
 * `SmartAcme` is instantiated once in `setupSmartProxy()`. `renewThresholdDays`
 * applies immediately to the next renewal check. See
 * `ts_web/elements/domains/ops-view-certificates.ts` for the UI warning.
 */
export class AcmeConfigManager {
  private cached: IAcmeConfig | null = null;

  public async start(): Promise<void> {
    logger.log('info', 'AcmeConfigManager: starting');
    const doc = await AcmeConfigDoc.load();

    if (!doc) {
      logger.log('info', 'AcmeConfigManager: no AcmeConfig in DB — ACME disabled until configured via Domains > Certificates > Settings.');
    }

    this.cached = doc ? this.toPlain(doc) : null;
    if (this.cached) {
      logger.log(
        'info',
        `AcmeConfigManager: loaded ACME config (accountEmail=${this.cached.accountEmail}, enabled=${this.cached.enabled}, useProduction=${this.cached.useProduction})`,
      );
    }
  }

  public async stop(): Promise<void> {
    this.cached = null;
  }

  /**
   * Returns the current ACME config, or null if not configured.
   * In-memory — does not hit the DB.
   */
  public getConfig(): IAcmeConfig | null {
    return this.cached;
  }

  /**
   * True if there is an enabled ACME config. Used by `setupSmartProxy()` to
   * decide whether to instantiate SmartAcme.
   */
  public hasEnabledConfig(): boolean {
    return this.cached !== null && this.cached.enabled;
  }

  /**
   * Upsert the ACME config. All fields are optional; missing fields are
   * preserved from the existing row (or defaulted if there is no row yet).
   */
  public async updateConfig(
    args: Partial<Omit<IAcmeConfig, 'updatedAt' | 'updatedBy'>>,
    updatedBy: string,
  ): Promise<IAcmeConfig> {
    let doc = await AcmeConfigDoc.load();
    const now = Date.now();

    if (!doc) {
      doc = new AcmeConfigDoc();
      doc.configId = 'acme-config';
      doc.accountEmail = args.accountEmail ?? '';
      doc.enabled = args.enabled ?? true;
      doc.useProduction = args.useProduction ?? true;
      doc.autoRenew = args.autoRenew ?? true;
      doc.renewThresholdDays = args.renewThresholdDays ?? 30;
    } else {
      if (args.accountEmail !== undefined) doc.accountEmail = args.accountEmail;
      if (args.enabled !== undefined) doc.enabled = args.enabled;
      if (args.useProduction !== undefined) doc.useProduction = args.useProduction;
      if (args.autoRenew !== undefined) doc.autoRenew = args.autoRenew;
      if (args.renewThresholdDays !== undefined) doc.renewThresholdDays = args.renewThresholdDays;
    }

    doc.updatedAt = now;
    doc.updatedBy = updatedBy;
    await doc.save();

    this.cached = this.toPlain(doc);
    return this.cached;
  }

  // ==========================================================================
  // Internal helpers
  // ==========================================================================

  private toPlain(doc: AcmeConfigDoc): IAcmeConfig {
    return {
      accountEmail: doc.accountEmail,
      enabled: doc.enabled,
      useProduction: doc.useProduction,
      autoRenew: doc.autoRenew,
      renewThresholdDays: doc.renewThresholdDays,
      updatedAt: doc.updatedAt,
      updatedBy: doc.updatedBy,
    };
  }
}
