/**
 * Enhanced Global Error Handler for Maximum Developer Transparency
 * Captures all uncaught errors, promise rejections, and React errors
 */

import { ApiError } from '../api/base/ApiError';
import { getGlobalErrorReporter } from './EnhancedErrorReporter';
import { ConsoleLogger } from './logging/ConsoleLogger';
import { getGlobalReplaySystem } from './ErrorReplay';

export interface GlobalErrorHandlerOptions {
  /**
   * Enable detailed error overlays in development
   */
  showDevOverlay?: boolean;
  
  /**
   * Log all errors to console with maximum detail
   */
  verboseLogging?: boolean;
  
  /**
   * Capture stack traces with enhanced context
   */
  captureEnhancedStacks?: boolean;
  
  /**
   * Report to external services (Sentry, etc.)
   */
  reportToExternal?: boolean;
  
  /**
   * Custom error filter to ignore certain errors
   */
  errorFilter?: (error: Error) => boolean;
}

export class GlobalErrorHandler {
  private options: GlobalErrorHandlerOptions;
  private logger: ConsoleLogger;
  private errorReporter: ReturnType<typeof getGlobalErrorReporter>;
  private errorHistory: Array<{
    error: Error | ApiError;
    timestamp: string;
    context?: unknown;
    stackEnhanced?: string;
  }> = [];
  private readonly MAX_HISTORY = 100;
  private isInitialized = false;

  constructor(options: GlobalErrorHandlerOptions = {}) {
    this.options = {
      showDevOverlay: options.showDevOverlay ?? process.env.NODE_ENV === 'development',
      verboseLogging: options.verboseLogging ?? process.env.NODE_ENV === 'development',
      captureEnhancedStacks: options.captureEnhancedStacks ?? process.env.NODE_ENV === 'development',
      reportToExternal: options.reportToExternal ?? process.env.NODE_ENV === 'production',
      errorFilter: options.errorFilter,
    };

    this.logger = new ConsoleLogger();
    this.errorReporter = getGlobalErrorReporter();
  }

  /**
   * Initialize global error handlers
   */
  initialize(): void {
    if (this.isInitialized) {
      console.warn('[GlobalErrorHandler] Already initialized');
      return;
    }

    // Only initialize in browser environment
    if (typeof window === 'undefined') {
      if (this.options.verboseLogging) {
        this.logger.info('Global error handler skipped (server-side)');
      }
      return;
    }

    // Handle uncaught JavaScript errors
    window.addEventListener('error', this.handleWindowError);
    
    // Handle unhandled promise rejections
    window.addEventListener('unhandledrejection', this.handleUnhandledRejection);
    
    // Override console.error to capture framework errors
    this.overrideConsoleError();
    
    // React error boundary fallback
    this.setupReactErrorHandling();

    this.isInitialized = true;

    if (this.options.verboseLogging) {
      this.logger.info('Global error handler initialized', {
        devOverlay: this.options.showDevOverlay,
        verboseLogging: this.options.verboseLogging,
        enhancedStacks: this.options.captureEnhancedStacks,
      });
    }
  }

  /**
   * Handle window errors (uncaught JavaScript errors)
   */
  private handleWindowError = (event: ErrorEvent): void => {
    const error = event.error;
    
    // Apply error filter if provided
    if (this.options.errorFilter && !this.options.errorFilter(error)) {
      return;
    }

    // Check if this is a network/CORS error (expected in some cases)
    if (error instanceof TypeError && error.message.includes('fetch')) {
      this.logger.warn('Network error detected', {
        type: 'network_error',
        message: error.message,
        url: event.filename,
        line: event.lineno,
        col: event.colno,
      });
      return; // Don't prevent default for network errors
    }

    const enhancedContext = this.captureEnhancedContext('window_error', {
      filename: event.filename,
      lineno: event.lineno,
      colno: event.colno,
      message: event.message,
    });

    // Log with appropriate detail
    this.logErrorWithMaximumDetail(error, enhancedContext);

    // Store in history
    this.addToHistory(error, enhancedContext);

    // Report to external if enabled (but not for network errors)
    if (this.options.reportToExternal) {
      this.reportToExternal(error, enhancedContext);
    }

    // Auto-capture for replay in development
    if (this.options.showDevOverlay) {
      const replaySystem = getGlobalReplaySystem();
      replaySystem.autoCapture(error, enhancedContext);
    }

    // Prevent default error logging in dev to avoid duplicates
    if (this.options.showDevOverlay) {
      event.preventDefault();
    }
  };

  /**
   * Handle unhandled promise rejections
   */
  private handleUnhandledRejection = (event: PromiseRejectionEvent): void => {
    const error = event.reason;
    
    // Apply error filter if provided
    if (this.options.errorFilter && !this.options.errorFilter(error)) {
      return;
    }

    // Check if this is a network error (expected)
    if (error instanceof TypeError && error.message.includes('fetch')) {
      this.logger.warn('Unhandled promise rejection - network error', {
        type: 'network_error',
        message: error.message,
      });
      return;
    }

    const enhancedContext = this.captureEnhancedContext('unhandled_promise', {
      promise: event.promise,
      reason: typeof error === 'string' ? error : error?.message || 'Unknown promise rejection',
    });

    // Log with appropriate detail
    this.logErrorWithMaximumDetail(error, enhancedContext);

    // Store in history
    this.addToHistory(error, enhancedContext);

    // Report to external if enabled (but not for network errors)
    if (this.options.reportToExternal) {
      this.reportToExternal(error, enhancedContext);
    }

    // Prevent default logging
    if (this.options.showDevOverlay) {
      event.preventDefault();
    }
  };

  /**
   * Override console.error to capture framework errors
   */
  private overrideConsoleError(): void {
    const originalError = console.error;
    
    console.error = (...args: unknown[]): void => {
      // Call original first
      originalError.apply(console, args);

      // Try to extract error from arguments
      const error = args.find(arg => arg instanceof Error) || 
                   args.find(arg => typeof arg === 'object' && arg !== null && 'message' in arg) ||
                   new Error(args.map(a => String(a)).join(' '));

      if (error instanceof Error) {
        const enhancedContext = this.captureEnhancedContext('console_error', {
          originalArgs: args,
        });

        // Store in history
        this.addToHistory(error, enhancedContext);

        // No overlay - just enhanced console logging
      }
    };
  }

  /**
   * Setup React-specific error handling
   */
  private setupReactErrorHandling(): void {
    // This will be used by React Error Boundaries
    // We'll provide a global registry for React errors
    (window as { __GRIDPILOT_REACT_ERRORS__?: unknown[] }).__GRIDPILOT_REACT_ERRORS__ = [];
  }

  /**
   * Capture enhanced context with stack trace and environment info
   */
  private captureEnhancedContext(type: string, additionalContext: Record<string, unknown> = {}): Record<string, unknown> {
    const stack = new Error().stack;
    
    return {
      type,
      timestamp: new Date().toISOString(),
      userAgent: navigator.userAgent,
      url: window.location.href,
      language: navigator.language,
      platform: navigator.platform,
      viewport: {
        width: window.innerWidth,
        height: window.innerHeight,
      },
      screen: {
        width: window.screen.width,
        height: window.screen.height,
      },
      memory: (performance as unknown as { memory?: { usedJSHeapSize: number; totalJSHeapSize: number } }).memory ? {
        usedJSHeapSize: (performance as unknown as { memory: { usedJSHeapSize: number; totalJSHeapSize: number } }).memory.usedJSHeapSize,
        totalJSHeapSize: (performance as unknown as { memory: { usedJSHeapSize: number; totalJSHeapSize: number } }).memory.totalJSHeapSize,
      } : null,
      connection: (navigator as unknown as { connection?: { effectiveType: string; downlink: number; rtt: number } }).connection ? {
        effectiveType: (navigator as unknown as { connection: { effectiveType: string; downlink: number; rtt: number } }).connection.effectiveType,
        downlink: (navigator as unknown as { connection: { effectiveType: string; downlink: number; rtt: number } }).connection.downlink,
        rtt: (navigator as unknown as { connection: { effectiveType: string; downlink: number; rtt: number } }).connection.rtt,
      } : null,
      ...additionalContext,
      enhancedStack: this.options.captureEnhancedStacks ? this.enhanceStackTrace(stack) : undefined,
    };
  }

  /**
   * Enhance stack trace with additional context
   */
  private enhanceStackTrace(stack?: string): string | undefined {
    if (!stack) return undefined;

    // Add source map information if available
    const lines = stack.split('\n').slice(1); // Remove first line (error message)
    
    const enhanced = lines.map(line => {
      // Try to extract file and line info
      const match = line.match(/at (.+) \((.+):(\d+):(\d+)\)/) || 
                   line.match(/at (.+):(\d+):(\d+)/);
      
      if (match) {
        const func = match[1] || 'anonymous';
        const file = match[2] || match[1];
        const lineNum = match[3] || match[2];
        const colNum = match[4] || match[3];
        
        // Add source map comment if in development
        if (process.env.NODE_ENV === 'development' && file && file.includes('.js')) {
          return `at ${func} (${file}:${lineNum}:${colNum}) [Source Map: ${file}.map]`;
        }
        
        return `at ${func} (${file}:${lineNum}:${colNum})`;
      }
      
      return line.trim();
    });

    return enhanced.join('\n');
  }

  /**
   * Log error with appropriate detail
   */
  private logErrorWithMaximumDetail(error: Error | ApiError, context: Record<string, unknown>): void {
    if (!this.options.verboseLogging) return;

    const isApiError = error instanceof ApiError;
    const isWarning = isApiError && error.getSeverity() === 'warn';

    if (isWarning) {
      // Simplified warning output
      this.logger.warn(error.message, context);
      return;
    }

    // Full error details for actual errors
    this.logger.error(error.message, error, context);

    // In development, show additional details
    if (process.env.NODE_ENV === 'development') {
      console.groupCollapsed(`%c[ERROR DETAIL] ${error.name || 'Error'}`, 'color: #ff4444; font-weight: bold; font-size: 14px;');
      
      console.log('%cError Details:', 'color: #666; font-weight: bold;', {
        name: error.name,
        message: error.message,
        type: isApiError ? error.type : 'N/A',
        severity: isApiError ? error.getSeverity() : 'error',
        retryable: isApiError ? error.isRetryable() : 'N/A',
        connectivity: isApiError ? error.isConnectivityIssue() : 'N/A',
      });

      console.log('%cContext:', 'color: #666; font-weight: bold;', context);

      if (isApiError && error.context?.developerHint) {
        console.log('%c💡 Developer Hint:', 'color: #00aaff; font-weight: bold;', error.context.developerHint);
      }

      console.groupEnd();
    }
  }


  /**
   * Report error to external services
   */
  private reportToExternal(error: Error | ApiError, context: Record<string, unknown>): void {
    // This is a placeholder for external error reporting (Sentry, LogRocket, etc.)
    // In a real implementation, you would send structured data to your error tracking service
    
    if (this.options.verboseLogging) {
      console.log('[EXTERNAL REPORT] Would send to error tracking service:', {
        error: {
          name: error.name,
          message: error.message,
          stack: error.stack,
          type: error instanceof ApiError ? error.type : undefined,
        },
        context,
        timestamp: new Date().toISOString(),
      });
    }
  }

  /**
   * Add error to history
   */
  private addToHistory(error: Error | ApiError, context: Record<string, unknown>): void {
    const entry = {
      error,
      timestamp: new Date().toISOString(),
      context,
      stackEnhanced: context.enhancedStack as string | undefined,
    };

    this.errorHistory.push(entry);

    // Keep only last N errors
    if (this.errorHistory.length > this.MAX_HISTORY) {
      this.errorHistory = this.errorHistory.slice(-this.MAX_HISTORY);
    }
  }

  /**
   * Get error history
   */
  getErrorHistory(): Array<{ error: Error | ApiError; timestamp: string; context?: unknown; stackEnhanced?: string }> {
    return [...this.errorHistory];
  }

  /**
   * Clear error history
   */
  clearHistory(): void {
    this.errorHistory = [];
    if (this.options.verboseLogging) {
      this.logger.info('Error history cleared');
    }
  }

  /**
   * Get statistics about errors
   */
  getStats(): {
    total: number;
    byType: Record<string, number>;
    recent: Array<{ timestamp: string; message: string; type: string }>;
  } {
    const stats = {
      total: this.errorHistory.length,
      byType: {} as Record<string, number>,
      recent: this.errorHistory.slice(-10).map(entry => ({
        timestamp: entry.timestamp,
        message: entry.error.message,
        type: entry.error instanceof ApiError ? entry.error.type : entry.error.name || 'Error',
      })),
    };

    this.errorHistory.forEach(entry => {
      const type = entry.error instanceof ApiError ? entry.error.type : entry.error.name || 'Error';
      stats.byType[type] = (stats.byType[type] || 0) + 1;
    });

    return stats;
  }

  /**
   * Manually report an error
   */
  report(error: Error | ApiError, additionalContext: Record<string, unknown> = {}): void {
    const context = this.captureEnhancedContext('manual_report', additionalContext);
    
    // Check if this is a network error (don't report to external services)
    const isNetworkError = error.message.includes('fetch') ||
                          error.message.includes('Failed to fetch') ||
                          error.message.includes('NetworkError');

    if (isNetworkError) {
      this.logger.warn(`Manual error report: ${error.message}`, context);
    } else {
      this.logErrorWithMaximumDetail(error, context);
    }

    this.addToHistory(error, context);

    // Auto-capture for replay in development
    if (this.options.showDevOverlay) {
      const replaySystem = getGlobalReplaySystem();
      replaySystem.autoCapture(error, context);
    }

    // Only report non-network errors to external services
    if (this.options.reportToExternal && !isNetworkError) {
      this.reportToExternal(error, context);
    }
  }

  /**
   * Destroy the error handler and remove all listeners
   */
  destroy(): void {
    if (typeof window !== 'undefined') {
      window.removeEventListener('error', this.handleWindowError);
      window.removeEventListener('unhandledrejection', this.handleUnhandledRejection);
      
      // Restore original console.error
      if ((console as unknown as { _originalError?: typeof console.error })._originalError) {
        console.error = (console as unknown as { _originalError: typeof console.error })._originalError;
      }
    }

    this.isInitialized = false;

    if (this.options.verboseLogging) {
      this.logger.info('Global error handler destroyed');
    }
  }
}

/**
 * Global instance accessor
 */
let globalErrorHandlerInstance: GlobalErrorHandler | null = null;

export function getGlobalErrorHandler(): GlobalErrorHandler {
  if (!globalErrorHandlerInstance) {
    globalErrorHandlerInstance = new GlobalErrorHandler();
  }
  return globalErrorHandlerInstance;
}

/**
 * Initialize global error handling
 */
export function initializeGlobalErrorHandling(options?: GlobalErrorHandlerOptions): GlobalErrorHandler {
  const handler = new GlobalErrorHandler(options);
  handler.initialize();
  globalErrorHandlerInstance = handler;
  return handler;
}

/**
 * React hook for manual error reporting
 */
export function useGlobalErrorHandler() {
  const handler = getGlobalErrorHandler();
  
  return {
    report: (error: Error | ApiError, context?: Record<string, unknown>) => handler.report(error, context),
    getHistory: () => handler.getErrorHistory(),
    getStats: () => handler.getStats(),
    clearHistory: () => handler.clearHistory(),
  };
}