/**
 * Edge Function Request Tracking Wrapper
 * 
 * Wraps Supabase function invocations with request tracking for debugging and monitoring.
 * Provides correlation IDs for tracing requests across the system.
 */

import { supabase } from '@/lib/supabaseClient';
import { trackRequest } from './requestTracking';
import { getErrorMessage } from './errorHandler';
import { withRetry, isRetryableError, type RetryOptions } from './retryHelpers';
import { breadcrumb } from './errorBreadcrumbs';
import { logger } from './logger';

/**
 * Invoke a Supabase edge function with request tracking
 * 
 * @param functionName - Name of the edge function to invoke
 * @param payload - Request payload
 * @param userId - User ID for tracking (optional)
 * @param parentRequestId - Parent request ID for chaining (optional)
 * @param traceId - Trace ID for distributed tracing (optional)
 * @param timeout - Request timeout in milliseconds (default: 30000)
 * @param retryOptions - Optional retry configuration
 * @param customHeaders - Custom headers to include in the request (e.g., X-Idempotency-Key)
 * @returns Response data with requestId, status, and tracking info
 */
export async function invokeWithTracking<T = any>(
  functionName: string,
  payload: any = {},
  userId?: string,
  parentRequestId?: string,
  traceId?: string,
  timeout: number = 30000,
  retryOptions?: Partial<RetryOptions>,
  customHeaders?: Record<string, string>
): Promise<{ 
  data: T | null; 
  error: any; 
  requestId: string; 
  duration: number; 
  attempts?: number; 
  status?: number;
  traceId?: string;
  backendRequestId?: string;
  backendSpanId?: string;
  backendTraceId?: string;
  backendDuration?: number;
}> {
  // Configure retry options with defaults
  const effectiveRetryOptions: RetryOptions = {
    maxAttempts: retryOptions?.maxAttempts ?? 3,
    baseDelay: retryOptions?.baseDelay ?? 1000,
    maxDelay: retryOptions?.maxDelay ?? 10000,
    backoffMultiplier: retryOptions?.backoffMultiplier ?? 2,
    jitter: true,
    shouldRetry: isRetryableError,
    onRetry: (attempt, error, delay) => {
      // Log retry attempt to breadcrumbs
      breadcrumb.apiCall(
        `/functions/${functionName}`,
        'POST',
        undefined // status unknown during retry
      );
      
      console.info(`Retrying ${functionName} (attempt ${attempt}) after ${delay}ms:`, 
        getErrorMessage(error)
      );
    },
  };

  let attemptCount = 0;
  
  try {
    const { result, requestId, duration } = await trackRequest(
      {
        endpoint: `/functions/${functionName}`,
        method: 'POST',
        userId,
        parentRequestId,
        traceId,
      },
      async (context) => {
        return await withRetry(
          async () => {
            attemptCount++;
            
            const controller = new AbortController();
            const timeoutId = setTimeout(() => controller.abort(), timeout);
            
            // Generate W3C Trace Context header
            const effectiveTraceId = context.traceId || crypto.randomUUID();
            const spanId = crypto.randomUUID();
            const traceparent = `00-${effectiveTraceId}-${spanId}-01`;
            
            // Add breadcrumb with trace context
            breadcrumb.apiCall(
              `/functions/${functionName}`,
              'POST',
              undefined
            );
            
            try {
              const { data, error } = await supabase.functions.invoke<T>(functionName, {
                body: { 
                  ...payload, 
                  clientRequestId: context.requestId,
                  traceId: effectiveTraceId,
                },
                signal: controller.signal,
                headers: {
                  ...customHeaders,
                  'traceparent': traceparent,
                },
              });
              
              clearTimeout(timeoutId);
              
              if (error) {
                // Enhance error with status and context for retry logic
                const enhancedError = new Error(error.message || 'Edge function error');
                (enhancedError as any).status = error.status;
                (enhancedError as any).context = error.context;
                throw enhancedError;
              }
              
              return data;
            } catch (error) {
              clearTimeout(timeoutId);
              throw error;
            }
          },
          effectiveRetryOptions
        );
      }
    );

    // Extract backend metadata from successful response
    let backendRequestId: string | undefined;
    let backendSpanId: string | undefined;
    let backendTraceId: string | undefined;
    let backendDuration: number | undefined;

    // Note: Response headers from edge functions are not currently accessible via the client
    // Backend metadata extraction will be enhanced when Supabase client supports response headers
    // For now, backend can include metadata in response body if needed

    return { 
      data: result, 
      error: null, 
      requestId, 
      duration, 
      attempts: attemptCount, 
      status: 200,
      traceId,
      backendRequestId,
      backendSpanId,
      backendTraceId,
      backendDuration,
    };
  } catch (error: unknown) {
    // Handle AbortError specifically
    if (error instanceof Error && error.name === 'AbortError') {
      return {
        data: null,
        error: { 
          message: `Request timeout: ${functionName} took longer than ${timeout}ms to respond`,
          code: 'TIMEOUT',
        },
        requestId: 'timeout',
        duration: timeout,
        attempts: attemptCount,
        status: 408,
        traceId: undefined,
      };
    }
    
    const errorMessage = getErrorMessage(error);
    
    // Extract backend metadata from error context
    let backendRequestId: string | undefined;
    let backendSpanId: string | undefined;
    let backendTraceId: string | undefined;
    let backendDuration: number | undefined;

    if (error && typeof error === 'object') {
      const context = (error as any).context;
      if (context) {
        backendRequestId = context['x-request-id'];
        backendSpanId = context['x-span-id'];
        backendTraceId = context['x-trace-id'];
        backendDuration = context['x-duration-ms'] ? parseInt(context['x-duration-ms']) : undefined;
      }
    }
    
    // Detect CORS errors specifically
    const isCorsError = errorMessage.toLowerCase().includes('cors') || 
                       errorMessage.toLowerCase().includes('cross-origin') ||
                       errorMessage.toLowerCase().includes('failed to send') ||
                       (error instanceof TypeError && errorMessage.toLowerCase().includes('failed to fetch'));
    
    // Enhanced error logging
    logger.error('[EdgeFunctionTracking] Edge function invocation failed', {
      functionName,
      error: errorMessage,
      errorType: isCorsError ? 'CORS/Network' : (error as any)?.name || 'Unknown',
      attempts: attemptCount,
      isCorsError,
      debugHint: isCorsError ? 'Browser blocked request - verify CORS headers allow X-Idempotency-Key or check network connectivity' : undefined,
      status: (error as any)?.status,
      backendMetadata: backendRequestId ? {
        requestId: backendRequestId,
        spanId: backendSpanId,
        traceId: backendTraceId,
        duration: backendDuration,
      } : undefined,
    });
    
    return {
        data: null,
        error: { 
          message: errorMessage, 
          status: (error as any)?.status,
          isCorsError,
        },
        requestId: 'unknown',
        duration: 0,
        attempts: attemptCount,
        status: (error as any)?.status,
        traceId: undefined,
        backendRequestId,
        backendSpanId,
        backendTraceId,
        backendDuration,
      };
    }
  }

/**
 * Invoke multiple edge functions in parallel with batch tracking
 * 
 * Uses a shared trace ID to correlate all operations.
 * 
 * @param operations - Array of function invocation configurations
 * @param userId - User ID for tracking
 * @returns Array of results with their request IDs
 */
export async function invokeBatchWithTracking<T = any>(
  operations: Array<{
    functionName: string;
    payload: any;
    retryOptions?: Partial<RetryOptions>;
  }>,
  userId?: string
): Promise<
  Array<{
    functionName: string;
    data: T | null;
    error: any;
    requestId: string;
    duration: number;
    attempts?: number;
    status?: number;
  }>
> {
  const traceId = crypto.randomUUID();

  const results = await Promise.allSettled(
    operations.map(async (op) => {
      const result = await invokeWithTracking<T>(
        op.functionName,
        op.payload,
        userId,
        undefined,
        traceId,
        30000,
        op.retryOptions
      );
      return { functionName: op.functionName, ...result };
    })
  );

  return results.map((result, index) => {
    if (result.status === 'fulfilled') {
      return result.value;
    } else {
      return {
        functionName: operations[index].functionName,
        data: null,
        error: { message: result.reason?.message || 'Unknown error' },
        requestId: 'unknown',
        duration: 0,
      };
    }
  });
}
