import { spawn } from "node:child_process";
import { createHash, createHmac, randomUUID, timingSafeEqual } from "node:crypto";
import { mkdtempSync, readFileSync, rmSync } from "node:fs";
import { mkdir, open, readFile, readdir, rename, stat, unlink } from "node:fs/promises";
import { createServer } from "node:http";
import { tmpdir } from "node:os";
import { join } from "node:path";

export function createGithubSyncHandler(config) {
  const controller = config.controller ?? createDurableSyncController(config);
  if (!Number.isInteger(config.responseBudgetMs) || config.responseBudgetMs < 1) throw new Error("responseBudgetMs must be a positive integer");
  return async (req, res) => {
    const responseDeadlineMs = Date.now() + config.responseBudgetMs;
    const requestContext = { deliveryId: null, repository: null, ref: null, requestedCommit: null };
    try {
      const url = new URL(req.url || "/", "http://gitea-github-sync.local");
      if (req.method === "GET" && url.pathname === "/healthz") {
        const readiness = controller.readiness();
        writeJson(res, readiness.ready ? 200 : 503, { ok: readiness.ready, repos: config.repos.length, durableInbox: readiness, valuesPrinted: false });
        return;
      }
      if (req.method === "GET" && url.pathname === "/status") {
        const status = await controller.status();
        writeJson(res, status.ready ? 200 : 503, { ok: status.ready, ...status, valuesPrinted: false });
        return;
      }
      if (req.method !== "POST" || url.pathname !== config.path) {
        writeJson(res, 404, { ok: false, error: "not-found", valuesPrinted: false });
        return;
      }
      const deliveryId = singleHeader(req.headers["x-github-delivery"]);
      requestContext.deliveryId = deliveryId || null;
      const body = await waitWithinResponseBudget(
        readBody(req, config.maxBodyBytes),
        responseDeadlineMs,
        "request-body-response-deadline-exhausted",
      );
      verifySignature(req.headers["x-hub-signature-256"], body, config.webhookSecret);
      const event = String(req.headers["x-github-event"] || "");
      if (!deliveryId) throw new HttpError(400, "missing-github-delivery-id");
      if (event === "ping") {
        writeJson(res, 200, { ok: true, event, deliveryId, disposition: "pong", valuesPrinted: false });
        return;
      }
      if (event !== "push") {
        writeJson(res, 202, { ok: true, event, deliveryId, disposition: "ignored-event", valuesPrinted: false });
        return;
      }
      const payload = parsePayload(body);
      const repository = String(payload?.repository?.full_name || "");
      const ref = String(payload?.ref || "");
      const requestedCommit = String(payload?.after || "");
      Object.assign(requestContext, { repository, ref, requestedCommit });
      const repo = config.repos.find((item) => item?.upstream?.repository === repository && ref === `refs/heads/${item?.upstream?.branch}`);
      if (!repo || /^0+$/u.test(requestedCommit)) {
        writeJson(res, 202, { ok: true, event, deliveryId, repository, ref, disposition: "ignored-repo-ref-or-delete", valuesPrinted: false });
        return;
      }
      if (!/^[0-9a-f]{40,64}$/u.test(requestedCommit)) throw new HttpError(400, "invalid-push-after");
      const delivery = {
        deliveryId,
        repository,
        ref,
        requestedCommit,
        payloadSha: createHash("sha256").update(body).digest("hex"),
        receivedAt: new Date().toISOString(),
      };
      const acceptPromise = Promise.resolve().then(() => controller.accept(repo, delivery));
      let result;
      try {
        result = await waitWithinResponseBudget(
          acceptPromise,
          responseDeadlineMs,
          "durable-acceptance-response-deadline-exhausted",
        );
      } catch (error) {
        if (error instanceof HttpError && error.message === "durable-acceptance-response-deadline-exhausted") {
          void acceptPromise.then(
            (lateResult) => safeLog(config.log, deliveryLog("github-to-gitea-delivery-accepted-after-response-deadline", repo, delivery, {
              ok: true,
              state: lateResult.state,
              duplicate: lateResult.duplicate,
            })),
            (lateError) => safeLog(config.log, deliveryLog("github-to-gitea-delivery-persist-failed-after-response-deadline", repo, delivery, {
              ok: false,
              errorType: "durable-acceptance-late-failure",
              error: redact(String(lateError?.message || lateError)),
            })),
          );
        }
        throw error;
      }
      const refsCommitted = result.state === "committed";
      writeJson(res, 202, {
        ok: true,
        event,
        deliveryId,
        repository,
        ref,
        repo: repo.key,
        requestedCommit,
        state: result.state,
        disposition: result.duplicate ? "duplicate-durable-inbox-record" : "durable-inbox-accepted",
        refsCommitted,
        ...(refsCommitted && result.result ? { result: result.result } : {}),
        valuesPrinted: false,
      });
    } catch (error) {
      const status = error instanceof HttpError ? error.status : 503;
      safeLog(config.log, {
        event: "github-to-gitea-request-rejected",
        ...requestContext,
        ok: false,
        status,
        error: redact(String(error?.message || error)),
        valuesPrinted: false,
      });
      writeJson(res, status, { ok: false, error: error instanceof HttpError ? error.message : "internal-sync-error", valuesPrinted: false });
    }
  };
}

export function createDurableInboxStore(options) {
  const inboxDirectory = options.path;
  const maxBytes = options.maxBytes;
  const now = options.now ?? Date.now;
  let totalBytes = 0;
  let nextAcceptedSequence = 1;
  let initialized = false;
  let metadata = null;
  let mutationTail = Promise.resolve();

  function withMutationLock(task) {
    const result = mutationTail.then(task);
    mutationTail = result.catch(() => undefined);
    return result;
  }

  async function initialize() {
    await mkdir(inboxDirectory, { recursive: true, mode: 0o700 });
    const probePath = join(inboxDirectory, `.readiness-${randomUUID()}`);
    await durableWrite(probePath, '{"ok":true}\n');
    await unlink(probePath);
    await syncDirectory(inboxDirectory);
    const metadataPath = join(inboxDirectory, ".journal-metadata");
    try {
      metadata = validateJournalMetadata(JSON.parse(await readFile(metadataPath, "utf8")));
    } catch (error) {
      if (error?.code !== "ENOENT") throw new DurableInboxError(503, "durable-inbox-metadata-invalid");
      metadata = validateJournalMetadata({
        version: 1,
        kind: "GiteaGithubSyncDurableInbox",
        createdAt: new Date(now()).toISOString(),
      });
      await durableWrite(metadataPath, serializeRecord(metadata));
    }
    totalBytes = 0;
    nextAcceptedSequence = 1;
    for (const entry of await readdir(inboxDirectory, { withFileTypes: true })) {
      if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
      const path = join(inboxDirectory, entry.name);
      const record = await readRecordPath(path);
      nextAcceptedSequence = Math.max(nextAcceptedSequence, record.acceptedSequence + 1);
      totalBytes += (await stat(path)).size;
    }
    initialized = true;
    return { totalBytes, maxBytes };
  }

  async function accept(repo, delivery) {
    return await withMutationLock(async () => {
      assertInitialized();
      const path = recordPath(inboxDirectory, delivery.deliveryId);
      const existing = await readRecordPath(path, true);
      const fingerprint = deliveryFingerprint(repo, delivery);
      if (existing) {
        if (existing.fingerprint !== fingerprint) throw new DurableInboxError(409, "github-delivery-id-payload-conflict");
        return { record: existing, duplicate: true };
      }
      const now = delivery.receivedAt || new Date().toISOString();
      const record = {
        version: 1,
        deliveryId: delivery.deliveryId,
        fingerprint,
        repoKey: repo.key,
        repository: delivery.repository,
        ref: delivery.ref,
        requestedCommit: delivery.requestedCommit,
        payloadSha: delivery.payloadSha,
        receivedAt: now,
        acceptedAt: now,
        acceptedSequence: nextAcceptedSequence,
        updatedAt: now,
        state: "accepted",
        totalAttempts: 0,
        cycleAttempt: 0,
        nextAttemptAt: now,
        result: null,
        lastError: null,
      };
      const serialized = serializeRecord(validateInboxRecord(record));
      if (totalBytes + Buffer.byteLength(serialized) > maxBytes) {
        throw new DurableInboxError(503, "durable-inbox-capacity-exhausted");
      }
      await durableWrite(path, serialized);
      totalBytes += Buffer.byteLength(serialized);
      nextAcceptedSequence += 1;
      return { record, duplicate: false };
    });
  }

  async function get(deliveryId) {
    assertInitialized();
    return await readRecordPath(recordPath(inboxDirectory, deliveryId), true);
  }

  async function list() {
    assertInitialized();
    const records = [];
    for (const entry of await readdir(inboxDirectory, { withFileTypes: true })) {
      if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
      records.push(await readRecordPath(join(inboxDirectory, entry.name)));
    }
    return sortInboxRecordsByAcceptedSequence(records);
  }

  async function update(deliveryId, updater) {
    return await withMutationLock(async () => {
      assertInitialized();
      const path = recordPath(inboxDirectory, deliveryId);
      const previous = await readRecordPath(path);
      const previousSize = (await stat(path)).size;
      const next = updater(structuredClone(previous));
      const identityFields = ["version", "deliveryId", "fingerprint", "repoKey", "repository", "ref", "requestedCommit", "payloadSha", "receivedAt", "acceptedAt", "acceptedSequence"];
      if (!next || identityFields.some((field) => next[field] !== previous[field])) {
        throw new DurableInboxError(503, "durable-inbox-invalid-record-update");
      }
      const serialized = serializeRecord(validateInboxRecord(next));
      await durableWrite(path, serialized);
      totalBytes += Buffer.byteLength(serialized) - previousSize;
      return next;
    });
  }

  async function recoverProcessing(now) {
    const records = await list();
    for (const record of records) {
      if (record.state !== "processing") continue;
      await update(record.deliveryId, (current) => ({
        ...current,
        state: "accepted",
        updatedAt: now,
        nextAttemptAt: now,
        recoveredAfterRestart: true,
        lastError: { errorType: "worker-restarted-during-processing", retryable: true },
      }));
    }
  }

  async function cleanupCommitted(cutoffMs) {
    return await withMutationLock(async () => {
      assertInitialized();
      let removed = 0;
      const entries = (await readdir(inboxDirectory, { withFileTypes: true }))
        .filter((entry) => entry.isFile() && entry.name.endsWith(".json"));
      const records = [];
      for (const entry of entries) {
        const path = join(inboxDirectory, entry.name);
        records.push({ entry, path, record: await readRecordPath(path) });
      }
      const latestCommittedByRepo = new Map();
      for (const item of records) {
        if (item.record.state !== "committed") continue;
        const current = latestCommittedByRepo.get(item.record.repoKey);
        if (!current || item.record.acceptedSequence > current.record.acceptedSequence) {
          latestCommittedByRepo.set(item.record.repoKey, item);
        }
      }
      for (const item of records) {
        const { path, record } = item;
        if (record.state !== "committed" || latestCommittedByRepo.get(record.repoKey) === item || Date.parse(record.committedAt) > cutoffMs) continue;
        const size = (await stat(path)).size;
        await unlink(path);
        totalBytes = Math.max(0, totalBytes - size);
        removed += 1;
      }
      if (removed > 0) await syncDirectory(inboxDirectory);
      return removed;
    });
  }

  function capacityAvailable() {
    return initialized && totalBytes < maxBytes;
  }

  function usage() {
    return { totalBytes, maxBytes, capacityAvailable: capacityAvailable() };
  }

  function journalMetadata() {
    assertInitialized();
    return structuredClone(metadata);
  }

  function assertInitialized() {
    if (!initialized) throw new DurableInboxError(503, "durable-inbox-unavailable");
  }

  return { initialize, accept, get, list, update, recoverProcessing, cleanupCommitted, capacityAvailable, usage, journalMetadata };
}

export function createDurableSyncController(options) {
  const now = options.now ?? Date.now;
  const store = options.store ?? createDurableInboxStore(options.inbox);
  const retry = options.workerRetry;
  const workerAbort = new AbortController();
  const executor = options.executor ?? createSyncCoordinator({
    ...options,
    signal: workerAbort.signal,
    responseBudgetMs: retry.attemptTimeoutMs,
    retryMaxAttempts: 1,
    retryInitialDelayMs: retry.initialDelayMs,
    retryMaxDelayMs: retry.maxDelayMs,
  });
  const repoByKey = new Map(options.repos.map((repo) => [repo.key, repo]));
  const repoTails = new Map();
  const activeDeliveries = new Set();
  const inFlight = new Set();
  let storageReady = false;
  let storageError = "durable-inbox-starting";
  let capacityExhausted = false;
  let stopped = true;
  let scanTimer = null;
  let scanRunning = false;
  let lastCleanupAt = 0;

  async function start() {
    if (!stopped) return;
    stopped = false;
    try {
      await store.initialize();
      await store.recoverProcessing(new Date(now()).toISOString());
      storageReady = true;
      storageError = null;
      capacityExhausted = false;
      safeLog(options.log, { event: "github-to-gitea-inbox-ready", ...store.usage(), valuesPrinted: false });
      wake(0);
    } catch (error) {
      storageReady = false;
      storageError = typedStorageError(error);
      safeLog(options.log, { event: "github-to-gitea-inbox-unavailable", ok: false, errorType: storageError, error: redact(String(error?.message || error)), valuesPrinted: false });
      wake(retry.scanIntervalMs);
    }
  }

  async function accept(repo, delivery) {
    if (!storageReady) throw new HttpError(503, storageError || "durable-inbox-unavailable");
    try {
      const accepted = await store.accept(repo, delivery);
      capacityExhausted = false;
      safeLog(options.log, deliveryLog("github-to-gitea-delivery-accepted", repo, delivery, {
        ok: true,
        state: accepted.record.state,
        duplicate: accepted.duplicate,
      }));
      wake(0);
      return {
        state: accepted.record.state,
        duplicate: accepted.duplicate,
        result: accepted.record.state === "committed" && accepted.record.result
          ? publicSyncResult(accepted.record.result)
          : null,
      };
    } catch (error) {
      if (error instanceof DurableInboxError && error.status === 409) throw new HttpError(409, error.message);
      if (!(error instanceof DurableInboxError && error.message === "durable-inbox-capacity-exhausted")) {
        storageReady = false;
        storageError = typedStorageError(error);
      } else {
        capacityExhausted = true;
      }
      safeLog(options.log, deliveryLog("github-to-gitea-delivery-persist-failed", repo, delivery, {
        ok: false,
        state: "rejected",
        errorType: error instanceof DurableInboxError ? error.message : "durable-inbox-write-failed",
        error: redact(String(error?.message || error)),
      }));
      throw new HttpError(503, error instanceof DurableInboxError ? error.message : "durable-inbox-write-failed");
    }
  }

  function readiness() {
    const usage = store.usage();
    return {
      ready: storageReady && usage.capacityAvailable && !capacityExhausted,
      storageReady,
      capacityAvailable: usage.capacityAvailable && !capacityExhausted,
      totalBytes: usage.totalBytes,
      maxBytes: usage.maxBytes,
      errorType: storageReady ? (usage.capacityAvailable && !capacityExhausted ? null : "durable-inbox-capacity-exhausted") : storageError,
    };
  }

  async function status() {
    let records = [];
    if (storageReady) {
      try {
        records = await store.list();
      } catch (error) {
        storageReady = false;
        storageError = typedStorageError(error);
      }
    }
    const counts = { accepted: 0, processing: 0, committed: 0, failed: 0 };
    for (const record of records) {
      if (Object.hasOwn(counts, record.state)) counts[record.state] += 1;
    }
    return {
      ...readiness(),
      journal: storageReady ? store.journalMetadata() : null,
      counts,
      deliveries: statusRecords(records).map(publicInboxRecord),
      failedDeliveryRecovery: await readFailedDeliveryRecoveryStatus(options.failedDeliveryRecoveryStatePath),
    };
  }

  function wake(delayMs) {
    if (stopped) return;
    if (scanTimer !== null) clearTimeout(scanTimer);
    scanTimer = setTimeout(() => void scan(), Math.max(0, delayMs));
  }

  async function scan() {
    if (stopped || scanRunning) return;
    scanRunning = true;
    try {
      if (!storageReady) {
        await store.initialize();
        await store.recoverProcessing(new Date(now()).toISOString());
        storageReady = true;
        storageError = null;
      }
      if (now() - lastCleanupAt >= options.inbox.cleanupIntervalMs) {
        const removed = await store.cleanupCommitted(now() - options.inbox.committedRetentionMs);
        lastCleanupAt = now();
        if (removed > 0) {
          capacityExhausted = false;
          safeLog(options.log, { event: "github-to-gitea-inbox-retention", removed, ...store.usage(), valuesPrinted: false });
        }
      }
      const currentTime = now();
      for (const record of sortInboxRecordsByAcceptedSequence(await store.list())) {
        if (!["accepted", "failed"].includes(record.state)) continue;
        if (Date.parse(record.nextAttemptAt || record.receivedAt) > currentTime) continue;
        schedule(record);
      }
    } catch (error) {
      storageReady = false;
      storageError = typedStorageError(error);
      safeLog(options.log, { event: "github-to-gitea-inbox-scan-failed", ok: false, errorType: storageError, error: redact(String(error?.message || error)), valuesPrinted: false });
    } finally {
      scanRunning = false;
      wake(retry.scanIntervalMs);
    }
  }

  function schedule(record) {
    if (activeDeliveries.has(record.deliveryId) || stopped) return;
    activeDeliveries.add(record.deliveryId);
    const previous = repoTails.get(record.repoKey) ?? Promise.resolve();
    const task = previous.catch(() => undefined).then(() => processRecord(record.deliveryId))
      .catch((error) => {
        if (error instanceof DurableInboxError) {
          storageReady = false;
          storageError = typedStorageError(error);
        }
        safeLog(options.log, {
          event: "github-to-gitea-worker-exception",
          deliveryId: record.deliveryId,
          repo: record.repoKey,
          ok: false,
          errorType: "durable-worker-exception",
          error: redact(String(error?.message || error)),
          valuesPrinted: false,
        });
      })
      .finally(() => {
        activeDeliveries.delete(record.deliveryId);
        inFlight.delete(task);
        wake(0);
      });
    repoTails.set(record.repoKey, task.catch(() => undefined));
    inFlight.add(task);
  }

  async function processRecord(deliveryId) {
    let record = await store.get(deliveryId);
    if (!record || record.state === "committed") return;
    const repo = repoByKey.get(record.repoKey);
    if (!repo) {
      await markFailed(record, { ok: false, retryable: false, errorType: "repository-config-missing" });
      return;
    }
    const attemptStartedAt = new Date(now()).toISOString();
    record = await store.update(deliveryId, (current) => ({
      ...current,
      state: "processing",
      updatedAt: attemptStartedAt,
      processingAt: attemptStartedAt,
      totalAttempts: current.totalAttempts + 1,
      cycleAttempt: current.cycleAttempt + 1,
    }));
    const delivery = recordDelivery(record);
    safeLog(options.log, deliveryLog("github-to-gitea-delivery-processing", repo, delivery, {
      ok: true,
      state: "processing",
      totalAttempts: record.totalAttempts,
      cycleAttempt: record.cycleAttempt,
    }));
    let result;
    try {
      result = validateExecutorTerminalResult(await executor.execute(repo, delivery));
    } catch (error) {
      await markFailed(record, {
        ok: false,
        retryable: true,
        errorType: workerAbort.signal.aborted || stopped
          ? "source-sync-executor-aborted"
          : error instanceof ExecutorTerminalResultError
            ? "source-sync-executor-invalid-result"
            : "source-sync-executor-threw",
        error: redact(String(error?.message || error)),
      });
      return;
    }
    if (result.ok) {
      const committedAt = new Date(now()).toISOString();
      await store.update(deliveryId, (current) => ({
        ...current,
        state: "committed",
        updatedAt: committedAt,
        committedAt,
        nextAttemptAt: null,
        result: publicSyncResult(result),
        lastError: null,
      }));
      safeLog(options.log, deliveryLog("github-to-gitea-delivery-committed", repo, delivery, {
        ok: true,
        state: "committed",
        sourceCommit: result.sourceCommit,
        authorityCommit: result.authorityCommit,
        snapshotRef: result.snapshotRef,
        disposition: result.disposition,
        totalAttempts: record.totalAttempts,
      }));
      return;
    }
    await markFailed(record, result);
  }

  async function markFailed(record, result) {
    const repo = repoByKey.get(record.repoKey) ?? { key: record.repoKey };
    const terminal = result.retryable === false || record.cycleAttempt >= retry.maxAttempts;
    const delayMs = terminal
      ? retry.terminalRetryDelayMs
      : retryDelayMs(record.cycleAttempt, retry.initialDelayMs, retry.maxDelayMs);
    const failedAt = new Date(now()).toISOString();
    const nextAttemptAt = new Date(now() + delayMs).toISOString();
    await store.update(record.deliveryId, (current) => ({
      ...current,
      state: "failed",
      updatedAt: failedAt,
      failedAt,
      nextAttemptAt,
      cycleAttempt: terminal ? 0 : current.cycleAttempt,
      result: null,
      lastError: {
        errorType: result.errorType || "source-sync-failed",
        retryable: result.retryable !== false,
        terminal,
      },
    }));
    safeLog(options.log, deliveryLog("github-to-gitea-delivery-failed", repo, recordDelivery(record), {
      ok: false,
      state: "failed",
      errorType: result.errorType || "source-sync-failed",
      error: result.error ? redact(result.error) : null,
      terminal,
      automaticRetryScheduled: true,
      nextAttemptAt,
      totalAttempts: record.totalAttempts,
      cycleAttempt: record.cycleAttempt,
    }));
  }

  async function stop() {
    if (stopped && storageError === "durable-inbox-stopping") return;
    stopped = true;
    storageReady = false;
    storageError = "durable-inbox-stopping";
    if (scanTimer !== null) clearTimeout(scanTimer);
    workerAbort.abort();
    const settling = Promise.allSettled([...inFlight]);
    await Promise.race([settling, sleep(options.shutdownGraceMs)]);
  }

  return { start, stop, accept, readiness, status, scan };
}

export function createSyncCoordinator(options) {
  const states = new Map();
  const deliveryOwners = new Map();
  const sync = options.sync ?? ((repo, delivery, deadline) => syncRepository(repo, delivery, { ...options, ...deadline }));
  const wait = options.sleep ?? sleep;
  const now = options.now ?? Date.now;

  function stateFor(key) {
    let state = states.get(key);
    if (!state) {
      state = { tail: Promise.resolve(), active: null, lastSuccess: null };
      states.set(key, state);
    }
    return state;
  }

  function execute(repo, delivery) {
    const requestStartedAt = now();
    const deadlineAt = requestStartedAt + options.responseBudgetMs;
    const fingerprint = deliveryFingerprint(repo, delivery);
    const owner = deliveryOwners.get(delivery.deliveryId);
    if (owner && owner.fingerprint !== fingerprint) {
      throw new HttpError(409, "github-delivery-id-payload-conflict");
    }
    if (owner?.promise) {
      safeLog(options.log, deliveryLog("github-to-gitea-delivery-duplicate", repo, delivery, { disposition: "join-in-flight" }));
      return owner.promise.then((result) => ({ ...result, duplicate: true }));
    }
    if (owner && owner.fingerprint === fingerprint && owner.result?.ok === true) {
      safeLog(options.log, deliveryLog("github-to-gitea-delivery-duplicate", repo, delivery, { disposition: "durable-refs-already-committed" }));
      return Promise.resolve({ ...owner.result, duplicate: true });
    }
    const state = stateFor(repo.key);
    if (state.lastSuccess?.fingerprint === fingerprint) {
      safeLog(options.log, deliveryLog("github-to-gitea-delivery-duplicate", repo, delivery, { disposition: "durable-refs-already-committed" }));
      return Promise.resolve({ ...state.lastSuccess.result, duplicate: true });
    }
    const promise = state.tail.then(async () => {
      state.active = { delivery, fingerprint };
      const startedAt = requestStartedAt;
      let terminal = null;
      for (let syncAttempt = 1; syncAttempt <= options.retryMaxAttempts; syncAttempt += 1) {
        if (now() >= deadlineAt) {
          terminal = { ok: false, retryable: true, errorType: "response-deadline-exhausted" };
          safeLog(options.log, deliveryLog("github-to-gitea-sync", repo, delivery, {
            ok: false,
            errorType: terminal.errorType,
            syncAttempt,
            maxSyncAttempts: options.retryMaxAttempts,
            willRetry: false,
            terminal: true,
            elapsedMs: now() - startedAt,
          }));
          break;
        }
        let result;
        try {
          result = await sync(repo, delivery, { deadlineAt, now, signal: options.signal });
        } catch (error) {
          result = { ok: false, retryable: true, errorType: "sync-exception", error: String(error?.message || error) };
        }
        if (now() >= deadlineAt) {
          result = {
            ok: false,
            retryable: true,
            errorType: "response-deadline-exhausted",
            sourceCommit: result.sourceCommit ?? null,
            authorityCommit: result.authorityCommit ?? null,
            snapshotRef: result.snapshotRef ?? null,
            committedBeforeDeadlineUnknown: result.ok === true,
          };
        }
        const retryable = result.retryable !== false;
        const willRetry = !result.ok && retryable && syncAttempt < options.retryMaxAttempts;
        terminal = result;
        safeLog(options.log, deliveryLog("github-to-gitea-sync", repo, delivery, {
          ok: result.ok,
          sourceCommit: result.sourceCommit ?? null,
          authorityCommit: result.authorityCommit ?? null,
          snapshotRef: result.snapshotRef ?? null,
          disposition: result.disposition ?? null,
          errorType: result.errorType ?? null,
          error: result.error ? redact(result.error) : null,
          syncAttempt,
          maxSyncAttempts: options.retryMaxAttempts,
          willRetry,
          terminal: !willRetry,
          elapsedMs: now() - startedAt,
        }));
        if (!willRetry) break;
        try {
          const remainingMs = Math.max(0, deadlineAt - now());
          await wait(Math.min(remainingMs, retryDelayMs(syncAttempt, options.retryInitialDelayMs, options.retryMaxDelayMs)));
        } catch (error) {
          safeLog(options.log, deliveryLog("github-to-gitea-retry-delay-error", repo, delivery, { error: redact(String(error?.message || error)) }));
        }
      }
      if (terminal?.ok) state.lastSuccess = { fingerprint, result: terminal };
      const previousDeliveryId = state.lastDeliveryId;
      state.lastDeliveryId = delivery.deliveryId;
      deliveryOwners.set(delivery.deliveryId, { fingerprint, promise: null, result: terminal });
      if (previousDeliveryId && previousDeliveryId !== delivery.deliveryId) deliveryOwners.delete(previousDeliveryId);
      return terminal ?? { ok: false, errorType: "sync-produced-no-result", retryable: true };
    });
    const settled = promise.finally(() => {
      state.active = null;
      const current = deliveryOwners.get(delivery.deliveryId);
      if (current?.promise === settled) deliveryOwners.set(delivery.deliveryId, { fingerprint, promise: null, result: null });
    });
    state.tail = settled.catch(() => undefined);
    deliveryOwners.set(delivery.deliveryId, { fingerprint, promise: settled });
    return settled;
  }

  return { execute, stateFor };
}

export async function syncRepository(repo, delivery, runtime) {
  const work = mkdtempSync(join(tmpdir(), `gitea-gh-sync-${repo.key}-`));
  let sourceCommit = null;
  let snapshotRef = null;
  let authorityCommit = null;
  const execute = (args) => runWithinDeadline(runtime, args);
  const executeResult = (args) => runResultWithinDeadline(runtime, args);
  try {
    await execute(["git", "init", "--bare", work]);
    const snapshotRefForRequest = `${repo.snapshot.prefix.replace(/\/+$/u, "")}/${delivery.requestedCommit}`;
    const giteaUrl = runtime.giteaBaseUrl + new URL(repo.gitea.readUrl).pathname;
    const branchRef = `refs/heads/${repo.upstream.branch}`;
    const initialRemoteRefs = parseLsRemote((await execute([
      "git", "-C", work, "-c", `http.extraHeader=${runtime.giteaAuthHeader}`,
      "ls-remote", giteaUrl, branchRef, snapshotRefForRequest,
    ])).stdout);
    const currentCommit = initialRemoteRefs.get(branchRef) ?? null;
    const snapshotCommit = initialRemoteRefs.get(snapshotRefForRequest) ?? null;
    if (currentCommit) {
      await execute(["git", "-C", work, "-c", `http.extraHeader=${runtime.giteaAuthHeader}`, "fetch", "--no-tags", giteaUrl, `+${branchRef}:refs/remotes/gitea/${repo.upstream.branch}`]);
    }
    if (snapshotCommit && snapshotCommit !== currentCommit) {
      await execute(["git", "-C", work, "-c", `http.extraHeader=${runtime.giteaAuthHeader}`, "fetch", "--no-tags", giteaUrl, `+${snapshotRefForRequest}:refs/remotes/gitea/requested-snapshot`]);
    }
    let requestedObject = await executeResult(["git", "-C", work, "cat-file", "-e", `${delivery.requestedCommit}^{commit}`]);
    if (requestedObject.status !== 0) {
      const branchFetch = await executeResult([
        "git", "-C", work, "-c", `http.extraHeader=${runtime.githubAuthHeaders[repo.key]}`,
        "fetch", "--no-tags", repo.upstream.cloneUrl,
        `+refs/heads/${repo.upstream.branch}:refs/remotes/github/${repo.upstream.branch}`,
      ]);
      requestedObject = await executeResult(["git", "-C", work, "cat-file", "-e", `${delivery.requestedCommit}^{commit}`]);
      if (requestedObject.status !== 0) {
        const exactFetch = await executeResult(["git", "-C", work, "-c", `http.extraHeader=${runtime.githubAuthHeaders[repo.key]}`, "fetch", "--no-tags", repo.upstream.cloneUrl, `${delivery.requestedCommit}:refs/remotes/github/delivery`]);
        if (exactFetch.status !== 0) {
          return {
            ok: false,
            retryable: true,
            errorType: "requested-commit-temporarily-unavailable",
            error: `${branchFetch.stderr}\n${exactFetch.stderr}`,
          };
        }
        requestedObject = await executeResult(["git", "-C", work, "cat-file", "-e", `${delivery.requestedCommit}^{commit}`]);
      }
    }
    if (requestedObject.status !== 0) return { ok: false, retryable: false, errorType: "requested-commit-not-a-commit" };
    sourceCommit = (await execute(["git", "-C", work, "rev-parse", `${delivery.requestedCommit}^{commit}`])).stdout.trim();
    if (sourceCommit !== delivery.requestedCommit) {
      return {
        ok: false,
        retryable: false,
        errorType: "source-commit-mismatch",
        sourceCommit,
        error: `fetched source commit ${sourceCommit} does not equal webhook after ${delivery.requestedCommit}`,
      };
    }
    snapshotRef = snapshotRefForRequest;
    if (snapshotCommit && snapshotCommit !== sourceCommit) {
      return { ok: false, retryable: false, errorType: "immutable-snapshot-conflict", sourceCommit, authorityCommit: currentCommit, snapshotRef };
    }
    let branchDisposition = "created";
    let updateBranch = true;
    if (currentCommit) {
      const currentIsAncestor = await ancestry(executeResult, work, currentCommit, sourceCommit);
      const sourceIsAncestor = await ancestry(executeResult, work, sourceCommit, currentCommit);
      if (currentCommit === sourceCommit) {
        branchDisposition = "current";
        updateBranch = false;
      } else if (currentIsAncestor === true) {
        branchDisposition = "fast-forwarded";
      } else if (sourceIsAncestor === true) {
        branchDisposition = "superseded";
        updateBranch = false;
      } else if (currentIsAncestor === null || sourceIsAncestor === null) {
        return { ok: false, retryable: true, errorType: "ancestry-proof-failed", sourceCommit, authorityCommit: currentCommit, snapshotRef };
      } else {
        return { ok: false, retryable: false, errorType: "source-history-diverged", sourceCommit, authorityCommit: currentCommit, snapshotRef };
      }
    }
    const refspecs = [];
    const pushArgs = ["git", "-C", work, "-c", `http.extraHeader=${runtime.giteaAuthHeader}`, "push", "--atomic"];
    if (updateBranch) {
      pushArgs.push(`--force-with-lease=${branchRef}:${currentCommit ?? ""}`);
      refspecs.push(`${sourceCommit}:${branchRef}`);
    }
    if (!snapshotCommit) refspecs.push(`${sourceCommit}:${snapshotRef}`);
    if (refspecs.length > 0) {
      const pushed = await executeResult([...pushArgs, giteaUrl, ...refspecs]);
      if (pushed.status !== 0) {
        return { ok: false, retryable: true, errorType: "atomic-push-race-or-failure", sourceCommit, authorityCommit: currentCommit, snapshotRef, error: pushed.stderr };
      }
    }
    authorityCommit = branchDisposition === "superseded" ? currentCommit : sourceCommit;
    const proofRefs = parseLsRemote((await execute([
      "git", "-C", work, "-c", `http.extraHeader=${runtime.giteaAuthHeader}`,
      "ls-remote", giteaUrl, branchRef, snapshotRef,
    ])).stdout);
    const provenAuthorityCommit = proofRefs.get(branchRef) ?? null;
    const provenSnapshotCommit = proofRefs.get(snapshotRef) ?? null;
    if (provenAuthorityCommit !== authorityCommit || provenSnapshotCommit !== sourceCommit) {
      return {
        ok: false,
        retryable: true,
        errorType: "post-push-authority-proof-failed",
        sourceCommit,
        authorityCommit: provenAuthorityCommit,
        snapshotRef,
      };
    }
    return {
      ok: true,
      sourceCommit,
      authorityCommit,
      snapshotRef,
      disposition: branchDisposition === "superseded" ? "superseded-with-immutable-snapshot" : "atomic-source-authority-committed",
    };
  } catch (error) {
    return { ok: false, retryable: true, errorType: "git-source-sync-failed", sourceCommit, authorityCommit, snapshotRef, error: String(error?.message || error) };
  } finally {
    rmSync(work, { recursive: true, force: true });
  }
}

export async function run(args, timeoutMs = 120000) {
  const result = await runResult(args, timeoutMs);
  if (result.status !== 0) {
    throw new Error(`${args[0]} failed status=${result.status ?? "error"} stderr=${result.stderr}`);
  }
  return { stdout: result.stdout, stderr: result.stderr };
}

export function runResult(args, timeoutMs = 120000, signal = null) {
  const boundedTimeoutMs = Math.max(1, Math.floor(timeoutMs));
  if (signal?.aborted) return Promise.resolve({ status: null, stdout: "", stderr: "command aborted before spawn" });
  return new Promise((resolve) => {
    let stdout = "";
    let stderr = "";
    let timedOut = false;
    let aborted = false;
    let settled = false;
    let deadlineTimer = null;
    let forceKillTimer = null;
    let abortCommand = () => {};
    const child = spawn(args[0], args.slice(1), {
      stdio: ["ignore", "pipe", "pipe"],
      detached: process.platform !== "win32",
      env: {
        ...process.env,
        GIT_TERMINAL_PROMPT: "0",
        GIT_CONFIG_NOSYSTEM: "1",
        GIT_CONFIG_GLOBAL: "/dev/null",
      },
    });
    const finish = (status, error = "") => {
      if (settled) return;
      settled = true;
      if (deadlineTimer !== null) clearTimeout(deadlineTimer);
      if (forceKillTimer !== null) clearTimeout(forceKillTimer);
      signal?.removeEventListener("abort", abortCommand);
      resolve({
        status,
        stdout,
        stderr: appendTail(stderr, error, 1600),
      });
    };
    child.stdout.on("data", (chunk) => {
      stdout = appendTail(stdout, String(chunk), 65536);
    });
    child.stderr.on("data", (chunk) => {
      stderr = appendTail(stderr, String(chunk), 1600);
    });
    child.once("error", (error) => finish(null, String(error?.message || error)));
    child.once("close", (status, signal) => {
      const terminalError = aborted
        ? `command aborted; signal=${signal || "none"}`
        : timedOut
          ? `command deadline exceeded after ${boundedTimeoutMs}ms; signal=${signal || "none"}`
          : "";
      finish(aborted || timedOut ? null : status, terminalError);
    });
    abortCommand = () => {
      aborted = true;
      signalProcessTree(child, "SIGTERM");
      forceKillTimer = setTimeout(() => signalProcessTree(child, "SIGKILL"), 50);
    };
    signal?.addEventListener("abort", abortCommand, { once: true });
    if (signal?.aborted) abortCommand();
    deadlineTimer = setTimeout(() => {
      timedOut = true;
      signalProcessTree(child, "SIGTERM");
      forceKillTimer = setTimeout(() => signalProcessTree(child, "SIGKILL"), 50);
    }, boundedTimeoutMs);
  });
}

export function verifySignature(signatureHeader, body, webhookSecret) {
  const signature = singleHeader(signatureHeader);
  if (!signature || !signature.startsWith("sha256=")) throw new HttpError(401, "missing-github-signature");
  const expected = `sha256=${createHmac("sha256", webhookSecret).update(body).digest("hex")}`;
  if (!timingSafeEqualString(signature, expected)) throw new HttpError(401, "invalid-github-signature");
}

export function retryDelayMs(syncAttempt, initialDelayMs, maxDelayMs) {
  return Math.min(maxDelayMs, initialDelayMs * (2 ** Math.max(0, syncAttempt - 1)));
}

export function loadRuntimeConfig() {
  const giteaUsername = requiredEnv("GITEA_USERNAME");
  const giteaPassword = requiredEnv("GITEA_PASSWORD");
  const repos = JSON.parse(readFileSync(process.env.UNIDESK_GITEA_REPOS_PATH || "/etc/gitea-github-sync/repos.json", "utf8"));
  const defaultTokenEnv = requiredEnv("UNIDESK_GITEA_GITHUB_DEFAULT_TOKEN_ENV");
  const githubAuthHeaders = Object.fromEntries(repos.map((repo) => {
    const token = requiredEnv(repo.githubCredential?.tokenEnv || defaultTokenEnv);
    return [repo.key, `Authorization: Basic ${Buffer.from(`x-access-token:${token}`).toString("base64")}`];
  }));
  return {
    port: Number.parseInt(process.env.UNIDESK_GITEA_WEBHOOK_PORT || "8080", 10),
    path: process.env.UNIDESK_GITEA_WEBHOOK_PATH || "/_unidesk/github-to-gitea",
    responseBudgetMs: positiveIntegerEnv("UNIDESK_GITEA_WEBHOOK_RESPONSE_BUDGET_MS"),
    repos,
    webhookSecret: requiredEnv("GITHUB_WEBHOOK_SECRET"),
    giteaBaseUrl: requiredEnv("UNIDESK_GITEA_SERVICE_BASE_URL").replace(/\/+$/u, ""),
    inbox: {
      path: requiredEnv("UNIDESK_GITEA_WEBHOOK_INBOX_PATH"),
      maxBytes: positiveIntegerEnv("UNIDESK_GITEA_WEBHOOK_INBOX_MAX_BYTES"),
      committedRetentionMs: positiveIntegerEnv("UNIDESK_GITEA_WEBHOOK_COMMITTED_RETENTION_SECONDS") * 1000,
      cleanupIntervalMs: positiveIntegerEnv("UNIDESK_GITEA_WEBHOOK_CLEANUP_INTERVAL_MS"),
    },
    failedDeliveryRecoveryStatePath: process.env.UNIDESK_GITEA_HOOK_RECOVERY_STATE_PATH || null,
    workerRetry: {
      maxAttempts: positiveIntegerEnv("UNIDESK_GITEA_WEBHOOK_RETRY_MAX_ATTEMPTS"),
      initialDelayMs: positiveIntegerEnv("UNIDESK_GITEA_WEBHOOK_RETRY_INITIAL_DELAY_MS"),
      maxDelayMs: positiveIntegerEnv("UNIDESK_GITEA_WEBHOOK_RETRY_MAX_DELAY_MS"),
      terminalRetryDelayMs: positiveIntegerEnv("UNIDESK_GITEA_WEBHOOK_RETRY_TERMINAL_DELAY_MS"),
      attemptTimeoutMs: positiveIntegerEnv("UNIDESK_GITEA_WEBHOOK_WORKER_ATTEMPT_TIMEOUT_MS"),
      scanIntervalMs: positiveIntegerEnv("UNIDESK_GITEA_WEBHOOK_WORKER_SCAN_INTERVAL_MS"),
    },
    shutdownGraceMs: positiveIntegerEnv("UNIDESK_GITEA_WEBHOOK_SHUTDOWN_GRACE_MS"),
    maxBodyBytes: positiveIntegerEnv("UNIDESK_GITEA_WEBHOOK_MAX_BODY_BYTES"),
    githubAuthHeaders,
    giteaAuthHeader: `Authorization: Basic ${Buffer.from(`${giteaUsername}:${giteaPassword}`).toString("base64")}`,
    log,
    run,
    runResult,
  };
}

export async function readFailedDeliveryRecoveryStatus(statePath) {
  if (!statePath) return { enabled: false, ready: true, state: "disabled", counts: {}, entries: [] };
  let ledger;
  try {
    ledger = JSON.parse(await readFile(statePath, "utf8"));
  } catch (error) {
    if (error?.code === "ENOENT") return { enabled: true, ready: false, state: "not-initialized", counts: {}, entries: [] };
    return { enabled: true, ready: false, state: "invalid-or-unreadable", errorType: "hook-recovery-ledger-unreadable", counts: {}, entries: [] };
  }
  if (ledger?.version !== 1 || ledger?.kind !== "GiteaGithubHookRecoveryLedger" || !ledger.records || typeof ledger.records !== "object" || Array.isArray(ledger.records)) {
    return { enabled: true, ready: false, state: "invalid-or-unreadable", errorType: "hook-recovery-ledger-invalid", counts: {}, entries: [] };
  }
  const allowedStates = new Set(["pending", "attempted", "delivery-succeeded-awaiting-source", "source-committed", "terminal-exhausted"]);
  const records = Object.values(ledger.records);
  const validRecord = (item) => item && typeof item === "object"
    && allowedStates.has(item.state)
    && typeof item.repository === "string" && item.repository.length > 0
    && (typeof item.hookId === "number" || typeof item.hookId === "string")
    && typeof item.deliveryId === "string" && item.deliveryId.length > 0
    && Number.isInteger(item.attempts) && item.attempts >= 0
    && Number.isInteger(item.cooldownMs) && item.cooldownMs >= 0
    && typeof item.updatedAt === "string" && item.updatedAt.length > 0;
  if (records.some((item) => !validRecord(item))) {
    return { enabled: true, ready: false, state: "invalid-or-unreadable", errorType: "hook-recovery-ledger-record-invalid", counts: {}, entries: [] };
  }
  const counts = { pending: 0, attempted: 0, "delivery-succeeded-awaiting-source": 0, "source-committed": 0, "terminal-exhausted": 0 };
  for (const item of records) counts[item.state] += 1;
  const entries = records
    .sort((left, right) => String(right.updatedAt || "").localeCompare(String(left.updatedAt || "")))
    .slice(0, 50)
    .map((item) => ({
      repository: item.repository,
      hookId: item.hookId,
      deliveryId: item.deliveryId,
      state: item.state,
      attempts: item.attempts,
      cooldownMs: item.cooldownMs,
      nextAt: item.nextAt ?? null,
      updatedAt: item.updatedAt,
      lastStatusCode: item.lastStatusCode ?? null,
      completionReason: item.completionReason ?? null,
    }));
  return { enabled: true, ready: true, state: "ready", counts, entries };
}

export function startServer(config = loadRuntimeConfig()) {
  const controller = config.controller ?? createDurableSyncController(config);
  const server = createServer(createGithubSyncHandler({ ...config, controller }));
  void controller.start();
  server.listen(config.port, "0.0.0.0", () => {
    safeLog(config.log, { event: "github-to-gitea-sync-listening", port: config.port, path: config.path, repos: config.repos.map((repo) => repo.key), valuesPrinted: false });
  });
  const shutdown = async () => {
    server.close();
    await controller.stop();
  };
  server.once("close", () => void controller.stop());
  process.once("SIGTERM", shutdown);
  process.once("SIGINT", shutdown);
  return server;
}

class HttpError extends Error {
  constructor(status, message) {
    super(message);
    this.status = status;
  }
}

class DurableInboxError extends Error {
  constructor(status, message) {
    super(message);
    this.status = status;
  }
}

class ExecutorTerminalResultError extends Error {}

function validateExecutorTerminalResult(result) {
  if (!result || typeof result !== "object" || typeof result.ok !== "boolean") {
    throw new ExecutorTerminalResultError("executor terminal result must be an object with boolean ok");
  }
  if (result.ok === false) {
    if (typeof result.retryable !== "boolean" || !/^[a-z0-9][a-z0-9-]{0,127}$/u.test(result.errorType || "")) {
      throw new ExecutorTerminalResultError("failed executor terminal result must include retryable and typed errorType");
    }
    return result;
  }
  if (!/^[0-9a-f]{40,64}$/u.test(result.sourceCommit || "")
    || !/^[0-9a-f]{40,64}$/u.test(result.authorityCommit || "")
    || !/^refs\/[A-Za-z0-9._/-]+$/u.test(result.snapshotRef || "")
    || !["atomic-source-authority-committed", "superseded-with-immutable-snapshot"].includes(result.disposition)) {
    throw new ExecutorTerminalResultError("successful executor terminal result must include source, authority, snapshot and disposition proof");
  }
  return result;
}

function deliveryFingerprint(repo, delivery) {
  return createHash("sha256")
    .update([repo.key, delivery.deliveryId, delivery.repository, delivery.ref, delivery.requestedCommit, delivery.payloadSha].join("\0"))
    .digest("hex");
}

function nonRetryableStatus(errorType) {
  return [
    "github-delivery-id-payload-conflict",
    "source-commit-mismatch",
    "immutable-snapshot-conflict",
    "source-history-diverged",
  ].includes(errorType) ? 409 : 422;
}

function safeLog(logger, payload) {
  try {
    logger(payload);
  } catch {
    // Logging must never change delivery success or retry semantics.
  }
}

function parseLsRemote(output) {
  const refs = new Map();
  for (const line of String(output || "").split("\n")) {
    const [commit, ref] = line.trim().split(/\s+/u);
    if (commit && ref) refs.set(ref, commit);
  }
  return refs;
}

async function ancestry(executeResult, work, ancestor, descendant) {
  const result = await executeResult(["git", "-C", work, "merge-base", "--is-ancestor", ancestor, descendant]);
  if (result.status === 0) return true;
  if (result.status === 1) return false;
  return null;
}

async function runWithinDeadline(runtime, args) {
  const result = await runResultWithinDeadline(runtime, args);
  if (result.status !== 0) throw new Error(`${args[0]} failed status=${result.status ?? "deadline"} stderr=${result.stderr}`);
  return { stdout: result.stdout, stderr: result.stderr };
}

async function runResultWithinDeadline(runtime, args) {
  const remainingMs = Math.floor(runtime.deadlineAt - runtime.now());
  if (remainingMs < 1) return { status: null, stdout: "", stderr: "response deadline exhausted before command" };
  if (runtime.signal?.aborted) return { status: null, stdout: "", stderr: "command aborted before spawn" };
  return await runtime.runResult(args, remainingMs, runtime.signal);
}

function appendTail(previous, next, limit) {
  return `${previous}${next}`.slice(-limit);
}

function signalProcessTree(child, signal) {
  if (process.platform !== "win32" && child.pid) {
    try {
      process.kill(-child.pid, signal);
      return;
    } catch {
      // Fall back to the direct child when the process group already exited.
    }
  }
  child.kill(signal);
}

function recordPath(directory, deliveryId) {
  return join(directory, `${createHash("sha256").update(String(deliveryId)).digest("hex")}.json`);
}

function serializeRecord(record) {
  return `${JSON.stringify(record)}\n`;
}

async function readRecordPath(path, missingIsNull = false) {
  try {
    const parsed = JSON.parse(await readFile(path, "utf8"));
    return validateInboxRecord(parsed);
  } catch (error) {
    if (missingIsNull && error?.code === "ENOENT") return null;
    if (error instanceof DurableInboxError) throw error;
    throw new DurableInboxError(503, `durable-inbox-read-failed:${error?.code || "unknown"}`);
  }
}

async function durableWrite(path, content) {
  const temporaryPath = `${path}.tmp-${randomUUID()}`;
  let handle = null;
  try {
    handle = await open(temporaryPath, "wx", 0o600);
    await handle.writeFile(content, "utf8");
    await handle.sync();
    await handle.close();
    handle = null;
    await rename(temporaryPath, path);
    await syncDirectory(join(path, ".."));
  } catch (error) {
    if (handle) await handle.close().catch(() => undefined);
    await unlink(temporaryPath).catch(() => undefined);
    if (error instanceof DurableInboxError) throw error;
    throw new DurableInboxError(503, `durable-inbox-write-failed:${error?.code || "unknown"}`);
  }
}

async function syncDirectory(directory) {
  const handle = await open(directory, "r");
  try {
    await handle.sync();
  } finally {
    await handle.close();
  }
}

function typedStorageError(error) {
  if (error instanceof DurableInboxError) return error.message.split(":", 1)[0];
  return "durable-inbox-unavailable";
}

function recordDelivery(record) {
  return {
    deliveryId: record.deliveryId,
    repository: record.repository,
    ref: record.ref,
    requestedCommit: record.requestedCommit,
    payloadSha: record.payloadSha,
    receivedAt: record.receivedAt,
  };
}

function publicSyncResult(result) {
  return {
    sourceCommit: result.sourceCommit ?? null,
    authorityCommit: result.authorityCommit ?? null,
    snapshotRef: result.snapshotRef ?? null,
    disposition: result.disposition ?? null,
  };
}

function publicInboxRecord(record) {
  return {
    deliveryId: record.deliveryId,
    repo: record.repoKey,
    repository: record.repository,
    ref: record.ref,
    requestedCommit: record.requestedCommit,
    payloadSha: record.payloadSha,
    receivedAt: record.receivedAt,
    state: record.state,
    acceptedSequence: record.acceptedSequence,
    totalAttempts: record.totalAttempts,
    cycleAttempt: record.cycleAttempt,
    nextAttemptAt: record.nextAttemptAt,
    updatedAt: record.updatedAt,
    result: record.result,
    lastError: record.lastError,
  };
}

function statusRecords(records) {
  const selected = new Map();
  for (const record of records.slice(-100)) selected.set(record.deliveryId, record);
  const latestByRepo = new Map();
  const latestCommittedByRepo = new Map();
  for (const record of records) {
    const current = latestByRepo.get(record.repoKey);
    if (!current || record.acceptedSequence > current.acceptedSequence) latestByRepo.set(record.repoKey, record);
    if (record.state === "committed") {
      const committed = latestCommittedByRepo.get(record.repoKey);
      if (!committed || record.acceptedSequence > committed.acceptedSequence) latestCommittedByRepo.set(record.repoKey, record);
    }
  }
  for (const record of latestByRepo.values()) selected.set(record.deliveryId, record);
  for (const record of latestCommittedByRepo.values()) selected.set(record.deliveryId, record);
  return sortInboxRecordsByAcceptedSequence([...selected.values()]);
}

export function sortInboxRecordsByAcceptedSequence(records) {
  return [...records].sort((left, right) => left.acceptedSequence - right.acceptedSequence);
}

function validateInboxRecord(record) {
  const invalid = () => { throw new DurableInboxError(503, "durable-inbox-record-invalid"); };
  if (!record || record.version !== 1) invalid();
  if (typeof record.deliveryId !== "string" || record.deliveryId.length < 1 || record.deliveryId.length > 160) invalid();
  if (!/^[a-zA-Z0-9._-]+$/u.test(record.repoKey || "")) invalid();
  if (!/^[^/\s]+\/[^/\s]+$/u.test(record.repository || "")) invalid();
  if (!/^refs\/heads\/[^\s]+$/u.test(record.ref || "")) invalid();
  if (!/^[0-9a-f]{40,64}$/u.test(record.requestedCommit || "")) invalid();
  if (!/^[0-9a-f]{64}$/u.test(record.payloadSha || "")) invalid();
  if (!/^[0-9a-f]{64}$/u.test(record.fingerprint || "")) invalid();
  if (!["accepted", "processing", "committed", "failed"].includes(record.state)) invalid();
  if (!isTimestamp(record.receivedAt) || !isTimestamp(record.acceptedAt) || !isTimestamp(record.updatedAt)) invalid();
  if (!Number.isInteger(record.acceptedSequence) || record.acceptedSequence < 1) invalid();
  if (!Number.isInteger(record.totalAttempts) || record.totalAttempts < 0 || !Number.isInteger(record.cycleAttempt) || record.cycleAttempt < 0) invalid();
  if (record.nextAttemptAt !== null && !isTimestamp(record.nextAttemptAt)) invalid();
  if (record.lastError !== null) {
    if (typeof record.lastError !== "object" || !/^[a-z0-9][a-z0-9-]{0,127}$/u.test(record.lastError.errorType || "")) invalid();
    if (typeof record.lastError.retryable !== "boolean") invalid();
    if (record.lastError.terminal !== undefined && typeof record.lastError.terminal !== "boolean") invalid();
  }
  const expectedFingerprint = createHash("sha256")
    .update([record.repoKey, record.deliveryId, record.repository, record.ref, record.requestedCommit, record.payloadSha].join("\0"))
    .digest("hex");
  if (!timingSafeEqualString(record.fingerprint, expectedFingerprint)) invalid();
  if (record.state === "accepted" && (!isTimestamp(record.nextAttemptAt) || record.result !== null)) invalid();
  if (record.state === "processing" && (!isTimestamp(record.processingAt) || record.result !== null)) invalid();
  if (record.state === "failed" && (!isTimestamp(record.failedAt) || !isTimestamp(record.nextAttemptAt) || record.lastError === null || record.result !== null)) invalid();
  if (record.state === "committed") {
    if (!isTimestamp(record.committedAt) || record.nextAttemptAt !== null || record.lastError !== null || !record.result || typeof record.result !== "object") invalid();
    if (!/^[0-9a-f]{40,64}$/u.test(record.result.sourceCommit || "") || !/^[0-9a-f]{40,64}$/u.test(record.result.authorityCommit || "")) invalid();
    if (record.result.sourceCommit !== record.requestedCommit) invalid();
    if (!/^refs\/[a-zA-Z0-9._/-]+$/u.test(record.result.snapshotRef || "") || !record.result.snapshotRef.endsWith(`/${record.requestedCommit}`)) invalid();
    if (!["atomic-source-authority-committed", "superseded-with-immutable-snapshot"].includes(record.result.disposition)) invalid();
  }
  return record;
}

function validateJournalMetadata(metadata) {
  if (!metadata || metadata.version !== 1 || metadata.kind !== "GiteaGithubSyncDurableInbox" || !isTimestamp(metadata.createdAt)) {
    throw new DurableInboxError(503, "durable-inbox-metadata-invalid");
  }
  return metadata;
}

function isTimestamp(value) {
  return typeof value === "string" && Number.isFinite(Date.parse(value));
}

function deliveryLog(event, repo, delivery, extra = {}) {
  return {
    event,
    repo: repo.key,
    deliveryId: delivery.deliveryId,
    repository: delivery.repository,
    ref: delivery.ref,
    requestedCommit: delivery.requestedCommit,
    ...extra,
    valuesPrinted: false,
  };
}

function parsePayload(body) {
  try {
    return JSON.parse(body.toString("utf8") || "{}");
  } catch {
    throw new HttpError(400, "invalid-json-payload");
  }
}

function singleHeader(value) {
  return String(Array.isArray(value) ? value[0] : value || "").trim();
}

function timingSafeEqualString(a, b) {
  const left = Buffer.from(a);
  const right = Buffer.from(b);
  return left.length === right.length && timingSafeEqual(left, right);
}

function readBody(req, maxBodyBytes) {
  return new Promise((resolve, reject) => {
    const chunks = [];
    let bytes = 0;
    let rejected = false;
    req.on("data", (chunk) => {
      bytes += chunk.length;
      if (!rejected && bytes > maxBodyBytes) {
        rejected = true;
        reject(new HttpError(413, "request-body-too-large"));
        return;
      }
      if (!rejected) chunks.push(chunk);
    });
    req.on("end", () => {
      if (!rejected) resolve(Buffer.concat(chunks));
    });
    req.on("error", reject);
  });
}

export function waitWithinResponseBudget(promise, deadlineMs, errorType) {
  const remainingMs = deadlineMs - Date.now();
  if (remainingMs <= 0) return Promise.reject(new HttpError(503, errorType));
  return new Promise((resolve, reject) => {
    let settled = false;
    const timer = setTimeout(() => {
      if (settled) return;
      settled = true;
      reject(new HttpError(503, errorType));
    }, remainingMs);
    Promise.resolve(promise).then(
      (value) => {
        if (settled) return;
        settled = true;
        clearTimeout(timer);
        resolve(value);
      },
      (error) => {
        if (settled) return;
        settled = true;
        clearTimeout(timer);
        reject(error);
      },
    );
  });
}

function writeJson(res, status, payload) {
  if (res.headersSent || res.destroyed) return;
  res.writeHead(status, { "Content-Type": "application/json" });
  res.end(`${JSON.stringify(payload)}\n`);
}

function sleep(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

function log(payload) {
  console.log(JSON.stringify(payload));
}

function redact(value) {
  return String(value || "")
    .replace(/Authorization: Basic [A-Za-z0-9+/=]+/gu, "Authorization: Basic <redacted>")
    .replace(/x-access-token:[^@\s]+/gu, "x-access-token:<redacted>")
    .slice(-1600);
}

function requiredEnv(name) {
  const value = process.env[name];
  if (!value) throw new Error(`missing env ${name}`);
  return value;
}

function positiveIntegerEnv(name) {
  const value = Number.parseInt(requiredEnv(name), 10);
  if (!Number.isFinite(value) || value < 1) throw new Error(`invalid positive integer env ${name}`);
  return value;
}
