/**
 * Edge Function Retry Helper
 * Provides exponential backoff retry logic for external API calls
 */

import { edgeLogger } from './logger.ts';

export interface EdgeRetryOptions {
  maxAttempts?: number;
  baseDelay?: number;
  maxDelay?: number;
  backoffMultiplier?: number;
  jitter?: boolean;
  shouldRetry?: (error: unknown) => boolean;
}

/**
 * Determines if an error is transient and should be retried
 */
export function isRetryableError(error: unknown): boolean {
  // Network errors
  if (error instanceof TypeError && error.message.includes('fetch')) return true;
  
  if (error instanceof Error) {
    const msg = error.message.toLowerCase();
    if (msg.includes('network') || msg.includes('timeout') || msg.includes('econnrefused')) {
      return true;
    }
  }
  
  // HTTP status codes that should be retried
  if (error && typeof error === 'object') {
    const httpError = error as { status?: number };
    
    // Rate limiting
    if (httpError.status === 429) return true;
    
    // Service unavailable or gateway timeout
    if (httpError.status === 503 || httpError.status === 504) return true;
    
    // Server errors (5xx)
    if (httpError.status && httpError.status >= 500 && httpError.status < 600) {
      return true;
    }
  }
  
  return false;
}

/**
 * Calculate exponential backoff delay with optional jitter
 */
function calculateBackoffDelay(
  attempt: number,
  baseDelay: number,
  maxDelay: number,
  backoffMultiplier: number,
  jitter: boolean
): number {
  const exponentialDelay = baseDelay * Math.pow(backoffMultiplier, attempt);
  const cappedDelay = Math.min(exponentialDelay, maxDelay);
  
  if (!jitter) return cappedDelay;
  
  // Add random jitter (-30% to +30%)
  const jitterAmount = cappedDelay * 0.3;
  const randomJitter = (Math.random() * 2 - 1) * jitterAmount;
  
  return Math.max(0, cappedDelay + randomJitter);
}

/**
 * Retry wrapper for asynchronous operations with exponential backoff
 * 
 * @param fn - Async function to retry
 * @param options - Retry configuration
 * @param requestId - Request ID for tracking
 * @param context - Context description for logging
 */
export async function withEdgeRetry<T>(
  fn: () => Promise<T>,
  options: EdgeRetryOptions = {},
  requestId: string,
  context: string
): Promise<T> {
  const maxAttempts = options.maxAttempts ?? 3;
  const baseDelay = options.baseDelay ?? 1000;
  const maxDelay = options.maxDelay ?? 10000;
  const backoffMultiplier = options.backoffMultiplier ?? 2;
  const jitter = options.jitter ?? true;
  const shouldRetry = options.shouldRetry ?? isRetryableError;
  
  let lastError: unknown;
  
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
    try {
      return await fn();
    } catch (error) {
      lastError = error;
      
      // Don't retry if this is the last attempt
      if (attempt === maxAttempts - 1) {
        edgeLogger.error('All retry attempts exhausted', {
          requestId,
          context,
          attempts: maxAttempts,
          error: error instanceof Error ? error.message : 'Unknown error'
        });
        throw error;
      }
      
      // Don't retry if error is not retryable
      if (!shouldRetry(error)) {
        edgeLogger.info('Error not retryable, failing immediately', {
          requestId,
          context,
          attempt: attempt + 1,
          error: error instanceof Error ? error.message : 'Unknown error'
        });
        throw error;
      }
      
      // Calculate delay for next retry
      const delay = calculateBackoffDelay(attempt, baseDelay, maxDelay, backoffMultiplier, jitter);
      
      edgeLogger.info('Retrying after error', {
        requestId,
        context,
        attempt: attempt + 1,
        maxAttempts,
        delay: Math.round(delay),
        error: error instanceof Error ? error.message : 'Unknown error'
      });
      
      // Wait before retrying
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
  
  // This should never be reached, but TypeScript needs it
  throw lastError;
}
