import "server-only";
import { createHash, timingSafeEqual } from "node:crypto";
import type { ApplicationMaintenance } from "./application-maintenance";
import type { ApplicationProviders } from "./application-providers";
import { retentionDecision } from "./application-retention";
import { notifyApplication } from "./application-service";
export function schedulerAuthorized(header: string | null, secret: string) {
  const digest = (value: string) => createHash("sha256").update(value).digest();
  return (
    Boolean(header) &&
    timingSafeEqual(digest(header!), digest(`Bearer ${secret}`))
  );
}
export async function deleteEligibleApplication(
  id: string,
  providers: ApplicationProviders,
  now: number,
) {
  const record = await providers.repo.change(id, (current) => {
    if (!current) return { record: null, result: null };
    if (!current.deleting) {
      if (retentionDecision(current.retention, now).action !== "delete")
        return { record: current, result: null };
      current.deleting = true;
      current.revision++;
    }
    return { record: current, result: current };
  });
  if (!record) return false;
  for (const document of record.documents)
    await providers.remove(document.path, document.generation);
  await providers.repo.change(id, (current) => {
    if (current && !current.deleting) throw new Error("Deletion state changed");
    return { record: null, result: undefined };
  });
  return true;
}
export async function runApplicationMaintenance(
  providers: ApplicationProviders,
  maintenance: ApplicationMaintenance,
  clock = Date.now,
) {
  const lease = await maintenance.claim(clock());
  const result = {
    skipped: !lease,
    scanned: 0,
    retained: 0,
    deleted: 0,
    failed: 0,
    notificationsChecked: 0,
  };
  if (!lease) return result;
  let cursor = lease.cursor;
  const deadline = clock() + 45000;
  try {
    for (const id of await providers.repo.pendingNotifications(5)) {
      if (clock() >= deadline) break;
      try {
        await notifyApplication(id, providers);
        result.notificationsChecked++;
      } catch {
        result.failed++;
      }
    }
    const ids = await providers.repo.ids(cursor ?? undefined, 50);
    let finishedPage = true;
    for (const id of ids) {
      if (clock() >= deadline) {
        finishedPage = false;
        break;
      }
      try {
        if (await deleteEligibleApplication(id, providers, clock()))
          result.deleted++;
        else result.retained++;
      } catch {
        result.failed++;
      }
      cursor = id;
      result.scanned++;
    }
    if (finishedPage && ids.length < 50) cursor = null;
  } finally {
    await maintenance.finish(lease.token, cursor);
  }
  return result;
}
