import * as plugins from './smartlog.plugins.js';

import { LogRouter } from './smartlog.classes.logrouter.js';
import { LogGroup } from './smartlog.classes.loggroup.js';

export interface ISmartlogContructorOptions {
  logContext: plugins.smartlogInterfaces.ILogContext;
  minimumLogLevel?: plugins.smartlogInterfaces.TLogLevel;
}

export class Smartlog implements plugins.smartlogInterfaces.ILogDestination {
  // STATIC
  public static createForCommitinfo(
    commitinfo: plugins.smartlogInterfaces.ILogContext['commitinfo'],
  ) {
    return new Smartlog({
      logContext: {
        commitinfo,
      },
    });
  }

  // INSTANCE
  public logContext: plugins.smartlogInterfaces.ILogContext;
  public minimumLogLevel: plugins.smartlogInterfaces.TLogLevel;

  public uniInstanceId: string = plugins.isounique.uni();

  private consoleEnabled: boolean;

  private logRouter = new LogRouter();

  public addLogDestination(logDestinationArg: plugins.smartlogInterfaces.ILogDestination) {
    this.logRouter.addLogDestination(logDestinationArg);
  }

  constructor(optionsArg: ISmartlogContructorOptions) {
    this.logContext = optionsArg.logContext;
    this.minimumLogLevel = optionsArg.minimumLogLevel || 'silly';
  }

  // ============
  // Logger Setup
  // ============

  /**
   * enables console logging
   */
  public enableConsole(optionsArg?: { captureAll: boolean }) {
    if (globalThis.process && optionsArg && optionsArg.captureAll) {
      const process = globalThis.process;
      const originalStdoutWrite = process.stdout.write.bind(process.stdout);
      const originalStderrWrite = process.stderr.write.bind(process.stderr);

      process.stdout.write = (...args: any) => {
        const logString: string = args[0];
        if (!logString || typeof logString !== 'string') {
          // continue as planned
          return originalStdoutWrite(...args);
        }

        if (!logString.startsWith('LOG')) {
          if (logString.includes('Error:')) {
            this.log('error', logString);
          } else {
            this.log('info', logString);
          }
          return true;
        }

        return originalStdoutWrite(...args);
      };

      process.stderr.write = (...args: any) => {
        const logString: string = args[0];
        if (!logString || typeof logString !== 'string' || !logString.startsWith('LOG')) {
          this.log('error', logString);
          return true;
        }
        return originalStderrWrite(...args);
      };
    }
    this.consoleEnabled = true;
  }

  // =============
  // log functions
  // =============
  /**
   * main log method
   * @param logLevelArg - the log level
   * @param logMessageArg - the log message
   * @param logDataArg - any additional log data
   * @param correlationArg - info about corrleations
   */
  public async log(
    logLevelArg: plugins.smartlogInterfaces.TLogLevel,
    logMessageArg: string,
    logDataArg?: any,
    correlationArg?: plugins.smartlogInterfaces.ILogCorrelation,
  ) {
    correlationArg = {
      ...{
        id: plugins.isounique.uni(),
        type: 'none',
        instance: this.uniInstanceId,
      },
      ...correlationArg,
    };

    if (this.consoleEnabled) {
      this.safeConsoleLog(`${logLevelArg}: ${logMessageArg}`);
    }

    const logPackage: plugins.smartlogInterfaces.ILogPackage = {
      timestamp: Date.now(),
      type: 'log',
      context: this.logContext,
      level: logLevelArg,
      correlation: correlationArg,
      message: logMessageArg,
    };
    if (logDataArg) {
      logPackage.data = logDataArg;
    }
    await this.logRouter.routeLog(logPackage);
  }

  public increment(
    logLevelArg: plugins.smartlogInterfaces.TLogLevel,
    logMessageArg: string,
    logDataArg?: any,
    correlationArg: plugins.smartlogInterfaces.ILogCorrelation = {
      id: plugins.isounique.uni(),
      type: 'none',
    },
  ) {
    if (this.consoleEnabled) {
      this.safeConsoleLog(`INCREMENT: ${logLevelArg}: ${logMessageArg}`);
    }
    this.logRouter.routeLog({
      timestamp: Date.now(),
      type: 'increment',
      context: this.logContext,
      level: logLevelArg,
      message: logMessageArg,
      correlation: correlationArg,
    });
  }

  public async handleLog(logPackageArg: plugins.smartlogInterfaces.ILogPackage) {
    await this.logRouter.routeLog(logPackageArg);
  }

  private safeConsoleLog(logLine: string) {
    console.log(
      `LOG => ${new Date().getHours()}:${new Date().getMinutes()}:${new Date().getSeconds()} => ${logLine}`,
    );
  }

  public createLogGroup(transactionId: string = 'none') {
    return new LogGroup(this, transactionId);
  }
}
