/**
 * Centralized Error Handler for Application-wide Error Management
 */

import React from 'react';
import { BaseError, ErrorCode, ErrorContext } from './BaseError';

export interface ErrorLogEntry {
  id: string;
  timestamp: Date;
  level: 'error' | 'warn' | 'info';
  message: string;
  code: ErrorCode;
  context: ErrorContext;
  stack?: string;
  resolved: boolean;
}

export interface ErrorHandlerConfig {
  logToConsole: boolean;
  logToFile: boolean;
  logToExternal: boolean;
  maxLogEntries: number;
  enableStackTrace: boolean;
}

export class ErrorHandler {
  private static instance: ErrorHandler;
  private config: ErrorHandlerConfig;
  private errorLog: ErrorLogEntry[] = [];
  private errorListeners: ((error: BaseError) => void)[] = [];

  private constructor(config: Partial<ErrorHandlerConfig> = {}) {
    this.config = {
      logToConsole: true,
      logToFile: false,
      logToExternal: false,
      maxLogEntries: 1000,
      enableStackTrace: true,
      ...config
    };
  }

  public static getInstance(config?: Partial<ErrorHandlerConfig>): ErrorHandler {
    if (!ErrorHandler.instance) {
      ErrorHandler.instance = new ErrorHandler(config);
    }
    return ErrorHandler.instance;
  }

  public handleError(error: Error | BaseError, context: ErrorContext = {}): void {
    const baseError = this.normalizeError(error, context);
    const logEntry = this.createLogEntry(baseError);

    // Add to internal log
    this.addToLog(logEntry);

    // Log to various outputs
    if (this.config.logToConsole) {
      this.logToConsole(baseError, logEntry);
    }

    if (this.config.logToFile) {
      this.logToFile(logEntry);
    }

    if (this.config.logToExternal) {
      this.logToExternal(logEntry);
    }

    // Notify listeners
    this.notifyListeners(baseError);
  }

  public handleAsyncError(promise: Promise<any>, context: ErrorContext = {}): Promise<any> {
    return promise.catch((error) => {
      this.handleError(error, context);
      throw error; // Re-throw to maintain promise chain behavior
    });
  }

  public wrapFunction<T extends (...args: any[]) => any>(
    fn: T,
    context: ErrorContext = {}
  ): T {
    return ((...args: any[]) => {
      try {
        const result = fn(...args);
        
        // Handle async functions
        if (result instanceof Promise) {
          return this.handleAsyncError(result, context);
        }
        
        return result;
      } catch (error) {
        this.handleError(error as Error, context);
        throw error;
      }
    }) as T;
  }

  public addErrorListener(listener: (error: BaseError) => void): void {
    this.errorListeners.push(listener);
  }

  public removeErrorListener(listener: (error: BaseError) => void): void {
    const index = this.errorListeners.indexOf(listener);
    if (index > -1) {
      this.errorListeners.splice(index, 1);
    }
  }

  public getErrorLog(): ErrorLogEntry[] {
    return [...this.errorLog];
  }

  public getErrorStats(): {
    total: number;
    byCode: Record<ErrorCode, number>;
    byLevel: Record<string, number>;
    resolved: number;
    unresolved: number;
  } {
    const stats = {
      total: this.errorLog.length,
      byCode: {} as Record<ErrorCode, number>,
      byLevel: { error: 0, warn: 0, info: 0 },
      resolved: 0,
      unresolved: 0
    };

    this.errorLog.forEach(entry => {
      // Count by code
      stats.byCode[entry.code] = (stats.byCode[entry.code] || 0) + 1;
      
      // Count by level
      stats.byLevel[entry.level]++;
      
      // Count resolved/unresolved
      if (entry.resolved) {
        stats.resolved++;
      } else {
        stats.unresolved++;
      }
    });

    return stats;
  }

  public markErrorResolved(errorId: string): void {
    const entry = this.errorLog.find(e => e.id === errorId);
    if (entry) {
      entry.resolved = true;
    }
  }

  public clearErrorLog(): void {
    this.errorLog = [];
  }

  private normalizeError(error: Error | BaseError, context: ErrorContext): BaseError {
    if (error instanceof BaseError) {
      // Merge additional context
      return new (error.constructor as any)(
        error.message,
        error.code,
        error.statusCode,
        { ...error.context, ...context },
        error.isOperational
      );
    }

    // Convert regular Error to BaseError
    return new (class extends BaseError {
      constructor() {
        super(
          error.message || 'Unknown error occurred',
          ErrorCode.UNKNOWN,
          500,
          context,
          false // Regular errors are not operational by default
        );
        this.stack = error.stack;
      }
    })();
  }

  private createLogEntry(error: BaseError): ErrorLogEntry {
    return {
      id: this.generateId(),
      timestamp: new Date(),
      level: this.getErrorLevel(error),
      message: error.message,
      code: error.code,
      context: error.context,
      stack: this.config.enableStackTrace ? error.stack : undefined,
      resolved: false
    };
  }

  private getErrorLevel(error: BaseError): 'error' | 'warn' | 'info' {
    if (!error.isOperational) {
      return 'error'; // Programming errors are always errors
    }

    switch (error.statusCode) {
      case 400:
      case 401:
      case 403:
      case 404:
        return 'warn'; // Client errors are warnings
      case 422:
        return 'info'; // Validation errors are info
      default:
        return 'error'; // Server errors are errors
    }
  }

  private addToLog(entry: ErrorLogEntry): void {
    this.errorLog.unshift(entry); // Add to beginning for chronological order
    
    // Trim log if it exceeds max entries
    if (this.errorLog.length > this.config.maxLogEntries) {
      this.errorLog = this.errorLog.slice(0, this.config.maxLogEntries);
    }
  }

  private logToConsole(error: BaseError, entry: ErrorLogEntry): void {
    const logMethod = entry.level === 'error' ? console.error : 
                     entry.level === 'warn' ? console.warn : console.log;

    logMethod(`[${entry.timestamp.toISOString()}] ${entry.level.toUpperCase()}: ${error.message}`);
    
    if (error.context && Object.keys(error.context).length > 0) {
      console.log('Context:', error.context);
    }
    
    if (this.config.enableStackTrace && error.stack) {
      console.log('Stack:', error.stack);
    }
  }

  private logToFile(entry: ErrorLogEntry): void {
    // In a real implementation, this would write to a log file
    // For now, we'll just store it (could be implemented with fs in Node.js)
    console.log('Would log to file:', entry);
  }

  private logToExternal(entry: ErrorLogEntry): void {
    // In a real implementation, this would send to external logging service
    // (e.g., Sentry, LogRocket, DataDog, etc.)
    console.log('Would log to external service:', entry);
  }

  private notifyListeners(error: BaseError): void {
    this.errorListeners.forEach(listener => {
      try {
        listener(error);
      } catch (listenerError) {
        console.error('Error in error listener:', listenerError);
      }
    });
  }

  private generateId(): string {
    return `error_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
  }
}

// Global error handler setup
export function setupGlobalErrorHandling(): void {
  const errorHandler = ErrorHandler.getInstance();

  // Handle unhandled promise rejections
  if (typeof window !== 'undefined') {
    window.addEventListener('unhandledrejection', (event) => {
      errorHandler.handleError(
        new Error(`Unhandled promise rejection: ${event.reason}`),
        { metadata: { type: 'unhandled_promise_rejection' } }
      );
    });

    // Handle global errors
    window.addEventListener('error', (event) => {
      errorHandler.handleError(
        event.error || new Error(event.message),
        { 
          metadata: { 
            type: 'global_error',
            filename: event.filename,
            lineno: event.lineno,
            colno: event.colno
          }
        }
      );
    });
  }

  // Node.js environment
  if (typeof process !== 'undefined') {
    process.on('unhandledRejection', (reason, promise) => {
      errorHandler.handleError(
        new Error(`Unhandled promise rejection: ${reason}`),
        { metadata: { type: 'unhandled_promise_rejection', promise: promise.toString() } }
      );
    });

    process.on('uncaughtException', (error) => {
      errorHandler.handleError(error, { metadata: { type: 'uncaught_exception' } });
      // In production, you might want to exit the process
      // process.exit(1);
    });
  }
}

// Utility function for error boundaries in React
export function createErrorBoundary(
  fallbackComponent: React.ComponentType<{ error: Error; errorInfo: any }>
) {
  return class ErrorBoundary extends React.Component<
    { children: React.ReactNode },
    { hasError: boolean; error?: Error; errorInfo?: any }
  > {
    constructor(props: { children: React.ReactNode }) {
      super(props);
      this.state = { hasError: false };
    }

    static getDerivedStateFromError(error: Error) {
      return { hasError: true, error };
    }

    componentDidCatch(error: Error, errorInfo: any) {
      const errorHandler = ErrorHandler.getInstance();
      errorHandler.handleError(error, {
        metadata: { 
          type: 'react_error_boundary',
          componentStack: errorInfo.componentStack
        }
      });
      
      this.setState({ error, errorInfo });
    }

    render() {
      if (this.state.hasError) {
        const FallbackComponent = fallbackComponent;
        return <FallbackComponent error={this.state.error!} errorInfo={this.state.errorInfo} />;
      }

      return this.props.children;
    }
  };
}