/**
 * Graceful Degradation Utilities
 * Provides fallback behavior and user-friendly error handling
 * Includes Retry-After headers for backpressure signaling (RFC 7231)
 */

import { logger } from '@/lib/logger';
import {
  DEFAULT_RETRY_AFTER,
  createDegradedServiceSignal,
  logBackpressure,
  type BackpressureSignal,
} from './backpressure';

// Scoped logger for degradation events
const degradationLogger = logger.scope('DEGRADATION');

export interface DegradationResult<T> {
  success: boolean;
  data?: T;
  fallback?: boolean;
  error?: string;
  userMessage: {
    en: string;
    ar: string;
  };
  /** Retry-After in seconds (RFC 7231) - present when fallback is true */
  retryAfterSeconds?: number;
  /** Backpressure signal for HTTP response generation */
  backpressureSignal?: BackpressureSignal;
}

export interface RetryConfig {
  maxAttempts: number;
  baseDelayMs: number;
  maxDelayMs: number;
}

// Default retry configuration
const DEFAULT_RETRY_CONFIG: RetryConfig = {
  maxAttempts: 3,
  baseDelayMs: 1000,
  maxDelayMs: 10000,
};

// Default user-friendly messages for common service failures
export const SERVICE_MESSAGES = {
  stripe: {
    en: 'Payment services are temporarily unavailable. Please try again in a few minutes.',
    ar: 'خدمات الدفع غير متوفرة مؤقتاً. يرجى المحاولة بعد بضع دقائق.',
  },
  whatsapp: {
    en: 'WhatsApp notifications are temporarily unavailable. You will receive updates via email instead.',
    ar: 'إشعارات واتساب غير متوفرة مؤقتاً. ستتلقى التحديثات عبر البريد الإلكتروني بدلاً من ذلك.',
  },
  database: {
    en: 'Service is temporarily unavailable. Please try again later.',
    ar: 'الخدمة غير متوفرة مؤقتاً. يرجى المحاولة لاحقاً.',
  },
  cache: {
    en: 'Some features may be slower than usual. We are working on it.',
    ar: 'قد تكون بعض الميزات أبطأ من المعتاد. نحن نعمل على حلها.',
  },
  email: {
    en: 'Email service is temporarily unavailable. Please try again later.',
    ar: 'خدمة البريد الإلكتروني غير متوفرة مؤقتاً. يرجى المحاولة لاحقاً.',
  },
  voiceAgent: {
    en: 'Voice agent service is temporarily unavailable. Please try again later.',
    ar: 'خدمة المساعد الصوتي غير متوفرة مؤقتاً. يرجى المحاولة لاحقاً.',
  },
  calendar: {
    en: 'Calendar integration is temporarily unavailable. Your booking has been saved.',
    ar: 'تكامل التقويم غير متوفر مؤقتاً. تم حفظ حجزك.',
  },
  general: {
    en: 'An unexpected error occurred. Please try again later.',
    ar: 'حدث خطأ غير متوقع. يرجى المحاولة لاحقاً.',
  },
} as const;

export type ServiceName = keyof typeof SERVICE_MESSAGES;

/**
 * Calculates delay with exponential backoff and jitter
 */
function calculateBackoffDelay(
  attempt: number,
  baseDelayMs: number,
  maxDelayMs: number
): number {
  // Exponential backoff: baseDelay * 2^attempt
  const exponentialDelay = baseDelayMs * Math.pow(2, attempt);
  // Cap at maxDelay
  const cappedDelay = Math.min(exponentialDelay, maxDelayMs);
  // Add jitter (random 0-25% of the delay)
  const jitter = cappedDelay * Math.random() * 0.25;
  return cappedDelay + jitter;
}

/**
 * Sleep for a specified number of milliseconds
 */
function sleep(ms: number): Promise<void> {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

/**
 * Extracts error message from various error types
 */
function getErrorMessage(error: unknown): string {
  if (error instanceof Error) {
    return error.message;
  }
  if (typeof error === 'string') {
    return error;
  }
  return 'Unknown error occurred';
}

/**
 * Logs degradation events for monitoring
 */
export function logDegradation(
  service: string,
  operation: string,
  error: Error | string,
  fallbackUsed: boolean
): void {
  const errorMessage = error instanceof Error ? error.message : error;
  const errorStack = error instanceof Error ? error.stack : undefined;

  degradationLogger.warn(`Service degradation: ${service}`, {
    service,
    operation,
    error: errorMessage,
    stack: errorStack,
    fallbackUsed,
    timestamp: new Date().toISOString(),
  });

  // In production, you might want to send this to a monitoring service
  // e.g., Sentry, DataDog, CloudWatch, etc.
}

/**
 * Retries an operation with exponential backoff
 * Throws the last error if all attempts fail
 */
export async function retryWithBackoff<T>(
  operation: () => Promise<T>,
  config?: Partial<RetryConfig>
): Promise<T> {
  const { maxAttempts, baseDelayMs, maxDelayMs } = {
    ...DEFAULT_RETRY_CONFIG,
    ...config,
  };

  let lastError: unknown;

  for (let attempt = 0; attempt < maxAttempts; attempt++) {
    try {
      return await operation();
    } catch (error) {
      lastError = error;

      // Don't wait after the last attempt
      if (attempt < maxAttempts - 1) {
        const delay = calculateBackoffDelay(attempt, baseDelayMs, maxDelayMs);
        degradationLogger.info(
          `Retry attempt ${attempt + 1}/${maxAttempts} failed, waiting ${Math.round(delay)}ms`,
          { error: getErrorMessage(error) }
        );
        await sleep(delay);
      }
    }
  }

  // All attempts failed, throw the last error
  throw lastError;
}

/**
 * Wraps an operation with graceful degradation
 * Returns fallback value if operation fails after retries
 * Includes Retry-After for backpressure signaling
 */
export async function withGracefulDegradation<T>(
  operation: () => Promise<T>,
  fallback: T,
  serviceName: ServiceName,
  config?: Partial<RetryConfig>
): Promise<DegradationResult<T>> {
  try {
    const data = await retryWithBackoff(operation, config);
    return {
      success: true,
      data,
      fallback: false,
      userMessage: {
        en: 'Operation completed successfully.',
        ar: 'تمت العملية بنجاح.',
      },
    };
  } catch (error) {
    const errorMessage = getErrorMessage(error);

    logDegradation(
      serviceName,
      'withGracefulDegradation',
      error instanceof Error ? error : errorMessage,
      true
    );

    // Create backpressure signal for degraded response
    const backpressureSignal = createDegradedServiceSignal(serviceName);
    logBackpressure(backpressureSignal);

    return {
      success: false,
      data: fallback,
      fallback: true,
      error: errorMessage,
      userMessage: SERVICE_MESSAGES[serviceName],
      retryAfterSeconds: backpressureSignal.retryAfterSeconds,
      backpressureSignal,
    };
  }
}

/**
 * Wraps an operation with graceful degradation but no fallback
 * Returns success: false if operation fails
 * Includes Retry-After for backpressure signaling
 */
export async function withGracefulFailure<T>(
  operation: () => Promise<T>,
  serviceName: ServiceName,
  config?: Partial<RetryConfig>
): Promise<DegradationResult<T>> {
  try {
    const data = await retryWithBackoff(operation, config);
    return {
      success: true,
      data,
      fallback: false,
      userMessage: {
        en: 'Operation completed successfully.',
        ar: 'تمت العملية بنجاح.',
      },
    };
  } catch (error) {
    const errorMessage = getErrorMessage(error);

    logDegradation(
      serviceName,
      'withGracefulFailure',
      error instanceof Error ? error : errorMessage,
      false
    );

    // Create backpressure signal for failed response
    const backpressureSignal = createDegradedServiceSignal(serviceName);
    logBackpressure(backpressureSignal);

    return {
      success: false,
      fallback: false,
      error: errorMessage,
      userMessage: SERVICE_MESSAGES[serviceName],
      retryAfterSeconds: backpressureSignal.retryAfterSeconds,
      backpressureSignal,
    };
  }
}

/**
 * Creates a degradation-aware wrapper for a service function
 * Useful for wrapping entire service modules
 * Includes Retry-After for backpressure signaling
 */
export function createDegradedService<TArgs extends unknown[], TResult>(
  fn: (...args: TArgs) => Promise<TResult>,
  fallback: TResult,
  serviceName: ServiceName,
  operationName: string,
  config?: Partial<RetryConfig>
): (...args: TArgs) => Promise<DegradationResult<TResult>> {
  return async (...args: TArgs): Promise<DegradationResult<TResult>> => {
    try {
      const data = await retryWithBackoff(() => fn(...args), config);
      return {
        success: true,
        data,
        fallback: false,
        userMessage: {
          en: 'Operation completed successfully.',
          ar: 'تمت العملية بنجاح.',
        },
      };
    } catch (error) {
      const errorMessage = getErrorMessage(error);

      logDegradation(
        serviceName,
        operationName,
        error instanceof Error ? error : errorMessage,
        true
      );

      // Create backpressure signal for degraded response
      const backpressureSignal = createDegradedServiceSignal(serviceName);
      logBackpressure(backpressureSignal);

      return {
        success: false,
        data: fallback,
        fallback: true,
        error: errorMessage,
        userMessage: SERVICE_MESSAGES[serviceName],
        retryAfterSeconds: backpressureSignal.retryAfterSeconds,
        backpressureSignal,
      };
    }
  };
}

/**
 * Executes multiple operations with fallback chain
 * Returns the first successful result or the last fallback
 * Includes Retry-After for backpressure signaling
 */
export async function withFallbackChain<T>(
  operations: Array<{
    operation: () => Promise<T>;
    serviceName: ServiceName;
  }>,
  finalFallback: T
): Promise<DegradationResult<T>> {
  for (const { operation, serviceName } of operations) {
    try {
      const data = await operation();
      return {
        success: true,
        data,
        fallback: false,
        userMessage: {
          en: 'Operation completed successfully.',
          ar: 'تمت العملية بنجاح.',
        },
      };
    } catch (error) {
      const errorMessage = getErrorMessage(error);
      logDegradation(
        serviceName,
        'fallbackChain',
        error instanceof Error ? error : errorMessage,
        false
      );
      // Continue to next operation in chain
    }
  }

  // All operations failed, return final fallback with backpressure
  const backpressureSignal = createDegradedServiceSignal('general');
  logBackpressure(backpressureSignal);

  return {
    success: false,
    data: finalFallback,
    fallback: true,
    error: 'All operations in fallback chain failed',
    userMessage: SERVICE_MESSAGES.general,
    retryAfterSeconds: backpressureSignal.retryAfterSeconds,
    backpressureSignal,
  };
}

/**
 * Circuit breaker state for a service
 */
interface CircuitBreakerState {
  failures: number;
  lastFailure: number;
  isOpen: boolean;
}

const circuitBreakerStates = new Map<string, CircuitBreakerState>();

/**
 * Circuit breaker configuration
 */
export interface CircuitBreakerConfig {
  failureThreshold: number;
  resetTimeoutMs: number;
}

const DEFAULT_CIRCUIT_BREAKER_CONFIG: CircuitBreakerConfig = {
  failureThreshold: 5,
  resetTimeoutMs: 30000, // 30 seconds
};

/**
 * Wraps an operation with circuit breaker pattern
 * Opens circuit after threshold failures, allows retry after timeout
 * Includes Retry-After headers for backpressure signaling (RFC 7231)
 */
export async function withCircuitBreaker<T>(
  operation: () => Promise<T>,
  fallback: T,
  serviceName: ServiceName,
  config?: Partial<CircuitBreakerConfig>
): Promise<DegradationResult<T>> {
  const { failureThreshold, resetTimeoutMs } = {
    ...DEFAULT_CIRCUIT_BREAKER_CONFIG,
    ...config,
  };

  const stateKey = serviceName;
  let state = circuitBreakerStates.get(stateKey);

  if (!state) {
    state = { failures: 0, lastFailure: 0, isOpen: false };
    circuitBreakerStates.set(stateKey, state);
  }

  // Check if circuit should be reset
  if (state.isOpen && Date.now() - state.lastFailure > resetTimeoutMs) {
    state.isOpen = false;
    state.failures = 0;
    degradationLogger.info(`Circuit breaker reset for ${serviceName}`);
  }

  // If circuit is open, return fallback immediately with Retry-After
  if (state.isOpen) {
    const remainingTimeoutMs = Math.max(
      0,
      resetTimeoutMs - (Date.now() - state.lastFailure)
    );
    const backpressureSignal = createDegradedServiceSignal(
      serviceName,
      Math.ceil(remainingTimeoutMs / 1000) || DEFAULT_RETRY_AFTER.CIRCUIT_BREAKER
    );
    logBackpressure(backpressureSignal);

    return {
      success: false,
      data: fallback,
      fallback: true,
      error: 'Circuit breaker is open',
      userMessage: SERVICE_MESSAGES[serviceName],
      retryAfterSeconds: backpressureSignal.retryAfterSeconds,
      backpressureSignal,
    };
  }

  try {
    const data = await operation();
    // Reset failures on success
    state.failures = 0;
    return {
      success: true,
      data,
      fallback: false,
      userMessage: {
        en: 'Operation completed successfully.',
        ar: 'تمت العملية بنجاح.',
      },
    };
  } catch (error) {
    const errorMessage = getErrorMessage(error);
    state.failures++;
    state.lastFailure = Date.now();

    // Open circuit if threshold reached
    if (state.failures >= failureThreshold) {
      state.isOpen = true;
      degradationLogger.warn(`Circuit breaker opened for ${serviceName}`, {
        failures: state.failures,
        threshold: failureThreshold,
      });
    }

    logDegradation(
      serviceName,
      'withCircuitBreaker',
      error instanceof Error ? error : errorMessage,
      true
    );

    // Create backpressure signal for degraded response
    const backpressureSignal = createDegradedServiceSignal(serviceName);
    logBackpressure(backpressureSignal);

    return {
      success: false,
      data: fallback,
      fallback: true,
      error: errorMessage,
      userMessage: SERVICE_MESSAGES[serviceName],
      retryAfterSeconds: backpressureSignal.retryAfterSeconds,
      backpressureSignal,
    };
  }
}

/**
 * Resets circuit breaker state for a service
 * Useful for testing or manual recovery
 */
export function resetCircuitBreaker(serviceName: ServiceName): void {
  circuitBreakerStates.delete(serviceName);
  degradationLogger.info(`Circuit breaker manually reset for ${serviceName}`);
}

/**
 * Gets the current circuit breaker state for a service
 */
export function getCircuitBreakerState(
  serviceName: ServiceName
): CircuitBreakerState | undefined {
  return circuitBreakerStates.get(serviceName);
}

/**
 * Converts a DegradationResult to HTTP response headers
 * Includes Retry-After when the result indicates a failure
 */
export function degradationResultToHeaders<T>(
  result: DegradationResult<T>
): Record<string, string> {
  const headers: Record<string, string> = {};

  if (!result.success && result.retryAfterSeconds) {
    headers['Retry-After'] = result.retryAfterSeconds.toString();
  }

  if (result.backpressureSignal) {
    headers['X-Backpressure-Reason'] = result.backpressureSignal.reason;
    if (result.backpressureSignal.service) {
      headers['X-Backpressure-Service'] = result.backpressureSignal.service;
    }
  }

  return headers;
}

/**
 * Gets the appropriate HTTP status code for a DegradationResult
 */
export function degradationResultToStatusCode<T>(
  result: DegradationResult<T>
): number {
  if (result.success) {
    return 200;
  }
  if (result.backpressureSignal) {
    return result.backpressureSignal.statusCode;
  }
  return 503; // Default to service unavailable for degraded responses
}
