import { toast } from 'sonner';
import { logger } from './logger';
import { supabase } from '@/integrations/supabase/client';
import { breadcrumbManager } from './errorBreadcrumbs';
import { captureEnvironmentContext } from './environmentContext';

export type ErrorContext = {
  action: string;
  userId?: string;
  metadata?: Record<string, unknown>;
  duration?: number; // Optional: milliseconds the operation took
};

export class AppError extends Error {
  constructor(
    message: string,
    public code: string,
    public userMessage?: string
  ) {
    super(message);
    this.name = 'AppError';
  }
}

/**
 * Check if error is a Supabase connection/API error
 */
export function isSupabaseConnectionError(error: unknown): boolean {
  if (error && typeof error === 'object') {
    const supabaseError = error as { code?: string; status?: number; message?: string };
    
    // Connection timeout errors
    if (supabaseError.code === 'PGRST301') return true; // Timeout
    if (supabaseError.code === 'PGRST000') return true; // Connection error
    
    // 5xx server errors
    if (supabaseError.status && supabaseError.status >= 500) return true;
    
    // Database connection errors (08xxx codes)
    if (supabaseError.code?.startsWith('08')) return true;
  }
  
  // Network fetch errors
  if (error instanceof TypeError) {
    const message = error.message.toLowerCase();
    if (message.includes('fetch') || message.includes('network') || message.includes('failed to fetch')) {
      return true;
    }
  }
  
  return false;
}

export const handleError = (
  error: unknown,
  context: ErrorContext
): string => {
  // Generate or use existing error ID
  const errorId = (context.metadata?.requestId as string) || crypto.randomUUID();
  const shortErrorId = errorId.slice(0, 8);
  
  // Check if this is a connection error and dispatch event
  if (isSupabaseConnectionError(error)) {
    window.dispatchEvent(new CustomEvent('api-connectivity-down'));
  }
  
  // Enhanced error message and stack extraction
  let errorMessage: string;
  let stack: string | undefined;
  let errorName = 'UnknownError';
  let supabaseErrorDetails: Record<string, any> | undefined;
  
  if (error instanceof Error) {
    errorMessage = error instanceof AppError 
      ? error.userMessage || error.message 
      : error.message;
    stack = error.stack;
    errorName = error.name;
    
    // Check if Error instance has attached Supabase metadata
    if ((error as any).supabaseCode) {
      supabaseErrorDetails = {
        code: (error as any).supabaseCode,
        details: (error as any).supabaseDetails,
        hint: (error as any).supabaseHint
      };
    }
  } else if (error && typeof error === 'object') {
    // Handle Supabase errors (plain objects with message/code/details)
    const supabaseError = error as { 
      message?: string; 
      code?: string; 
      details?: string; 
      hint?: string;
      stack?: string;
    };
    
    errorMessage = supabaseError.message || 'An unexpected error occurred';
    errorName = 'SupabaseError';
    
    // Capture Supabase error details for metadata
    supabaseErrorDetails = {
      code: supabaseError.code,
      details: supabaseError.details,
      hint: supabaseError.hint
    };
    
    // Try to extract stack from object
    if (supabaseError.stack && typeof supabaseError.stack === 'string') {
      stack = supabaseError.stack;
    } else if (supabaseError.code || supabaseError.details || supabaseError.hint) {
      // Create synthetic stack trace for Supabase errors to aid debugging
      const stackParts = [
        `SupabaseError: ${errorMessage}`,
        supabaseError.code ? `  Code: ${supabaseError.code}` : null,
        supabaseError.details ? `  Details: ${supabaseError.details}` : null,
        supabaseError.hint ? `  Hint: ${supabaseError.hint}` : null,
        `  at ${context.action}`,
        `  Reference ID: ${errorId}`
      ].filter(Boolean);
      
      stack = stackParts.join('\n');
    }
  } else if (typeof error === 'string') {
    errorMessage = error;
    // Generate synthetic stack trace for string errors
    stack = new Error().stack?.replace(/^Error\n/, `StringError: ${error}\n`);
  } else {
    errorMessage = 'An unexpected error occurred';
    // Generate synthetic stack trace for unknown error types
    stack = new Error().stack?.replace(/^Error\n/, `UnknownError: ${String(error)}\n`);
  }

  // Log to console/monitoring with enhanced debugging
  
  logger.error('Error occurred', {
    ...context,
    error: errorMessage,
    stack,
    errorId,
    errorName,
    errorType: typeof error,
    errorConstructor: error?.constructor?.name,
    hasStack: !!stack,
    isSyntheticStack: !!(error && typeof error === 'object' && !(error instanceof Error) && stack),
    supabaseError: supabaseErrorDetails,
  });
  
  // Additional debug logging when stack is missing
  if (!stack) {
    console.error('[handleError] Error without stack trace:', {
      type: typeof error,
      constructor: error?.constructor?.name,
      error: error,
      context,
      errorId
    });
  }

  // Log to database with breadcrumbs (non-blocking)
  try {
    const envContext = captureEnvironmentContext();
    const breadcrumbs = breadcrumbManager.getAll();
    
    // Fire-and-forget database logging
    supabase.rpc('log_request_metadata', {
      p_request_id: errorId,
      p_user_id: context.userId || undefined,
      p_endpoint: context.action,
      p_method: 'ERROR',
      p_status_code: 500,
      p_error_type: errorName,
      p_error_message: errorMessage,
      p_error_stack: stack,
      p_user_agent: navigator.userAgent,
      p_breadcrumbs: JSON.stringify({
        breadcrumbs,
        isRetry: context.metadata?.isRetry || false,
        attempt: context.metadata?.attempt,
        retriesExhausted: context.metadata?.retriesExhausted || false,
        supabaseError: supabaseErrorDetails,
        metadata: context.metadata
      }),
      p_timezone: envContext.timezone,
      p_referrer: document.referrer || undefined,
      p_duration_ms: context.duration,
    }).then(({ error: dbError }) => {
      if (dbError) {
        logger.error('Failed to log error to database', { dbError });
      }
    });
  } catch (logError) {
    logger.error('Failed to capture error context', { logError });
  }

  // Show user-friendly toast with error ID (skip for retry attempts)
  const isRetry = context.metadata?.isRetry === true || context.metadata?.attempt;
  if (!isRetry) {
    toast.error(context.action, {
      description: `${errorMessage}\n\nReference ID: ${shortErrorId}`,
      duration: 5000,
    });
  }

  return errorId;
};

export const handleSuccess = (
  title: string,
  description?: string
): void => {
  toast.success(title, {
    description,
    duration: 3000
  });
};

export const handleInfo = (
  title: string,
  description?: string
): void => {
  toast.info(title, {
    description,
    duration: 4000
  });
};

/**
 * Handle non-critical errors (background failures) that should be logged
 * to the database WITHOUT showing user toasts
 * Use this for fire-and-forget operations where user shouldn't be interrupted
 */
export const handleNonCriticalError = (
  error: unknown,
  context: ErrorContext
): string => {
  const errorId = crypto.randomUUID();
  const shortErrorId = errorId.slice(0, 8);
  
  const errorMessage = error instanceof AppError 
    ? error.userMessage || error.message
    : error instanceof Error 
    ? error.message 
    : 'An unexpected error occurred';

  // Log to console/monitoring (same as handleError)
  logger.error('Non-critical error occurred', {
    ...context,
    error: error instanceof Error ? error.message : String(error),
    stack: error instanceof Error ? error.stack : undefined,
    errorId,
    severity: 'low',
  });

  // Log to database with breadcrumbs (non-blocking, fire-and-forget)
  try {
    const envContext = captureEnvironmentContext();
    const breadcrumbs = breadcrumbManager.getAll();
    
    supabase.rpc('log_request_metadata', {
      p_request_id: errorId,
      p_user_id: context.userId || undefined,
      p_endpoint: context.action,
      p_method: 'NON_CRITICAL_ERROR',
      p_status_code: 500,
      p_error_type: error instanceof Error ? error.name : 'UnknownError',
      p_error_message: errorMessage,
      p_error_stack: error instanceof Error ? error.stack : undefined,
      p_user_agent: navigator.userAgent,
      p_breadcrumbs: JSON.stringify({
        breadcrumbs,
        metadata: context.metadata  // Include metadata for debugging
      }),
      p_timezone: envContext.timezone,
      p_referrer: document.referrer || undefined,
      p_duration_ms: context.duration,
    }).then(({ error: dbError }) => {
      if (dbError) {
        logger.error('Failed to log non-critical error to database', { dbError });
      }
    });
  } catch (logError) {
    logger.error('Failed to capture non-critical error context', { logError });
  }

  // NO TOAST - This is the key difference from handleError()
  return errorId;
};

/**
 * Type-safe error message extraction utility
 * Use this instead of `error: any` in catch blocks
 */
export function getErrorMessage(error: unknown): string {
  if (error instanceof Error) {
    return error.message;
  }
  if (typeof error === 'string') {
    return error;
  }
  if (error && typeof error === 'object' && 'message' in error) {
    return String(error.message);
  }
  return 'An unexpected error occurred';
}

/**
 * Type guard to check if error has a code property
 */
export function hasErrorCode(error: unknown): error is { code: string } {
  return (
    error !== null &&
    typeof error === 'object' &&
    'code' in error &&
    typeof (error as { code: unknown }).code === 'string'
  );
}

/**
 * Helper to wrap async operations with automatic duration tracking
 * Use this for operations where you want to track how long they took before failing
 */
export async function withErrorTiming<T>(
  fn: () => Promise<T>,
  errorContext: Omit<ErrorContext, 'duration'>
): Promise<T> {
  const start = performance.now();
  try {
    return await fn();
  } catch (error) {
    const duration = Math.round(performance.now() - start);
    handleError(error, { ...errorContext, duration });
    throw error;
  }
}
