import type { TransitionEngine } from "./transitions";
import type { ControllerStore } from "./store";

export const ADMISSION_LOOP_INTERVAL_MS = 60_000;

export interface AdmissionLoopOptions {
  store: ControllerStore;
  engine: Pick<TransitionEngine, "handle">;
  reconcileDeliveryLifecycle?: () => unknown | Promise<unknown>;
  intervalMs?: number;
  now?: () => number;
  log?: (line: string) => void;
}

export interface AdmissionLoop {
  /** Runs one pass. Resolves to the hosts probed, or null when the pass was skipped. */
  tick(): Promise<string[] | null>;
  /** The pass started at construction, so a caller can await the boot reconcile. */
  firstPass: Promise<string[] | null>;
  stop(): void;
}

type ProbeOutcome = { host: string; admitted: boolean; reason: string | null };

function outcomes(result: unknown): ProbeOutcome[] {
  if (typeof result !== "object" || result === null) return [];
  const probes = (result as { probes?: unknown }).probes;
  if (!Array.isArray(probes)) return [];
  return probes.flatMap((probe) => {
    if (typeof probe !== "object" || probe === null) return [];
    const { host, admitted, reason } = probe as Record<string, unknown>;
    if (typeof host !== "string" || typeof admitted !== "boolean") return [];
    return [{ host, admitted, reason: typeof reason === "string" ? reason : null }];
  });
}

/**
 * Enrolment only advances when something probes the enrolling hosts. Without this loop a host
 * that self-enrols on its first job report stays `enrolling` until an operator clicks
 * Reconcile, which is how debian3 sat unusable for a day.
 */
export function startAdmissionReconcileLoop(
  options: AdmissionLoopOptions,
): AdmissionLoop {
  const {
    store,
    engine,
    reconcileDeliveryLifecycle = () => {},
    intervalMs = ADMISSION_LOOP_INTERVAL_MS,
    now = () => Date.now(),
    log = (line) => process.stdout.write(`${line}\n`),
  } = options;

  let running = false;

  const tick = async (): Promise<string[] | null> => {
    if (running) return null;
    running = true;
    let enrolling: string[] = [];
    try {
      try { await reconcileDeliveryLifecycle(); }
      catch (error) { log(`admission-loop: delivery lifecycle reconcile threw: ${(error as Error).message}`); }
      enrolling = store
        .listHosts()
        .filter((host) => host.enrolling && host.state === "maintenance")
        .map((host) => host.hostname);
      if (enrolling.length === 0) return null;
      const response = await engine.handle("admission-reconcile", {
        expectedRevision: store.getRevision(),
        idempotencyKey: `admission-loop-${now()}`,
        args: { reason: "enrollment-loop" },
      });
      if (response.status !== 200) {
        log(
          `admission-loop: reconcile rejected (${response.status}) for ${enrolling.join(", ")}: ${
            JSON.stringify(response.body)
          }`,
        );
        return enrolling;
      }
      for (const probe of outcomes(response.body.result)) {
        log(
          probe.admitted
            ? `admission-loop: ${probe.host} admitted`
            : `admission-loop: ${probe.host} not admitted — ${probe.reason ?? "no reason reported"}`,
        );
      }
      return enrolling;
    } catch (error) {
      log(`admission-loop: reconcile threw for ${enrolling.join(", ")}: ${(error as Error).message}`);
      return enrolling;
    } finally {
      running = false;
    }
  };

  const timer = setInterval(() => void tick(), intervalMs);

  return {
    tick,
    firstPass: tick(),
    stop() {
      clearInterval(timer);
    },
  };
}
