/**
 * Error Handler and Logging Integration
 *
 * Provides centralized error handling and structured logging for the
 * credential manager module. Includes error classification, recovery
 * strategies, and audit trail functionality.
 */

import { EventEmitter } from 'events'

import { CredentialManagerError, ErrorType } from '../../types/credential.js'

// ============================================================================
// Types and Interfaces
// ============================================================================

export interface LogEntry {
  timestamp: Date
  level: LogLevel
  message: string
  component: string
  operation?: string
  accountId?: string
  platform?: string
  error?: Error
  metadata?: Record<string, any>
  correlationId?: string
}

export interface ErrorContext {
  component: string
  operation: string
  accountId?: string
  platform?: string
  attempt?: number
  maxRetries?: number
  metadata?: Record<string, any>
  correlationId?: string
}

export interface RecoveryStrategy {
  canHandle(error: Error, context: ErrorContext): boolean
  handle(error: Error, context: ErrorContext): Promise<RecoveryResult>
}

export interface RecoveryResult {
  recovered: boolean
  shouldRetry: boolean
  retryDelay?: number
  newError?: Error
  metadata?: Record<string, any>
}

export enum LogLevel {
  DEBUG = 'debug',
  INFO = 'info',
  WARN = 'warn',
  ERROR = 'error',
  FATAL = 'fatal',
}

export enum ErrorSeverity {
  LOW = 'low',
  MEDIUM = 'medium',
  HIGH = 'high',
  CRITICAL = 'critical',
}

export interface ErrorHandlerOptions {
  enableLogging?: boolean
  logLevel?: LogLevel
  enableConsoleOutput?: boolean
  enableErrorRecovery?: boolean
  maxRetries?: number
  retryDelay?: number
  enableAuditTrail?: boolean
  enableCorrelationTracking?: boolean
}

// ============================================================================
// Error Handler Implementation
// ============================================================================

export class ErrorHandler extends EventEmitter {
  private options: Required<ErrorHandlerOptions>
  private logEntries: LogEntry[] = []
  private errorCounts = new Map<string, number>()
  private recoveryStrategies: RecoveryStrategy[] = []
  private correlationCounter = 0

  constructor(options: ErrorHandlerOptions = {}) {
    super()

    this.options = {
      enableLogging: options.enableLogging ?? true,
      logLevel: options.logLevel ?? LogLevel.INFO,
      enableConsoleOutput: options.enableConsoleOutput ?? true,
      enableErrorRecovery: options.enableErrorRecovery ?? true,
      maxRetries: options.maxRetries ?? 3,
      retryDelay: options.retryDelay ?? 1000,
      enableAuditTrail: options.enableAuditTrail ?? true,
      enableCorrelationTracking: options.enableCorrelationTracking ?? true,
    }

    this.initializeDefaultRecoveryStrategies()
  }

  /**
   * Handle an error with context and recovery attempts
   */
  async handleError(error: Error, context: ErrorContext): Promise<RecoveryResult> {
    const correlationId = context.correlationId || this.generateCorrelationId()
    const enrichedContext = { ...context, correlationId }

    // Log the error
    this.logError(error, enrichedContext)

    // Track error occurrence
    this.trackError(error, enrichedContext)

    // Attempt recovery if enabled
    if (this.options.enableErrorRecovery) {
      return await this.attemptRecovery(error, enrichedContext)
    }

    return {
      recovered: false,
      shouldRetry: false,
    }
  }

  /**
   * Log messages with different levels
   */
  log(level: LogLevel, message: string, component: string, metadata?: Record<string, any>): void {
    if (!this.shouldLog(level)) {
      return
    }

    const entry: LogEntry = {
      timestamp: new Date(),
      level,
      message,
      component,
      metadata,
      correlationId: metadata?.correlationId,
    }

    this.addLogEntry(entry)
  }

  /**
   * Log debug messages
   */
  debug(message: string, component: string, metadata?: Record<string, any>): void {
    this.log(LogLevel.DEBUG, message, component, metadata)
  }

  /**
   * Log info messages
   */
  info(message: string, component: string, metadata?: Record<string, any>): void {
    this.log(LogLevel.INFO, message, component, metadata)
  }

  /**
   * Log warning messages
   */
  warn(message: string, component: string, metadata?: Record<string, any>): void {
    this.log(LogLevel.WARN, message, component, metadata)
  }

  /**
   * Log error messages
   */
  error(message: string, component: string, error?: Error, metadata?: Record<string, any>): void {
    const entry: LogEntry = {
      timestamp: new Date(),
      level: LogLevel.ERROR,
      message,
      component,
      error,
      metadata,
      correlationId: metadata?.correlationId,
    }

    this.addLogEntry(entry)
  }

  /**
   * Log fatal messages
   */
  fatal(message: string, component: string, error?: Error, metadata?: Record<string, any>): void {
    const entry: LogEntry = {
      timestamp: new Date(),
      level: LogLevel.FATAL,
      message,
      component,
      error,
      metadata,
      correlationId: metadata?.correlationId,
    }

    this.addLogEntry(entry)
  }

  /**
   * Register a custom recovery strategy
   */
  registerRecoveryStrategy(strategy: RecoveryStrategy): void {
    this.recoveryStrategies.push(strategy)
  }

  /**
   * Get error statistics
   */
  getErrorStats(): Record<string, any> {
    const errorsByType = new Map<string, number>()
    const errorsByComponent = new Map<string, number>()
    const recentErrors: LogEntry[] = []

    this.logEntries
      .filter(entry => entry.level === LogLevel.ERROR || entry.level === LogLevel.FATAL)
      .forEach(entry => {
        // Count by error type
        const errorType = entry.error instanceof CredentialManagerError ? entry.error.type : 'unknown'
        errorsByType.set(errorType, (errorsByType.get(errorType) || 0) + 1)

        // Count by component
        errorsByComponent.set(entry.component, (errorsByComponent.get(entry.component) || 0) + 1)

        // Collect recent errors (last 100)
        if (recentErrors.length < 100) {
          recentErrors.push(entry)
        }
      })

    return {
      totalErrors: this.logEntries.filter(e => e.level === LogLevel.ERROR || e.level === LogLevel.FATAL).length,
      errorsByType: Object.fromEntries(errorsByType),
      errorsByComponent: Object.fromEntries(errorsByComponent),
      recentErrors: recentErrors.map(entry => ({
        timestamp: entry.timestamp,
        component: entry.component,
        message: entry.message,
        errorType: entry.error instanceof CredentialManagerError ? entry.error.type : 'unknown',
        correlationId: entry.correlationId,
      })),
    }
  }

  /**
   * Get audit trail
   */
  getAuditTrail(filter?: { component?: string; level?: LogLevel; since?: Date }): LogEntry[] {
    if (!this.options.enableAuditTrail) {
      return []
    }

    let entries = [...this.logEntries]

    if (filter) {
      if (filter.component) {
        entries = entries.filter(e => e.component === filter.component)
      }
      if (filter.level) {
        entries = entries.filter(e => e.level === filter.level)
      }
      if (filter.since) {
        entries = entries.filter(e => e.timestamp >= filter.since)
      }
    }

    return entries.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime())
  }

  /**
   * Clear logs older than specified date
   */
  clearOldLogs(before: Date): number {
    const initialCount = this.logEntries.length
    this.logEntries = this.logEntries.filter(entry => entry.timestamp >= before)
    return initialCount - this.logEntries.length
  }

  /**
   * Generate correlation ID for request tracking
   */
  generateCorrelationId(): string {
    if (!this.options.enableCorrelationTracking) {
      return ''
    }

    this.correlationCounter = (this.correlationCounter + 1) % 1000000
    return `cm-${Date.now()}-${this.correlationCounter.toString().padStart(6, '0')}`
  }

  // ============================================================================
  // Private Methods
  // ============================================================================

  /**
   * Initialize default recovery strategies
   */
  private initializeDefaultRecoveryStrategies(): void {
    // Network timeout recovery
    this.registerRecoveryStrategy({
      canHandle: (error: Error) => error.message.includes('timeout') || error.message.includes('ETIMEDOUT'),
      handle: async (error: Error, context: ErrorContext) => ({
        recovered: false,
        shouldRetry: (context.attempt || 0) < (context.maxRetries || this.options.maxRetries),
        retryDelay: this.options.retryDelay * Math.pow(2, context.attempt || 0), // Exponential backoff
      }),
    })

    // File not found recovery
    this.registerRecoveryStrategy({
      canHandle: (error: Error) => error.message.includes('ENOENT') || error.message.includes('file not found'),
      handle: async (error: Error, context: ErrorContext) => ({
        recovered: false,
        shouldRetry: false,
        newError: new CredentialManagerError(
          `Configuration file not found: ${error.message}`,
          ErrorType.CONFIG_LOAD_ERROR,
        ),
      }),
    })

    // Validation error recovery
    this.registerRecoveryStrategy({
      canHandle: (error: Error) => error instanceof CredentialManagerError && error.type === ErrorType.VALIDATION_ERROR,
      handle: async (error: Error, context: ErrorContext) => ({
        recovered: false,
        shouldRetry: false,
        newError: error, // Don't retry validation errors
      }),
    })

    // Generic retry strategy for temporary failures
    this.registerRecoveryStrategy({
      canHandle: (error: Error) =>
        error instanceof CredentialManagerError &&
        [ErrorType.SIGNATURE_ERROR, ErrorType.CONFIG_LOAD_ERROR].includes(error.type),
      handle: async (error: Error, context: ErrorContext) => {
        const attempt = context.attempt || 0
        const maxRetries = context.maxRetries || this.options.maxRetries

        return {
          recovered: false,
          shouldRetry: attempt < maxRetries,
          retryDelay: this.options.retryDelay * (attempt + 1),
        }
      },
    })
  }

  /**
   * Log an error with context
   */
  private logError(error: Error, context: ErrorContext): void {
    const severity = this.determineErrorSeverity(error)
    const level = severity === ErrorSeverity.CRITICAL ? LogLevel.FATAL : LogLevel.ERROR

    const message = `${context.operation} failed: ${error.message}`

    const metadata = {
      ...context.metadata,
      severity,
      errorType: error instanceof CredentialManagerError ? error.type : 'unknown',
      correlationId: context.correlationId,
    }

    const entry: LogEntry = {
      timestamp: new Date(),
      level,
      message,
      component: context.component,
      operation: context.operation,
      accountId: context.accountId,
      platform: context.platform,
      error,
      metadata,
      correlationId: context.correlationId,
    }

    this.addLogEntry(entry)
  }

  /**
   * Track error occurrences for monitoring
   */
  private trackError(error: Error, context: ErrorContext): void {
    const errorKey = `${context.component}:${error.constructor.name}`
    const count = this.errorCounts.get(errorKey) || 0
    this.errorCounts.set(errorKey, count + 1)

    // Emit error event for external monitoring
    this.emit('error', {
      error,
      context,
      count: count + 1,
    })
  }

  /**
   * Attempt error recovery using registered strategies
   */
  private async attemptRecovery(error: Error, context: ErrorContext): Promise<RecoveryResult> {
    for (const strategy of this.recoveryStrategies) {
      if (strategy.canHandle(error, context)) {
        try {
          const result = await strategy.handle(error, context)

          this.debug(`Recovery strategy applied`, 'ErrorHandler', {
            strategy: strategy.constructor.name,
            recovered: result.recovered,
            shouldRetry: result.shouldRetry,
            correlationId: context.correlationId,
          })

          return result
        } catch (recoveryError) {
          this.warn(`Recovery strategy failed`, 'ErrorHandler', {
            strategy: strategy.constructor.name,
            error: recoveryError.message,
            correlationId: context.correlationId,
          })
        }
      }
    }

    // No strategy could handle the error
    return {
      recovered: false,
      shouldRetry: false,
    }
  }

  /**
   * Determine error severity based on error type and context
   */
  private determineErrorSeverity(error: Error): ErrorSeverity {
    if (error instanceof CredentialManagerError) {
      switch (error.type) {
        case ErrorType.VALIDATION_ERROR:
          return ErrorSeverity.MEDIUM
        case ErrorType.CONFIG_LOAD_ERROR:
          return ErrorSeverity.HIGH
        case ErrorType.SIGNATURE_ERROR:
          return ErrorSeverity.HIGH
        case ErrorType.PLATFORM_DETECTION_ERROR:
          return ErrorSeverity.MEDIUM
        default:
          return ErrorSeverity.MEDIUM
      }
    }

    // Check for critical system errors
    if (error.message.includes('EMFILE') || error.message.includes('ENOMEM')) {
      return ErrorSeverity.CRITICAL
    }

    return ErrorSeverity.LOW
  }

  /**
   * Check if message should be logged based on current level
   */
  private shouldLog(level: LogLevel): boolean {
    if (!this.options.enableLogging) {
      return false
    }

    const levels = [LogLevel.DEBUG, LogLevel.INFO, LogLevel.WARN, LogLevel.ERROR, LogLevel.FATAL]
    const currentLevelIndex = levels.indexOf(this.options.logLevel)
    const messageLevelIndex = levels.indexOf(level)

    return messageLevelIndex >= currentLevelIndex
  }

  /**
   * Add log entry to storage and emit to console if enabled
   */
  private addLogEntry(entry: LogEntry): void {
    if (this.options.enableAuditTrail) {
      this.logEntries.push(entry)

      // Keep only recent entries to prevent memory leaks
      if (this.logEntries.length > 10000) {
        this.logEntries = this.logEntries.slice(-5000)
      }
    }

    if (this.options.enableConsoleOutput) {
      this.outputToConsole(entry)
    }

    // Emit log event for external listeners
    this.emit('log', entry)
  }

  /**
   * Output log entry to console with appropriate formatting
   */
  private outputToConsole(entry: LogEntry): void {
    const timestamp = entry.timestamp.toISOString()
    const level = entry.level.toUpperCase().padEnd(5)
    const component = entry.component.padEnd(20)

    let message = `[${timestamp}] ${level} ${component} ${entry.message}`

    if (entry.correlationId) {
      message += ` [${entry.correlationId}]`
    }

    if (entry.accountId) {
      message += ` (account: ${entry.accountId})`
    }

    if (entry.platform) {
      message += ` (platform: ${entry.platform})`
    }

    switch (entry.level) {
      case LogLevel.DEBUG:
        console.debug(message)
        break
      case LogLevel.INFO:
        console.info(message)
        break
      case LogLevel.WARN:
        console.warn(message)
        break
      case LogLevel.ERROR:
        console.error(message)
        if (entry.error) {
          console.error(entry.error.stack)
        }
        break
      case LogLevel.FATAL:
        console.error(message)
        if (entry.error) {
          console.error(entry.error.stack)
        }
        break
    }

    if (entry.metadata && Object.keys(entry.metadata).length > 0) {
      console.debug('Metadata:', entry.metadata)
    }
  }
}

// ============================================================================
// Utility Functions and Exports
// ============================================================================

/**
 * Create error handler with default options
 */
export function createErrorHandler(options?: ErrorHandlerOptions): ErrorHandler {
  return new ErrorHandler(options)
}

/**
 * Create error handler for production use
 */
export function createProductionErrorHandler(): ErrorHandler {
  return new ErrorHandler({
    enableLogging: true,
    logLevel: LogLevel.INFO,
    enableConsoleOutput: false,
    enableErrorRecovery: true,
    maxRetries: 3,
    retryDelay: 1000,
    enableAuditTrail: true,
    enableCorrelationTracking: true,
  })
}

/**
 * Create error handler for development use
 */
export function createDevelopmentErrorHandler(): ErrorHandler {
  return new ErrorHandler({
    enableLogging: true,
    logLevel: LogLevel.DEBUG,
    enableConsoleOutput: true,
    enableErrorRecovery: true,
    maxRetries: 5,
    retryDelay: 500,
    enableAuditTrail: true,
    enableCorrelationTracking: true,
  })
}

/**
 * Global error handler instance for convenience
 */
let globalErrorHandler: ErrorHandler | null = null

/**
 * Get or create global error handler instance
 */
export function getGlobalErrorHandler(): ErrorHandler {
  if (!globalErrorHandler) {
    globalErrorHandler = createErrorHandler()
  }
  return globalErrorHandler
}

/**
 * Set global error handler instance
 */
export function setGlobalErrorHandler(handler: ErrorHandler): void {
  globalErrorHandler = handler
}

// ============================================================================
// Export
// ============================================================================

export default ErrorHandler
