import { logger } from '../logger.js';

export interface RetryConfig {
  /** Maximum number of retry attempts (default: 3). Does not count the initial attempt. */
  maxRetries: number;
  /** Initial delay in milliseconds before first retry (default: 1000). */
  initialDelayMs: number;
  /** Multiplier applied to delay after each retry (default: 2). */
  backoffMultiplier: number;
  /** Maximum delay in milliseconds (default: 30000). */
  maxDelayMs: number;
  /** Errors matching these patterns should NOT be retried (e.g. auth errors, invalid requests). */
  nonRetryablePatterns: string[];
}

export const DEFAULT_RETRY_CONFIG: RetryConfig = {
  maxRetries: 3,
  initialDelayMs: 1000,
  backoffMultiplier: 2,
  maxDelayMs: 30000,
  nonRetryablePatterns: [
    'invalid_api_key',
    'authentication',
    'unauthorized',
    'invalid_request',
    'context_length_exceeded',
    'content_policy',
    'does not support',
    'timeout',
    'timed out',
    'request aborted',
  ],
};

export function isRetryable(error: Error, nonRetryablePatterns: string[]): boolean {
  const msg = error.message.toLowerCase();
  return !nonRetryablePatterns.some(pattern => msg.includes(pattern.toLowerCase()));
}

export async function withRetry<T>(
  fn: () => Promise<T>,
  config: RetryConfig = DEFAULT_RETRY_CONFIG,
  label?: string,
): Promise<T> {
  let lastError: Error | undefined;

  for (let attempt = 0; attempt <= config.maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      lastError = error instanceof Error ? error : new Error(String(error));

      // Don't retry non-retryable errors
      if (!isRetryable(lastError, config.nonRetryablePatterns)) {
        throw lastError;
      }

      // Don't retry if we've exhausted attempts
      if (attempt >= config.maxRetries) {
        throw lastError;
      }

      // Calculate delay with exponential backoff + jitter
      const baseDelay = config.initialDelayMs * Math.pow(config.backoffMultiplier, attempt);
      const delay = Math.min(baseDelay, config.maxDelayMs);
      const jitter = delay * (0.5 + Math.random() * 0.5); // 50-100% of delay

      logger.debug(
        `[retry] ${label ?? 'operation'} attempt ${attempt + 1}/${config.maxRetries} failed: ${lastError.message}. Retrying in ${Math.round(jitter)}ms...`,
      );

      await new Promise(resolve => setTimeout(resolve, jitter));
    }
  }

  throw lastError ?? new Error('Retry failed with no error');
}
