/**
 * SSH maintenance mode (Phase 5).
 *
 * Master-key rotation flow (design: docs/superpowers/plans/2026-05-12-ssh-tool-integration.md
 * Phase 5 "Master key rotation flow") puts the SSH subsystem into a maintenance
 * window while DEK rows are re-wrapped under a new master key. While the flag
 * is set:
 *   - all SSH tool calls (SshExec/Upload/Download) return 503 with retry-after
 *   - all SSH API writes (create/update/delete, grants, host_key verify/replace,
 *     force-unlock) return 503 with retry-after
 *   - read-only endpoints remain available
 *
 * v1: in-memory only. The actual DEK re-wrap job is deferred to a follow-up
 * PR (the rotate-master-key endpoints currently return 501 inside a stub job).
 * The maintenance flag is still useful to (a) demonstrate the 503 envelope to
 * UI work in Phase 6, and (b) provide a chokepoint for the eventual rotation
 * job to set.
 */

import { logger } from '../logger.js';

interface MaintenanceState {
  active: boolean;
  reason: string | null;
  enteredAt: string | null;
  /** Job id of the rotation job that owns the maintenance window, if any. */
  jobId: string | null;
}

export interface MaintenanceSnapshot {
  active: boolean;
  reason: string | null;
  enteredAt: string | null;
  jobId: string | null;
}

export type MaintenanceEnterCallback = () => Promise<void> | void;

export interface MaintenanceController {
  isActive(): boolean;
  snapshot(): MaintenanceSnapshot;
  enter(reason: string, jobId?: string): void;
  exit(): void;
  /**
   * Register a callback to run after maintenance mode is entered. Used by
   * the SSH Console subsystem to close all live console sessions when the
   * master key rotation begins. Callbacks are awaited sequentially but
   * exceptions are swallowed (logged) so a slow / failing callback can't
   * block the rotation.
   */
  onEnter(cb: MaintenanceEnterCallback): void;
}

function buildController(): MaintenanceController {
  const state: MaintenanceState = { active: false, reason: null, enteredAt: null, jobId: null };
  const callbacks: MaintenanceEnterCallback[] = [];
  return {
    isActive: () => state.active,
    snapshot: () => ({ ...state }),
    enter(reason, jobId) {
      state.active = true;
      state.reason = reason;
      state.enteredAt = new Date().toISOString();
      state.jobId = jobId ?? null;
      // Run callbacks asynchronously — fire-and-forget so enter() stays sync.
      // Errors are logged but never thrown.
      void (async () => {
        for (const cb of callbacks) {
          try {
            await cb();
          } catch (e) {
            logger.warn(`[ssh-maintenance] onEnter callback error: ${(e as Error).message}`);
          }
        }
      })();
    },
    exit() {
      state.active = false;
      state.reason = null;
      state.enteredAt = null;
      state.jobId = null;
    },
    onEnter(cb) {
      callbacks.push(cb);
    },
  };
}

/** Module-level singleton. Production code uses this. */
export const maintenance: MaintenanceController = buildController();

/**
 * Create an isolated controller for tests. Each call returns its own state
 * — does NOT touch the module singleton. Inject via SshApiDeps.maintenance.
 */
export function createMaintenanceController(): MaintenanceController {
  return buildController();
}
