/**
 * Structured Logging Utility for Sync Operations
 * Provides consistent, categorized logging with context
 */

export enum LogLevel {
  DEBUG = 'DEBUG',
  INFO = 'INFO',
  WARN = 'WARN',
  ERROR = 'ERROR',
}

export enum ErrorCategory {
  NETWORK_ERROR = 'NETWORK_ERROR',
  AUTH_ERROR = 'AUTH_ERROR',
  RATE_LIMIT_ERROR = 'RATE_LIMIT_ERROR',
  DATABASE_CONSTRAINT_ERROR = 'DATABASE_CONSTRAINT_ERROR',
  DATABASE_ERROR = 'DATABASE_ERROR',
  API_ERROR = 'API_ERROR',
  MAPPING_ERROR = 'MAPPING_ERROR',
  VALIDATION_ERROR = 'VALIDATION_ERROR',
  UNKNOWN = 'UNKNOWN',
}

export enum SyncPhase {
  INITIALIZING = 'initializing',
  FETCHING = 'fetching',
  MAPPING = 'mapping',
  VALIDATING = 'validating',
  UPSERTING = 'upserting',
  DELETING = 'deleting',
  COMPLETING = 'completing',
}

export interface LogContext {
  syncId?: string;
  entityType?: string;
  phase?: SyncPhase;
  recordCount?: number;
  duration?: number;
  errorCategory?: ErrorCategory;
  [key: string]: any;
}

export interface LogEntry {
  timestamp: string;
  level: LogLevel;
  message: string;
  context?: LogContext;
  error?: {
    message: string;
    stack?: string;
    category?: ErrorCategory;
  };
}

/**
 * Categorize error based on error message
 */
export function categorizeError(error: Error | string): ErrorCategory {
  const errorMessage = typeof error === 'string' ? error : error.message;
  
  if (errorMessage.includes('ECONNREFUSED') || errorMessage.includes('ETIMEDOUT') || errorMessage.includes('ENOTFOUND')) {
    return ErrorCategory.NETWORK_ERROR;
  } else if (errorMessage.includes('401') || errorMessage.includes('403') || errorMessage.includes('Unauthorized')) {
    return ErrorCategory.AUTH_ERROR;
  } else if (errorMessage.includes('429') || errorMessage.includes('rate limit')) {
    return ErrorCategory.RATE_LIMIT_ERROR;
  } else if (errorMessage.includes('constraint') || errorMessage.includes('duplicate') || errorMessage.includes('foreign key')) {
    return ErrorCategory.DATABASE_CONSTRAINT_ERROR;
  } else if (errorMessage.includes('query') || errorMessage.includes('SQL') || errorMessage.includes('database')) {
    return ErrorCategory.DATABASE_ERROR;
  } else if (errorMessage.includes('API') || errorMessage.includes('endpoint')) {
    return ErrorCategory.API_ERROR;
  } else if (errorMessage.includes('mapping') || errorMessage.includes('transform')) {
    return ErrorCategory.MAPPING_ERROR;
  } else if (errorMessage.includes('validation') || errorMessage.includes('invalid')) {
    return ErrorCategory.VALIDATION_ERROR;
  }
  
  return ErrorCategory.UNKNOWN;
}

/**
 * Structured Logger Class
 */
export class SyncLogger {
  private context: LogContext;

  constructor(context: LogContext = {}) {
    this.context = context;
  }

  /**
   * Create a child logger with additional context
   */
  child(additionalContext: LogContext): SyncLogger {
    return new SyncLogger({ ...this.context, ...additionalContext });
  }

  /**
   * Update logger context
   */
  updateContext(updates: LogContext): void {
    this.context = { ...this.context, ...updates };
  }

  /**
   * Format log entry
   */
  private formatLog(level: LogLevel, message: string, context?: LogContext, error?: Error): LogEntry {
    const entry: LogEntry = {
      timestamp: new Date().toISOString(),
      level,
      message,
      context: { ...this.context, ...context },
    };

    if (error) {
      entry.error = {
        message: error.message,
        stack: error.stack,
        category: categorizeError(error),
      };
    }

    return entry;
  }

  /**
   * Output log entry
   */
  private output(entry: LogEntry): void {
    const prefix = `[${entry.timestamp}] [${entry.level}]`;
    const contextStr = entry.context ? ` [${this.formatContext(entry.context)}]` : '';
    const fullMessage = `${prefix}${contextStr} ${entry.message}`;

    switch (entry.level) {
      case LogLevel.DEBUG:
        console.log(fullMessage);
        break;
      case LogLevel.INFO:
        console.log(fullMessage);
        break;
      case LogLevel.WARN:
        console.warn(fullMessage);
        if (entry.error) {
          console.warn(`  Error: ${entry.error.message}`);
          console.warn(`  Category: ${entry.error.category}`);
        }
        break;
      case LogLevel.ERROR:
        console.error(fullMessage);
        if (entry.error) {
          console.error(`  Error: ${entry.error.message}`);
          console.error(`  Category: ${entry.error.category}`);
          if (entry.error.stack) {
            console.error(`  Stack: ${entry.error.stack}`);
          }
        }
        break;
    }
  }

  /**
   * Format context for display
   */
  private formatContext(context: LogContext): string {
    const parts: string[] = [];
    
    if (context.syncId) parts.push(`syncId=${context.syncId}`);
    if (context.entityType) parts.push(`entity=${context.entityType}`);
    if (context.phase) parts.push(`phase=${context.phase}`);
    if (context.recordCount !== undefined) parts.push(`records=${context.recordCount}`);
    if (context.duration !== undefined) parts.push(`duration=${context.duration}ms`);
    if (context.errorCategory) parts.push(`errorCategory=${context.errorCategory}`);
    
    return parts.join(', ');
  }

  /**
   * Debug level logging
   */
  debug(message: string, context?: LogContext): void {
    const entry = this.formatLog(LogLevel.DEBUG, message, context);
    this.output(entry);
  }

  /**
   * Info level logging
   */
  info(message: string, context?: LogContext): void {
    const entry = this.formatLog(LogLevel.INFO, message, context);
    this.output(entry);
  }

  /**
   * Warning level logging
   */
  warn(message: string, context?: LogContext, error?: Error): void {
    const entry = this.formatLog(LogLevel.WARN, message, context, error);
    this.output(entry);
  }

  /**
   * Error level logging
   */
  error(message: string, context?: LogContext, error?: Error): void {
    const entry = this.formatLog(LogLevel.ERROR, message, context, error);
    this.output(entry);
  }

  /**
   * Log phase transition
   */
  phase(phase: SyncPhase, message?: string): void {
    this.updateContext({ phase });
    this.info(message || `Entering phase: ${phase}`, { phase });
  }

  /**
   * Log operation start
   */
  start(operation: string, context?: LogContext): number {
    this.info(`Starting ${operation}`, context);
    return Date.now();
  }

  /**
   * Log operation completion
   */
  complete(operation: string, startTime: number, context?: LogContext): void {
    const duration = Date.now() - startTime;
    this.info(`Completed ${operation}`, { ...context, duration });
  }

  /**
   * Log operation failure
   */
  fail(operation: string, startTime: number, error: Error, context?: LogContext): void {
    const duration = Date.now() - startTime;
    const errorCategory = categorizeError(error);
    this.error(`Failed ${operation}`, { ...context, duration, errorCategory }, error);
  }
}

/**
 * Create a new sync logger instance
 */
export function createSyncLogger(context?: LogContext): SyncLogger {
  return new SyncLogger(context);
}

/**
 * Default logger instance
 */
export const defaultLogger = new SyncLogger();
