/**
 * Controlled retry with exponential backoff for temporary provider failures.
 * Configurable via env; defaults match product guidance.
 */

import { isRetryableErrorCode } from "@/lib/social/publish-errors";

export interface PublishRetryConfig {
  maxAttempts: number;
  /** Delays in ms between attempts (after failure). Length should be maxAttempts-1. */
  delaysMs: number[];
}

export function getPublishRetryConfig(): PublishRetryConfig {
  // Default 1 attempt for sync HTTP publish (avoid multi-minute request hangs).
  // Set SOCIAL_PUBLISH_MAX_ATTEMPTS=3 for background / long-retry workers.
  const maxAttempts = Math.max(
    1,
    Number.parseInt(process.env.SOCIAL_PUBLISH_MAX_ATTEMPTS ?? "1", 10) || 1,
  );
  const raw = process.env.SOCIAL_PUBLISH_RETRY_DELAYS_MS?.trim();
  const delaysMs = raw
    ? raw
        .split(",")
        .map((s) => Number.parseInt(s.trim(), 10))
        .filter((n) => Number.isFinite(n) && n >= 0)
    : [30_000, 120_000, 600_000];
  return { maxAttempts, delaysMs };
}

export async function sleepMs(ms: number): Promise<void> {
  if (ms <= 0) return;
  await new Promise((resolve) => setTimeout(resolve, ms));
}

/**
 * When SOCIAL_PUBLISH_FAST_RETRY=true (tests/dev), use 0ms delays.
 */
export function resolveRetryDelayMs(config: PublishRetryConfig, attemptIndex: number): number {
  if (process.env.SOCIAL_PUBLISH_FAST_RETRY === "true") return 0;
  return config.delaysMs[Math.min(attemptIndex, config.delaysMs.length - 1)] ?? 30_000;
}

export async function withPublishRetry<T extends { success: boolean; error?: string }>(input: {
  errorCode?: string;
  classify: (error: string | undefined) => { code: string; retryable: boolean };
  run: (attempt: number) => Promise<T>;
  onRetry?: (info: {
    attempt: number;
    delayMs: number;
    errorCode: string;
    message?: string;
  }) => Promise<void> | void;
}): Promise<T & { attempt: number; errorCode?: string; retryable?: boolean }> {
  const config = getPublishRetryConfig();
  let last: T | undefined;
  let lastCode: string | undefined;
  let lastRetryable = false;

  for (let attempt = 1; attempt <= config.maxAttempts; attempt++) {
    const result = await input.run(attempt);
    last = result;
    if (result.success) {
      return { ...result, attempt, errorCode: undefined, retryable: false };
    }

    const classified = input.errorCode
      ? {
          code: input.errorCode,
          retryable: isRetryableErrorCode(input.errorCode),
        }
      : input.classify(result.error);
    lastCode = classified.code;
    lastRetryable = classified.retryable;

    const canRetry =
      classified.retryable && attempt < config.maxAttempts && isRetryableErrorCode(classified.code);

    if (!canRetry) {
      return {
        ...result,
        attempt,
        errorCode: classified.code,
        retryable: classified.retryable,
      };
    }

    const delayMs = resolveRetryDelayMs(config, attempt - 1);
    await input.onRetry?.({
      attempt,
      delayMs,
      errorCode: classified.code,
      message: result.error,
    });
    await sleepMs(delayMs);
  }

  return {
    ...(last as T),
    attempt: config.maxAttempts,
    errorCode: lastCode,
    retryable: lastRetryable,
  };
}
