import { createLogger } from "./logger.js";

const log = createLogger("shutdown");

const DEFAULT_TIMEOUT_MS = 10_000;

/**
 * A disposable resource that can be shut down during graceful shutdown.
 * Each step has a name (for logging) and an async teardown function.
 */
export interface ShutdownStep {
  name: string;
  fn: () => void | Promise<void>;
}

export interface GracefulShutdownOptions {
  /** Maximum time (ms) to wait before force-exiting. Default: 10 000. */
  timeoutMs?: number;
  /** Override process.exit for testing. */
  exit?: (code: number) => void;
}

/**
 * Orchestrates graceful shutdown of all server resources in a deterministic
 * order. Handles errors in individual steps without aborting the rest and
 * force-exits after a configurable timeout.
 */
export class GracefulShutdown {
  private steps: ShutdownStep[] = [];
  private isShuttingDown = false;
  private readonly timeoutMs: number;
  private readonly exit: (code: number) => void;

  constructor(options: GracefulShutdownOptions = {}) {
    this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
    this.exit = options.exit ?? ((code) => process.exit(code));
  }

  /** Register a teardown step. Steps execute in registration order. */
  register(name: string, fn: () => void | Promise<void>): void {
    this.steps.push({ name, fn });
  }

  /** Install SIGTERM and SIGINT handlers that trigger shutdown. */
  installSignalHandlers(): void {
    const handler = () => {
      this.shutdown();
    };
    process.on("SIGTERM", handler);
    process.on("SIGINT", handler);
  }

  /**
   * Execute all registered teardown steps in order.
   * Each step runs independently — a failure in one does not skip subsequent steps.
   * After all steps complete (or the timeout fires), the process exits.
   */
  async shutdown(): Promise<void> {
    if (this.isShuttingDown) {
      log.warn("Shutdown already in progress, ignoring duplicate signal");
      return;
    }
    this.isShuttingDown = true;
    log.info({ steps: this.steps.length, timeoutMs: this.timeoutMs }, "Graceful shutdown starting");

    // Force-exit safety net
    const forceTimer = setTimeout(() => {
      log.error("Graceful shutdown timed out, forcing exit");
      this.exit(1);
    }, this.timeoutMs);
    // Don't let the timer keep the process alive if everything finishes
    if (forceTimer && typeof forceTimer === "object" && "unref" in forceTimer) {
      forceTimer.unref();
    }

    for (const step of this.steps) {
      try {
        log.info({ step: step.name }, "Running shutdown step");
        await step.fn();
        log.info({ step: step.name }, "Shutdown step completed");
      } catch (err) {
        log.error({ step: step.name, err }, "Shutdown step failed");
      }
    }

    clearTimeout(forceTimer);
    log.info("Graceful shutdown complete");
    this.exit(0);
  }
}
