export function assertBatchTimeBounds(batch, { now = new Date(), maxFutureSkewSeconds = 300 } = {}) {
  const acceptedAt = validDate(now, "intake_policy_clock_invalid");
  const maxFutureAt = new Date(acceptedAt.getTime() + Number(maxFutureSkewSeconds) * 1000);
  if (!Number.isInteger(maxFutureSkewSeconds) || maxFutureSkewSeconds < 0) {
    throw intakePolicyError("intake_policy_future_skew_invalid");
  }

  const receivedAt = validDate(batch?.batch?.receivedAt, "batch_received_at_invalid");
  if (receivedAt > maxFutureAt) throw intakePolicyError("batch_received_at_too_far_in_future");

  for (const fact of batch?.facts || []) {
    const observedAt = validDate(fact?.observedAt, "fact_observed_at_invalid");
    if (observedAt > maxFutureAt) throw intakePolicyError("fact_observed_at_too_far_in_future");
  }
}

/**
 * Retention is based on the server's acceptance time, never on provider or L2
 * timestamps supplied in a batch.
 */
export function rawRetentionExpiry({ now = new Date(), rawRetentionDays }) {
  const acceptedAt = validDate(now, "intake_policy_clock_invalid");
  if (!Number.isInteger(rawRetentionDays) || rawRetentionDays < 1) {
    throw intakePolicyError("raw_retention_days_invalid");
  }
  return new Date(acceptedAt.getTime() + rawRetentionDays * 24 * 60 * 60 * 1000);
}

export function intakePolicyError(code) {
  return Object.assign(new Error(code), { status: 422, code });
}

function validDate(value, code) {
  const parsed = value instanceof Date ? new Date(value.getTime()) : new Date(String(value ?? ""));
  if (Number.isNaN(parsed.getTime())) throw intakePolicyError(code);
  return parsed;
}
