import { Injectable, inject } from '@angular/core';
import { LogLevel } from './log-level.enum';
import {
  LoggerApi,
  LoggerContext,
  LoggingConfig,
  Sink,
  SinkFn,
} from './logging.types';

/**
 * The main service for logging functionality in the ISA Frontend application.
 * Implements the LoggerApi interface to provide centralized logging methods.
 *
 * Features:
 * - Multiple log levels (trace, debug, info, warn, error)
 * - Support for multiple logging sinks
 * - Global context for all log messages
 * - Performance optimized with level caching
 * - Error resilience to prevent logging failures from affecting the application
 *
 * @example
 * ```typescript
 * @Injectable()
 * class MyService {
 *   constructor(private loggingService: LoggingService) {
 *     this.loggingService.info('MyService initialized');
 *   }
 *
 *   handleError(error: Error): void {
 *     this.loggingService.error('Operation failed', error, {
 *       operationId: '12345'
 *     });
 *   }
 * }
 * ```
 */
@Injectable({ providedIn: 'root' })
export class LoggingService implements LoggerApi {
  private level: LogLevel = LogLevel.Info; // Default level
  private sinks: Array<
    (
      level: LogLevel,
      message: string,
      context?: LoggerContext,
      error?: Error,
    ) => void
  > = [];
  private globalContext?: LoggerContext;

  /** Cache log level indexes for faster level comparisons */
  private readonly LOG_LEVEL_ORDER: Record<LogLevel, number> = {
    [LogLevel.Trace]: 0,
    [LogLevel.Debug]: 1,
    [LogLevel.Info]: 2,
    [LogLevel.Warn]: 3,
    [LogLevel.Error]: 4,
    [LogLevel.Off]: 5,
  };

  /**
   * Configures the logging service with the provided options.
   *
   * @param config - The logging configuration object
   * @param config.level - The minimum log level to process
   * @param config.sinks - Array of logging destinations (console, remote service, etc.)
   * @param config.context - Optional global context added to all log messages
   *
   * @example
   * ```typescript
   * loggingService.configure({
   *   level: LogLevel.Debug,
   *   sinks: [new ConsoleLogSink()],
   *   context: { environment: 'production' }
   * });
   * ```
   */
  configure(config: LoggingConfig): void {
    this.level = config.level;
    this.globalContext = config.context;
    this.sinks = [];

    // Initialize all sinks
    for (const sink of config.sinks) {
      if (typeof sink === 'function') {
        // Check if it's a constructor function (class) or a sink function
        if (sink.prototype && sink.prototype.log) {
          // It's a constructor function (class), instantiate it
          const instance = new (sink as new () => Sink)();
          this.sinks.push(instance.log.bind(instance));
        } else {
          // It's a SinkFn
          this.sinks.push((sink as SinkFn)());
        }
      } else {
        // It's a Sink object
        this.sinks.push(sink.log.bind(sink));
      }
    }
  }

  /**
   * Logs a trace message with optional context data.
   * Use for fine-grained debugging information.
   *
   * @param message - The message to log
   * @param context - Optional metadata or structured data to include
   */
  trace(message: string, context?: () => LoggerContext): void {
    this.log(LogLevel.Trace, message, context);
  }

  /**
   * Logs a debug message with optional context data.
   * Use for development-time debugging information.
   *
   * @param message - The message to log
   * @param context - Optional metadata or structured data to include
   */
  debug(message: string, context?: () => LoggerContext): void {
    this.log(LogLevel.Debug, message, context);
  }

  /**
   * Logs an informational message with optional context data.
   * Use for general runtime information and application flow.
   *
   * @param message - The message to log
   * @param context - Optional metadata or structured data to include
   */
  info(message: string, context?: () => LoggerContext): void {
    this.log(LogLevel.Info, message, context);
  }

  /**
   * Logs a warning message with optional context data.
   * Use for potentially harmful situations that don't prevent operation.
   *
   * @param message - The message to log
   * @param context - Optional metadata or structured data to include
   */
  warn(message: string, context?: () => LoggerContext): void {
    this.log(LogLevel.Warn, message, context);
  }

  /**
   * Logs an error message with an optional error object and context data.
   * Use for error conditions that affect functionality.
   *
   * @param message - The error message to log
   * @param error - Any error object or value that caused this error condition
   * @param context - Optional metadata or structured data to include
   *
   * @example
   * ```typescript
   * try {
   *   // Some operation
   * } catch (error) {
   *   this.loggingService.error(
   *     'Failed to process data',
   *     error as Error,
   *     { operationId: '12345' }
   *   );
   * }
   * ```
   */
  error(message: string, error?: Error, context?: () => LoggerContext): void {
    this.log(LogLevel.Error, message, context, error);
  }

  /**
   * Internal method to handle logging with level filtering and context merging.
   * @param level The log level.
   * @param message The log message.
   * @param context Optional context data specific to this log message.
   * @param error Optional error object.
   */
  private log(
    level: LogLevel,
    message: string,
    context?: () => LoggerContext,
    error?: Error,
  ): void {
    // Short-circuit if logging is disabled or level is too low (performance optimization)
    if (this.level === LogLevel.Off || !this.shouldLog(level)) {
      return;
    }

    // If there are no sinks configured, don't do any work
    if (!this.sinks.length) {
      return;
    }

    // Merge global context with the provided context
    const mergedContext = this.mergeContext(context?.());

    // Send to all sinks
    for (const sink of this.sinks) {
      try {
        sink(level, message, mergedContext, error);
      } catch (e) {
        // Prevent logger failures from affecting application
        console.error('Error in logging sink:', e);
      }
    }
  }

  /**
   * Determines if a message at the specified level should be logged.
   * Uses cached level values for better performance.
   *
   * @param level The log level to check.
   * @returns True if the message should be logged, false otherwise.
   */
  private shouldLog(level: LogLevel): boolean {
    // Using cached indexes for better performance
    const currentLevelIndex = this.LOG_LEVEL_ORDER[this.level];
    const messageLevelIndex = this.LOG_LEVEL_ORDER[level];

    // Log the message if its level is higher or equal to the current level
    return messageLevelIndex >= currentLevelIndex;
  }

  /**
   * Merges the global context (if configured) with the context provided in the log call.
   * @param context The context provided in the specific log call.
   * @returns The merged context object. If both global and specific contexts exist, they are merged with specific context properties overwriting global ones.
   */
  private mergeContext(context?: LoggerContext): LoggerContext {
    if (!this.globalContext) {
      return context || {};
    }

    if (!context) {
      return this.globalContext;
    }

    // Both contexts exist, merge them
    if (typeof context === 'object' && context !== null) {
      return { ...this.globalContext, ...context };
    }

    // Context is not an object, return both separately
    return {
      globalContext: this.globalContext,
      logContext: context,
    };
  }
}
