/**
 * Platform Service Error System
 * 
 * This module provides a comprehensive error handling system for the Platform Service,
 * with structured error types, error codes, and consistent patterns for logging and recovery.
 */

// Export error codes and types
export * from './error.codes.js';

// Export base error classes
export * from './base.errors.js';

// Export domain-specific error classes
export * from './email.errors.js';
export * from './mta.errors.js';
export * from './reputation.errors.js';

// Export error handler
export * from './error-handler.js';

// Export utility function to create specific error types based on the error category
import { getErrorClassForCategory } from './base.errors.js';
export { getErrorClassForCategory };

// Import needed classes for utility functions
import { PlatformError } from './base.errors.js';
import { ErrorSeverity, ErrorCategory, ErrorRecoverability } from './error.codes.js';

/**
 * Create a typed error from a standard Error
 * Useful for converting errors from external libraries or APIs
 * 
 * @param error Standard error to convert
 * @param code Error code to assign
 * @param contextData Additional context data
 * @returns Typed PlatformError
 */
export function fromError(
  error: Error,
  code: string,
  contextData: Record<string, any> = {}
): PlatformError {
  return new PlatformError(
    error.message,
    code,
    ErrorSeverity.MEDIUM,
    ErrorCategory.OPERATION,
    ErrorRecoverability.NON_RECOVERABLE,
    {
      data: {
        ...contextData,
        originalError: {
          name: error.name,
          message: error.message,
          stack: error.stack
        }
      }
    }
  );
}

/**
 * Determine if an error is retryable
 * 
 * @param error Error to check
 * @returns Boolean indicating if the error should be retried
 */
export function isRetryable(error: any): boolean {
  // If it's our platform error, use its recoverability property
  if (error && typeof error === 'object' && 'recoverability' in error) {
    return error.recoverability === ErrorRecoverability.RECOVERABLE ||
           error.recoverability === ErrorRecoverability.MAYBE_RECOVERABLE ||
           error.recoverability === ErrorRecoverability.TRANSIENT;
  }
  
  // Check if it's a network error (these are often transient)
  if (error && typeof error === 'object' && error.code) {
    const networkErrors = [
      'ECONNRESET', 'ECONNREFUSED', 'ETIMEDOUT', 'EHOSTUNREACH',
      'ENETUNREACH', 'ENOTFOUND', 'EPROTO', 'ECONNABORTED'
    ];
    
    return networkErrors.includes(error.code);
  }
  
  // By default, we can't determine if the error is retryable
  return false;
}

/**
 * Create a wrapped version of a function that catches errors
 * and converts them to typed PlatformErrors
 * 
 * @param fn Function to wrap
 * @param errorCode Default error code to use
 * @param contextData Additional context data
 * @returns Wrapped function
 */
export function withErrorHandling<T extends (...args: any[]) => Promise<any>>(
  fn: T,
  errorCode: string,
  contextData: Record<string, any> = {}
): T {
  return (async function(...args: Parameters<T>): Promise<ReturnType<T>> {
    try {
      return await fn(...args);
    } catch (error) {
      if (error && typeof error === 'object' && 'code' in error) {
        // Already a typed error, rethrow
        throw error;
      }
      
      throw fromError(
        error instanceof Error ? error : new Error(String(error)),
        errorCode,
        {
          ...contextData,
          fnName: fn.name,
          args: args.map(arg => 
            typeof arg === 'object' 
              ? '[Object]' 
              : String(arg).substring(0, 100)
          )
        }
      );
    }
  }) as T;
}

/**
 * Retry a function with exponential backoff
 * 
 * @param fn Function to retry
 * @param options Retry options
 * @returns Function result or throws after max retries
 */
export async function retry<T>(
  fn: () => Promise<T>,
  options: {
    maxRetries?: number;
    initialDelay?: number;
    maxDelay?: number;
    backoffFactor?: number;
    retryableErrors?: Array<string | RegExp>;
  } = {}
): Promise<T> {
  const {
    maxRetries = 3,
    initialDelay = 1000,
    maxDelay = 30000,
    backoffFactor = 2,
    retryableErrors = []
  } = options;
  
  let lastError: Error;
  
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      lastError = error instanceof Error 
        ? error 
        : new Error(String(error));
      
      // Check if we should retry
      const shouldRetry = attempt < maxRetries && (
        isRetryable(error) ||
        retryableErrors.some(pattern => {
          if (typeof pattern === 'string') {
            return lastError.message.includes(pattern);
          }
          return pattern.test(lastError.message);
        })
      );
      
      if (!shouldRetry) {
        throw lastError;
      }
      
      // Calculate delay with exponential backoff
      const delay = Math.min(initialDelay * Math.pow(backoffFactor, attempt), maxDelay);
      
      // Add jitter to prevent thundering herd problem (±20%)
      const jitter = 0.8 + Math.random() * 0.4;
      const actualDelay = Math.floor(delay * jitter);
      
      // Wait before next retry
      await new Promise(resolve => setTimeout(resolve, actualDelay));
    }
  }
  
  // This should never happen, but TypeScript needs it
  throw lastError!;
}