/**
 * Fleet trace-context helpers (§4 ops tracing).
 *
 * A `correlationId` threads a single logical work-unit across the
 * coordinator↔runner boundary: it is minted when a job is submitted, persisted
 * on the job, then inherited by the run/lease and stamped on every lifecycle
 * event (assigned → transition → lease_renewed → lease_released). The
 * coordinator echoes it back to the runner on the `x-correlation-id` response
 * header so an external factory can carry it on its subsequent renew/release
 * calls, and a downstream tracer can stitch job → claim → run → ship together.
 *
 * PURE + dependency-light so the resolution rules stay unit-testable.
 */

import crypto from 'node:crypto';

/** Response/request header carrying the fleet correlation id. */
export const CORRELATION_HEADER = 'x-correlation-id';

/** Mint a fresh correlation id (`ftr_<uuid>` — distinct from job/run ids). */
export function genCorrelationId(): string {
  return `ftr_${crypto.randomUUID()}`;
}

/** Header bag as Fastify exposes it (`string | string[] | undefined`). */
export type HeaderBag = Record<string, string | string[] | undefined>;

/** Read a header case-insensitively, collapsing an array to its first value. */
function header(headers: HeaderBag, name: string): string | undefined {
  const v = headers[name] ?? headers[name.toLowerCase()];
  const s = Array.isArray(v) ? v[0] : v;
  const trimmed = s?.trim();
  return trimmed ? trimmed : undefined;
}

/**
 * Extract the 32-hex trace-id from a W3C `traceparent` header
 * (`<version>-<trace-id>-<parent-id>-<flags>`). Returns undefined when the
 * header is malformed or the trace-id is all-zero (the spec's "invalid" value).
 */
export function traceIdFromTraceparent(traceparent: string | undefined): string | undefined {
  if (!traceparent) return undefined;
  const parts = traceparent.trim().split('-');
  if (parts.length < 4) return undefined;
  const traceId = parts[1];
  if (!/^[0-9a-f]{32}$/i.test(traceId) || /^0{32}$/.test(traceId)) return undefined;
  return traceId.toLowerCase();
}

/**
 * Resolve the correlation id carried by an inbound request, in precedence
 * order: an explicit `x-correlation-id`, then the trace-id of a W3C
 * `traceparent`, then the propagated `x-request-id`. Returns undefined when the
 * request carries none (the caller decides whether to mint one).
 */
export function correlationIdFromHeaders(headers: HeaderBag): string | undefined {
  return (
    header(headers, CORRELATION_HEADER) ||
    traceIdFromTraceparent(header(headers, 'traceparent')) ||
    header(headers, 'x-request-id')
  );
}

/**
 * Resolve the correlation id for a new job: the inbound request's id when
 * present, else a freshly minted one. Always returns a non-empty string.
 */
export function resolveCorrelationId(headers: HeaderBag): string {
  return correlationIdFromHeaders(headers) ?? genCorrelationId();
}
