/**
 * Retry utilities for Crypto.com OTC API
 * Handles transient failures and rate limits (TOO_MANY_REQUESTS)
 */

export interface RetryOptions {
  maxRetries?: number;
  baseDelayMs?: number;
  maxDelayMs?: number;
  retryableErrors?: number[];
  exponentialBackoff?: boolean;
}

const DEFAULT_OPTIONS: Required<RetryOptions> = {
  maxRetries: 3,
  baseDelayMs: 1000,
  maxDelayMs: 10000,
  retryableErrors: [429, 500, 502, 503, 504], // Rate limit + server errors
  exponentialBackoff: true,
};

export async function withRetry<T>(
  fn: () => Promise<T>,
  options: RetryOptions = {}
): Promise<T> {
  const opts = { ...DEFAULT_OPTIONS, ...options };
  let lastError: Error | unknown;

  for (let attempt = 0; attempt <= opts.maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      lastError = error;

      if (attempt === opts.maxRetries) {
        throw error;
      }

      const statusCode = getStatusCode(error);
      if (statusCode && !opts.retryableErrors.includes(statusCode)) {
        throw error;
      }

      const delay = opts.exponentialBackoff
        ? Math.min(
            opts.baseDelayMs * Math.pow(2, attempt),
            opts.maxDelayMs
          )
        : opts.baseDelayMs;

      await sleep(delay);
    }
  }

  throw lastError;
}

function getStatusCode(error: unknown): number | undefined {
  if (error && typeof error === 'object') {
    const err = error as Record<string, unknown>;
    if (typeof err.response?.status === 'number') {
      return (err.response as { status: number }).status;
    }
    if (typeof err.code === 'number') {
      return err.code;
    }
  }
  return undefined;
}

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

/**
 * Exponential backoff for reconnection
 */
export function getExponentialBackoffDelay(
  attempt: number,
  baseMs: number = 1000,
  maxMs: number = 60000,
  jitter: boolean = true
): number {
  const delay = Math.min(baseMs * Math.pow(2, attempt), maxMs);
  if (jitter) {
    return delay * (0.5 + Math.random() * 0.5);
  }
  return delay;
}
