/**
 * Package Deletion Worker
 *
 * Finishes the job the DELETE /subscription-packages/:id route starts.
 *
 * When a Super Admin deletes a package that still has live subscriptions, the
 * package is hidden and stamped with `pendingDeletionAt` — the day the last
 * subscription expires. This worker sweeps for packages whose date has passed
 * and hard-deletes them.
 *
 * It re-checks usage before deleting rather than trusting the stored date: a
 * subscription may have been renewed or extended since the deletion was
 * requested, in which case the date is simply pushed out to the new expiry.
 */

import { getModels } from '../models';
import { getPackageUsage } from '../services/packageDeletionService';

interface DeletionWorkerConfig {
  /** How often to sweep, in ms. */
  intervalMs: number;
  /** Delay before the first sweep after boot, in ms. */
  warmupMs: number;
  verbose: boolean;
}

const DEFAULT_CONFIG: DeletionWorkerConfig = {
  intervalMs: parseInt(process.env.PACKAGE_DELETION_INTERVAL_MS || '3600000', 10) || 3600000, // 1 hour
  warmupMs: parseInt(process.env.PACKAGE_DELETION_WARMUP_MS || '60000', 10) || 60000, // 1 min
  verbose: process.env.NODE_ENV === 'development',
};

export interface DeletionRunSummary {
  at: string;
  durationMs: number;
  /** Packages whose scheduled date had passed. */
  due: number;
  /** Packages actually removed this tick. */
  deleted: number;
  /** Packages whose deletion date moved because a subscription was extended. */
  rescheduled: number;
  names: string[];
  error?: string;
}

class PackageDeletionWorker {
  private config: DeletionWorkerConfig;
  private intervalId: NodeJS.Timeout | null = null;
  private warmupId: NodeJS.Timeout | null = null;
  private running = false;

  constructor(config?: Partial<DeletionWorkerConfig>) {
    this.config = { ...DEFAULT_CONFIG, ...config };
  }

  start(): void {
    if (this.intervalId) return;
    this.warmupId = setTimeout(() => { this.runTick(); }, this.config.warmupMs);
    this.intervalId = setInterval(() => { this.runTick(); }, this.config.intervalMs);
  }

  stop(): void {
    if (this.warmupId) { clearTimeout(this.warmupId); this.warmupId = null; }
    if (this.intervalId) { clearInterval(this.intervalId); this.intervalId = null; }
  }

  async runTick(): Promise<DeletionRunSummary> {
    const startedAt = Date.now();
    const summary: DeletionRunSummary = {
      at: new Date().toISOString(),
      durationMs: 0,
      due: 0,
      deleted: 0,
      rescheduled: 0,
      names: [],
    };

    // Overlapping sweeps would double-delete; skip if the last one is still going.
    if (this.running) return summary;
    this.running = true;

    try {
      const { SubscriptionPackage } = getModels();
      const now = new Date();
      const duePackages = await SubscriptionPackage.find({
        pendingDeletionAt: { $ne: null, $lte: now },
      });

      summary.due = duePackages.length;

      for (const pkg of duePackages as any[]) {
        try {
          // Re-check: a subscription may have been renewed since the request.
          const usage = await getPackageUsage(String(pkg._id));

          if (usage.liveSubscriptions > 0) {
            if (usage.lastExpiryDate) {
              pkg.pendingDeletionAt = usage.lastExpiryDate;
              await pkg.save();
              summary.rescheduled += 1;
              if (this.config.verbose) {
                console.log(`[PackageDeletionWorker] "${pkg.name}" still has ${usage.liveSubscriptions} live subscription(s) — deletion moved to ${usage.lastExpiryDate.toISOString()}`);
              }
            } else {
              // Lifetime subscriptions only — nothing to wait for. Leave it
              // queued and surfaced in the UI rather than deleting live data.
              console.warn(`[PackageDeletionWorker] "${pkg.name}" is blocked by ${usage.lifetimeSubscriptions} lifetime subscription(s) — not deleting`);
            }
            continue;
          }

          await SubscriptionPackage.deleteOne({ _id: pkg._id });
          summary.deleted += 1;
          summary.names.push(pkg.name);
          console.log(`[PackageDeletionWorker] Hard-deleted "${pkg.name}" (${pkg._id}) — last subscription expired`);
        } catch (err: any) {
          console.error(`[PackageDeletionWorker] Failed to delete package ${pkg?._id}:`, err?.message);
        }
      }
    } catch (err: any) {
      summary.error = err?.message || 'Unknown error';
      console.error('[PackageDeletionWorker] Sweep failed:', summary.error);
    } finally {
      this.running = false;
      summary.durationMs = Date.now() - startedAt;
    }

    if (this.config.verbose && summary.due > 0) {
      console.log(`[PackageDeletionWorker] Sweep done — due: ${summary.due}, deleted: ${summary.deleted}, rescheduled: ${summary.rescheduled}`);
    }

    return summary;
  }
}

// ============================================
// Singleton accessors
// ============================================

let workerInstance: PackageDeletionWorker | null = null;

export function getPackageDeletionWorker(config?: Partial<DeletionWorkerConfig>): PackageDeletionWorker {
  if (!workerInstance) workerInstance = new PackageDeletionWorker(config);
  return workerInstance;
}

export function startPackageDeletionWorker(config?: Partial<DeletionWorkerConfig>): void {
  getPackageDeletionWorker(config).start();
}

export function stopPackageDeletionWorker(): void {
  if (workerInstance) workerInstance.stop();
}

/** Run a single sweep immediately (used by tests / manual triggers). */
export async function runPackageDeletionSweepOnce(): Promise<DeletionRunSummary> {
  return getPackageDeletionWorker().runTick();
}
