/**
 * Edge Function Wrapper with Comprehensive Error Handling
 * 
 * Provides standardized:
 * - Request/response logging
 * - Error handling and formatting
 * - Distributed tracing
 * - Type validation
 * - Performance monitoring
 */

import { 
  edgeLogger, 
  startSpan, 
  endSpan, 
  addSpanEvent, 
  logSpan,
  logSpanToDatabase,
  extractSpanContextFromHeaders,
  type Span
} from './logger.ts';
import { formatEdgeError, toError } from './errorFormatter.ts';
import { ValidationError, logValidationError } from './typeValidation.ts';
import { createClient } from "https://esm.sh/@supabase/supabase-js@2.57.4";

export interface EdgeFunctionConfig {
  name: string;
  requireAuth?: boolean;
  requiredRoles?: string[];
  useServiceRole?: boolean;
  corsHeaders?: HeadersInit;
  logRequests?: boolean;
  logResponses?: boolean;
}

export interface EdgeFunctionContext {
  requestId: string;
  span: Span;
  userId?: string;
  user?: any;
  supabase: any;
}

export type EdgeFunctionHandler = (
  req: Request,
  context: EdgeFunctionContext
) => Promise<Response>;

/**
 * Wrap an edge function with comprehensive error handling
 */
export function wrapEdgeFunction(
  config: EdgeFunctionConfig,
  handler: EdgeFunctionHandler
): (req: Request) => Promise<Response> {
  const {
    name,
    requireAuth = true,
    requiredRoles = [],
    useServiceRole = false,
    corsHeaders = {},
    logRequests = true,
    logResponses = true,
  } = config;

  return async (req: Request): Promise<Response> => {
    // ========================================================================
    // STEP 1: Handle CORS preflight
    // ========================================================================
    if (req.method === 'OPTIONS') {
      return new Response(null, { 
        status: 204,
        headers: corsHeaders 
      });
    }

    // ========================================================================
    // STEP 2: Initialize tracing
    // ========================================================================
    const parentSpanContext = extractSpanContextFromHeaders(req.headers);
    const span = startSpan(
      name,
      'SERVER',
      parentSpanContext,
      {
        'http.method': req.method,
        'http.url': req.url,
        'function.name': name,
      }
    );
    const requestId = span.spanId;

    // ========================================================================
    // STEP 3: Log incoming request
    // ========================================================================
    if (logRequests) {
      edgeLogger.info('Request received', {
        requestId,
        action: name,
        method: req.method,
        url: req.url,
        hasAuth: req.headers.has('Authorization'),
        contentType: req.headers.get('Content-Type'),
        userAgent: req.headers.get('User-Agent'),
      });
    }

    try {
      // ====================================================================
      // STEP 4: Create Supabase client
      // ====================================================================
      const supabaseUrl = Deno.env.get('SUPABASE_URL')!;
      const authHeader = req.headers.get('Authorization');
      
      let supabase;
      if (useServiceRole) {
        // Use service role key for backend operations
        const serviceRoleKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!;
        supabase = createClient(supabaseUrl, serviceRoleKey);
        addSpanEvent(span, 'supabase_client_created', { type: 'service_role' });
      } else if (authHeader) {
        // Use anon key with user's auth header
        const anonKey = Deno.env.get('SUPABASE_ANON_KEY')!;
        supabase = createClient(supabaseUrl, anonKey, {
          global: { headers: { Authorization: authHeader } }
        });
        addSpanEvent(span, 'supabase_client_created', { type: 'authenticated' });
      } else {
        // Use anon key without auth
        const anonKey = Deno.env.get('SUPABASE_ANON_KEY')!;
        supabase = createClient(supabaseUrl, anonKey);
        addSpanEvent(span, 'supabase_client_created', { type: 'anonymous' });
      }

      // ====================================================================
      // STEP 5: Authentication (if required)
      // ====================================================================
      let userId: string | undefined;
      let user: any = undefined;
      
      if (requireAuth) {
        addSpanEvent(span, 'authentication_start');
        
        if (!authHeader) {
          addSpanEvent(span, 'authentication_failed', { reason: 'missing_header' });
          endSpan(span, 'error');
          logSpan(span);
          
          return new Response(
            JSON.stringify({ 
              error: 'Missing Authorization header',
              requestId 
            }),
            { 
              status: 401, 
              headers: { ...corsHeaders, 'Content-Type': 'application/json' } 
            }
          );
        }

        // Get user from Supabase
        const { data: { user: authUser }, error: userError } = await supabase.auth.getUser();
        
        if (userError || !authUser) {
          addSpanEvent(span, 'authentication_failed', { 
            reason: 'invalid_token',
            error: formatEdgeError(userError) 
          });
          endSpan(span, 'error', userError);
          logSpan(span);
          
          return new Response(
            JSON.stringify({ 
              error: 'Invalid authentication token',
              requestId 
            }),
            { 
              status: 401, 
              headers: { ...corsHeaders, 'Content-Type': 'application/json' } 
            }
          );
        }
        
        user = authUser;
        userId = authUser.id;
        
        addSpanEvent(span, 'authentication_success', { userId });
        span.attributes['user.id'] = userId;

        // ====================================================================
        // STEP 6: Role verification (if required)
        // ====================================================================
        if (requiredRoles.length > 0) {
          addSpanEvent(span, 'role_check_start', { requiredRoles });
          
          let hasRequiredRole = false;
          for (const role of requiredRoles) {
            const { data: hasRole } = await supabase
              .rpc('has_role', { _user_id: userId, _role: role });
            
            if (hasRole) {
              hasRequiredRole = true;
              addSpanEvent(span, 'role_check_success', { role });
              break;
            }
          }
          
          if (!hasRequiredRole) {
            addSpanEvent(span, 'role_check_failed', { 
              userId,
              requiredRoles 
            });
            endSpan(span, 'error');
            logSpan(span);
            
            return new Response(
              JSON.stringify({ 
                error: 'Insufficient permissions',
                requestId 
              }),
              { 
                status: 403, 
                headers: { ...corsHeaders, 'Content-Type': 'application/json' } 
              }
            );
          }
        }
      }

      // ====================================================================
      // STEP 7: Execute handler
      // ====================================================================
      addSpanEvent(span, 'handler_start');
      
      const context: EdgeFunctionContext = {
        requestId,
        span,
        userId,
        user,
        supabase,
      };

      const response = await handler(req, context);

      // ====================================================================
      // STEP 6: Log success response
      // ====================================================================
      addSpanEvent(span, 'handler_complete', { 
        status: response.status,
        statusText: response.statusText 
      });
      
      const duration = span.endTime ? span.duration : Date.now() - span.startTime;
      
      if (logResponses) {
        edgeLogger.info('Request completed', {
          requestId,
          action: name,
          status: response.status,
          duration,
        });
      }

      endSpan(span, 'ok');
      logSpan(span);
      logSpanToDatabase(span, requestId);

      // Clone response to add tracking headers
      const responseBody = await response.text();
      const enhancedResponse = new Response(responseBody, {
        status: response.status,
        statusText: response.statusText,
        headers: {
          ...Object.fromEntries(response.headers.entries()),
          'X-Request-Id': requestId,
          'X-Span-Id': span.spanId,
          'X-Trace-Id': span.traceId,
          'X-Duration-Ms': duration.toString(),
        },
      });

      return enhancedResponse;

    } catch (error) {
      // ====================================================================
      // STEP 7: Handle errors
      // ====================================================================
      
      // Validation errors (client error)
      if (error instanceof ValidationError) {
        addSpanEvent(span, 'validation_error', {
          field: error.field,
          expected: error.expected,
          received: error.received,
        });
        
        logValidationError(error, requestId, name);
        endSpan(span, 'error', error);
        logSpan(span);
        logSpanToDatabase(span, requestId);

        const duration = span.endTime ? span.duration : Date.now() - span.startTime;
        
        return new Response(
          JSON.stringify({
            error: error.message,
            field: error.field,
            expected: error.expected,
            received: error.received,
            requestId,
          }),
          {
            status: 400,
            headers: { 
              ...corsHeaders, 
              'Content-Type': 'application/json',
              'X-Request-Id': requestId,
              'X-Span-Id': span.spanId,
              'X-Trace-Id': span.traceId,
              'X-Duration-Ms': duration.toString(),
            },
          }
        );
      }

      // Database errors (check for specific codes)
      const errorObj = error as any;
      if (errorObj?.code) {
        addSpanEvent(span, 'database_error', {
          code: errorObj.code,
          message: errorObj.message,
        });

        // Handle specific error codes
        let status = 500;
        let message = formatEdgeError(error);

        if (errorObj.code === '23505') {
          // Unique constraint violation
          status = 409;
          message = 'A record with this information already exists';
        } else if (errorObj.code === '23503') {
          // Foreign key violation
          status = 400;
          message = 'Referenced record does not exist';
        } else if (errorObj.code === '23514') {
          // Check constraint violation
          status = 400;
          message = 'Data violates database constraints';
        } else if (errorObj.code === 'P0001') {
          // Raised exception
          status = 400;
          message = errorObj.message || 'Database validation failed';
        } else if (errorObj.code === '42501') {
          // Insufficient privilege
          status = 403;
          message = 'Permission denied';
        }

        edgeLogger.error('Database error', {
          requestId,
          action: name,
          errorCode: errorObj.code,
          errorMessage: errorObj.message,
          errorDetails: errorObj.details,
          errorHint: errorObj.hint,
        });

        endSpan(span, 'error', error);
        logSpan(span);
        logSpanToDatabase(span, requestId);

        const duration = span.endTime ? span.duration : Date.now() - span.startTime;

        return new Response(
          JSON.stringify({
            error: message,
            code: errorObj.code,
            details: errorObj.details,
            requestId,
          }),
          {
            status,
            headers: { 
              ...corsHeaders, 
              'Content-Type': 'application/json',
              'X-Request-Id': requestId,
              'X-Span-Id': span.spanId,
              'X-Trace-Id': span.traceId,
              'X-Duration-Ms': duration.toString(),
            },
          }
        );
      }

      // Generic errors
      const errorMessage = formatEdgeError(error);
      
      addSpanEvent(span, 'unhandled_error', {
        error: errorMessage,
        errorType: error instanceof Error ? error.name : typeof error,
      });

      edgeLogger.error('Unhandled error', {
        requestId,
        action: name,
        error: errorMessage,
        errorType: error instanceof Error ? error.name : typeof error,
        stack: error instanceof Error ? error.stack : undefined,
      });

      endSpan(span, 'error', error);
      logSpan(span);
      logSpanToDatabase(span, requestId);

      const duration = span.endTime ? span.duration : Date.now() - span.startTime;

      return new Response(
        JSON.stringify({
          error: 'Internal server error',
          message: errorMessage,
          requestId,
        }),
        {
          status: 500,
          headers: { 
            ...corsHeaders, 
            'Content-Type': 'application/json',
            'X-Request-Id': requestId,
            'X-Span-Id': span.spanId,
            'X-Trace-Id': span.traceId,
            'X-Duration-Ms': duration.toString(),
          },
        }
      );
    }
  };
}

/**
 * Create a simple edge function with standard error handling
 * 
 * Example usage:
 * ```ts
 * serve(createEdgeFunction({
 *   name: 'my-function',
 *   requireAuth: true,
 *   corsHeaders: myCorsHeaders,
 * }, async (req, { requestId, span, userId }) => {
 *   // Your handler logic here
 *   return new Response(JSON.stringify({ success: true }), {
 *     status: 200,
 *     headers: { 'Content-Type': 'application/json' }
 *   });
 * }));
 * ```
 */
export function createEdgeFunction(
  config: EdgeFunctionConfig,
  handler: EdgeFunctionHandler
): (req: Request) => Promise<Response> {
  return wrapEdgeFunction(config, handler);
}
