import { QueryFailedError } from "typeorm";

/** Postgres unique-violation SQLSTATE. */
const PG_UNIQUE_VIOLATION = "23505";

/**
 * True when `error` is a Postgres unique-constraint violation. Used to detect a
 * reference-number collision from a concurrent insert so the caller can retry
 * with a freshly-computed number instead of surfacing a 500.
 */
export function isUniqueViolation(error: unknown): boolean {
  if (!(error instanceof QueryFailedError)) return false;
  const driver = (
    error as QueryFailedError & { driverError?: { code?: string } }
  ).driverError;
  return driver?.code === PG_UNIQUE_VIOLATION;
}

/**
 * Run `insert(reference)` under a "generate → try → retry on collision" loop.
 *
 * `MAX(sequence) + 1` alone is not concurrency-safe: two requests can read the
 * same max and derive the same reference, and one insert then hits the unique
 * index. On that collision we recompute the reference and try again, so the
 * sequence advances under load instead of throwing. Non-collision errors (and
 * exhausting the attempt budget) propagate unchanged.
 *
 * @param generate  Async producer of the next reference (e.g. `CTR-2026-00033`).
 *                  Re-invoked on each attempt so it re-reads the current max.
 * @param insert    Performs the insert with the given reference; its result is
 *                  returned on success.
 * @param attempts  Maximum tries before giving up (default 5).
 */
export async function insertWithGeneratedReference<T>(
  generate: () => Promise<string>,
  insert: (reference: string) => Promise<T>,
  attempts = 5,
): Promise<T> {
  let lastError: unknown;
  for (let attempt = 0; attempt < attempts; attempt++) {
    const reference = await generate();
    try {
      return await insert(reference);
    } catch (error) {
      if (!isUniqueViolation(error)) throw error;
      lastError = error;
    }
  }
  throw lastError;
}
