/**
 * Copyright 2024 Phenix Real Time Solutions, Inc. Confidential and Proprietary. All Rights Reserved.
 */
/* eslint-disable @typescript-eslint/no-explicit-any */
import {IAppender} from './IAppender';
import Appenders from './Appenders';
import LoggingThreshold from './LoggingThreshold';

export enum LoggingLevel {
  All = -1,
  Trace = 10,
  Debug = 20,
  Info = 30,
  Warn = 40,
  Error = 50,
  Fatal = 60,
  Off = 100
}

export type LoggingLevelType = 'Off' | 'Trace' | 'Debug' | 'Info' | 'Warn' | 'Error' | 'Fatal' | 'All';

export default class Logger {
  private readonly _category: string;
  private readonly _appenders: Appenders;
  private readonly _threshold: LoggingThreshold;

  get category(): string {
    return this._category;
  }

  get appenders(): Appenders {
    return this._appenders;
  }

  get threshold(): LoggingThreshold {
    return this._threshold;
  }

  trace(...args: any): void {
    if (!this._threshold.value || this._threshold.value > LoggingLevel.Trace) {
      return;
    }

    this.log(LoggingLevel.Trace, args);
  }

  debug(...args: any): void {
    if (!this._threshold.value || this._threshold.value > LoggingLevel.Debug) {
      return;
    }

    this.log(LoggingLevel.Debug, args);
  }

  info(...args: any): void {
    if (!this._threshold.value || this._threshold.value > LoggingLevel.Info) {
      return;
    }

    this.log(LoggingLevel.Info, args);
  }

  warn(...args: any): void {
    if (!this._threshold.value || this._threshold.value > LoggingLevel.Warn) {
      return;
    }

    this.log(LoggingLevel.Warn, args);
  }

  error(...args: any): void {
    if (!this._threshold.value || this._threshold.value > LoggingLevel.Error) {
      return;
    }

    this.log(LoggingLevel.Error, args);
  }

  fatal(...args: any): void {
    if (!this._threshold.value || this._threshold.value > LoggingLevel.Fatal) {
      return;
    }

    this.log(LoggingLevel.Fatal, args);
  }

  private log(level: number, args: any): void {
    const date = new Date();
    const message = this.replacePlaceholders(args);

    this._appenders.value.forEach((appender: IAppender) => {
      appender.log(level, message, this.category, date);
    });
  }

  private replacePlaceholders(args: any): string {
    let replacePlaceholdersString = args[0];
    let index = 0;

    while (replacePlaceholdersString.indexOf && args.length > 1 && index >= 0) {
      index = replacePlaceholdersString.indexOf('%', index);

      if (index > 0) {
        const type = replacePlaceholdersString.substring(index + 1, index + 2);

        switch (type) {
          case '%':
            // Escaped '%%' turns into '%'
            replacePlaceholdersString = replacePlaceholdersString.substring(0, index) + replacePlaceholdersString.substring(index + 1);
            index++;

            break;
          case 's':
          case 'd':
            // Replace '%d' or '%s' with the argument
            args[0] = replacePlaceholdersString = this.replaceArgument(this.toString(args[1]), replacePlaceholdersString, index);
            args.splice(1, 1);

            break;
          case 'j':
            // Replace %j' with the argument
            args[0] = replacePlaceholdersString = this.replaceArgument(this.stringify(args[1]), replacePlaceholdersString, index);

            args.splice(1, 1);

            break;
          default:
            return args.toString();
        }
      }
    }

    if (args.length > 1) {
      args = args.reduce((accumulator: any, currentValue: any, index: number, array: any[]) => {
        if (index + 1 === array.length && currentValue instanceof Error) {
          return accumulator + '\n' + this.toString(currentValue.stack);
        }

        return accumulator + `[${this.toString(currentValue)}]`;
      });
    }

    return args.toString();
  }

  private stringify(arg: any): string {
    try {
      return JSON.stringify(arg instanceof Error ? this.toString(arg) : arg, null, 2);
      // eslint-disable-next-line @typescript-eslint/no-unused-vars
    } catch (e) {
      return '[object invalid JSON.stringify]';
    }
  }

  private replaceArgument(argument: any, replacePlaceholdersString: string, index: number): string {
    return replacePlaceholdersString.substring(0, index) + this.toString(argument) + replacePlaceholdersString.substring(index + 2);
  }

  private toString(data: any): string {
    if (typeof data === 'string') {
      return data;
    }

    if (typeof data === 'boolean') {
      return data ? 'true' : 'false';
    }

    if (typeof data === 'number') {
      return data.toString();
    }

    let toStringStr = '';

    if (data) {
      if (typeof data === 'function') {
        toStringStr = data.toString();
      } else if (data instanceof Object) {
        try {
          toStringStr = data.toString();

          // eslint-disable-next-line @typescript-eslint/no-unused-vars
        } catch (e) {
          toStringStr = '[object invalid toString()]';
        }
      }
    }

    return toStringStr;
  }

  constructor(category: string, appenders: Appenders, threshold: LoggingThreshold) {
    this._category = category;
    this._appenders = appenders;
    this._threshold = threshold;
  }
}
