/**
 * Timeout Detection & Recovery
 * 
 * Detects timeout errors from various sources (fetch, Supabase, edge functions)
 * and provides recovery strategies.
 * 
 * Part of Sacred Pipeline Phase 4: Transaction Resilience
 */

import { logger } from './logger';

export interface TimeoutError extends Error {
  isTimeout: true;
  source: 'fetch' | 'supabase' | 'edge-function' | 'database' | 'unknown';
  originalError?: unknown;
  duration?: number;
}

/**
 * Check if an error is a timeout error
 */
export function isTimeoutError(error: unknown): boolean {
  if (!error) return false;

  // Check for AbortController timeout
  if (error instanceof DOMException && error.name === 'AbortError') {
    return true;
  }

  // Check for fetch timeout
  if (error instanceof TypeError && error.message.includes('aborted')) {
    return true;
  }

  // Check error message for timeout keywords
  if (error instanceof Error) {
    const message = error.message.toLowerCase();
    return (
      message.includes('timeout') ||
      message.includes('timed out') ||
      message.includes('deadline exceeded') ||
      message.includes('request aborted') ||
      message.includes('etimedout')
    );
  }

  // Check Supabase/HTTP timeout status codes
  if (error && typeof error === 'object') {
    const errorObj = error as { status?: number; code?: string; message?: string };
    
    // HTTP 408 Request Timeout
    if (errorObj.status === 408) return true;
    
    // HTTP 504 Gateway Timeout
    if (errorObj.status === 504) return true;
    
    // Supabase timeout codes
    if (errorObj.code === 'PGRST301') return true; // Connection timeout
    if (errorObj.code === '57014') return true; // PostgreSQL query cancelled
    
    // Check message
    if (errorObj.message?.toLowerCase().includes('timeout')) return true;
  }

  return false;
}

/**
 * Wrap an error as a TimeoutError with source information
 */
export function wrapAsTimeoutError(
  error: unknown,
  source: TimeoutError['source'],
  duration?: number
): TimeoutError {
  const message = error instanceof Error ? error.message : 'Operation timed out';
  const timeoutError = new Error(message) as TimeoutError;
  
  timeoutError.name = 'TimeoutError';
  timeoutError.isTimeout = true;
  timeoutError.source = source;
  timeoutError.originalError = error;
  timeoutError.duration = duration;
  
  return timeoutError;
}

/**
 * Execute a function with a timeout wrapper
 * 
 * @param fn - Function to execute
 * @param timeoutMs - Timeout in milliseconds
 * @param source - Source identifier for error tracking
 * @returns Promise that resolves or rejects with timeout
 */
export async function withTimeout<T>(
  fn: () => Promise<T>,
  timeoutMs: number,
  source: TimeoutError['source'] = 'unknown'
): Promise<T> {
  const startTime = Date.now();
  const controller = new AbortController();
  
  const timeoutId = setTimeout(() => {
    controller.abort();
  }, timeoutMs);

  try {
    // Execute the function with abort signal if supported
    const result = await fn();
    clearTimeout(timeoutId);
    return result;
  } catch (error) {
    clearTimeout(timeoutId);
    const duration = Date.now() - startTime;
    
    // Check if error is timeout-related
    if (isTimeoutError(error) || controller.signal.aborted) {
      const timeoutError = wrapAsTimeoutError(error, source, duration);
      
      logger.error('Operation timed out', {
        source,
        duration,
        timeoutMs,
        originalError: error instanceof Error ? error.message : String(error)
      });
      
      throw timeoutError;
    }
    
    // Re-throw non-timeout errors
    throw error;
  }
}

/**
 * Categorize timeout severity for recovery strategy
 */
export function getTimeoutSeverity(error: TimeoutError): 'minor' | 'moderate' | 'critical' {
  const { duration, source } = error;
  
  // No duration means immediate abort - likely user action or critical failure
  if (!duration) return 'critical';
  
  // Database/edge function timeouts are more critical
  if (source === 'database' || source === 'edge-function') {
    if (duration > 30000) return 'critical'; // >30s
    if (duration > 10000) return 'moderate'; // >10s
    return 'minor';
  }
  
  // Fetch timeouts
  if (source === 'fetch') {
    if (duration > 60000) return 'critical'; // >60s
    if (duration > 20000) return 'moderate'; // >20s
    return 'minor';
  }
  
  return 'moderate';
}

/**
 * Get recommended retry strategy based on timeout error
 */
export function getTimeoutRetryStrategy(error: TimeoutError): {
  shouldRetry: boolean;
  delayMs: number;
  maxAttempts: number;
  increaseTimeout: boolean;
} {
  const severity = getTimeoutSeverity(error);
  
  switch (severity) {
    case 'minor':
      return {
        shouldRetry: true,
        delayMs: 1000,
        maxAttempts: 3,
        increaseTimeout: false,
      };
    
    case 'moderate':
      return {
        shouldRetry: true,
        delayMs: 3000,
        maxAttempts: 2,
        increaseTimeout: true, // Increase timeout by 50%
      };
    
    case 'critical':
      return {
        shouldRetry: false, // Don't auto-retry critical timeouts
        delayMs: 5000,
        maxAttempts: 1,
        increaseTimeout: true,
      };
  }
}

/**
 * User-friendly timeout error message
 */
export function getTimeoutErrorMessage(error: TimeoutError): string {
  const severity = getTimeoutSeverity(error);
  
  switch (severity) {
    case 'minor':
      return 'The request took longer than expected. Retrying...';
    
    case 'moderate':
      return 'The server is taking longer than usual to respond. Please wait while we retry.';
    
    case 'critical':
      return 'The operation timed out. Please check your connection and try again.';
  }
}
