import httpContext from 'express-http-context';
import path from 'path';
import config from 'config';
import {
  Logger as WinstonLogger,
  createLogger,
  format,
  transports,
} from 'winston';
import 'winston-syslog';
import fileUtils from './utils/file-utils';

const logFolder = process.env.LOG_LOCAL_FOLDER || './logs/';
const exceptionsFileName =
  process.env.LOG_LOCAL_EXCEPTION_FILE_NAME || 'exceptions.log';
const logFileName = process.env.LOG_LOCAL_FILE_NAME || 'logs.log';
const logEnvironment = process.env.LOG_ENVIRONMENT || 'console';
const logLevel = process.env.LOG_LEVEL || 'info';
const applicationName = config.get<string>('service_name') || '';
const exceptionsFilePath = path.join(logFolder, exceptionsFileName);
const regularLogFilePath = path.join(logFolder, logFileName);

type LogFunction = (
  message: string,
  initTime?: number,
  endTime?: number,
  requestId?: string,
) => void;

class Logger {
  private logger: WinstonLogger;

  public info: LogFunction;
  public debug: LogFunction;
  public warn: LogFunction;
  public error: LogFunction;
  public crit: LogFunction;

  constructor(globalFilename: string) {
    const colorizer = format.colorize();
    const filenamePath = fileUtils.getFolderAndFilename(globalFilename);
    const defaultLoggingFormat = format.combine(
      format.simple(),
      format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
      format.printf((info) =>
        colorizer.colorize(
          info.level,
          `[${info.timestamp}][${info.level}][${info.applicationName}][${info.filenamePath}] ${info.message}`,
        ),
      ),
    );

    const transportsList: transports.StreamTransportInstance[] = [
      new transports.Console({ format: defaultLoggingFormat, level: logLevel }),
    ];

    if (logEnvironment.toLowerCase() === 'local') {
      transportsList.push(
        new transports.File({
          filename: regularLogFilePath,
          format: defaultLoggingFormat,
        }) as transports.StreamTransportInstance,
        new transports.File({
          filename: exceptionsFilePath,
          format: defaultLoggingFormat,
        }) as transports.StreamTransportInstance,
      );
    }

    this.logger = createLogger({
      level: logLevel,
      format: defaultLoggingFormat,
      defaultMeta: { applicationName, filenamePath },
      transports: transportsList,
      exceptionHandlers: transportsList,
    });

    const prepareMessage = (
      message: string,
      initTime?: number,
      endTime?: number,
      requestId?: string,
    ) => {
      const transactionId = httpContext.get('Request-ID');
      const separator = transactionId ? `[id:${transactionId}] >>> ` : ' >>> ';
      let messageToLog = '';

      if (initTime === undefined) {
        messageToLog = buildMessage(requestId, separator, message);
      } else if (endTime === undefined) {
        messageToLog = buildMessage(requestId, separator, message, initTime);
      } else {
        messageToLog = buildMessage(
          requestId,
          separator,
          message,
          initTime,
          endTime,
        );
      }

      return messageToLog;
    };

    this.info = (message, initTime?, endTime?, requestId?) =>
      this.logger.info(prepareMessage(message, initTime, endTime, requestId));

    this.debug = (message, initTime?, endTime?, requestId?) =>
      this.logger.debug(prepareMessage(message, initTime, endTime, requestId));

    this.warn = (message, initTime?, endTime?, requestId?) =>
      this.logger.warn(prepareMessage(message, initTime, endTime, requestId));

    this.error = (message, initTime?, endTime?, requestId?) =>
      this.logger.error(prepareMessage(message, initTime, endTime, requestId));

    this.crit = (message, initTime?, endTime?, requestId?) =>
      this.logger.crit(prepareMessage(message, initTime, endTime, requestId));
  }
}

function buildMessage(
  requestId: string | undefined,
  separator: string,
  message: string,
  initTime?: number,
  endTime?: number,
): string {
  let messageToLog = '';
  if (initTime !== undefined && endTime !== undefined) {
    const difference = endTime - initTime;
    messageToLog = `[${difference}ms]`;
  } else if (initTime !== undefined) {
    const _endTime = new Date().getTime();
    const difference = _endTime - initTime;
    messageToLog = `[${difference}ms]`;
  }

  if (requestId !== undefined) {
    messageToLog += `[id:${requestId}]`;
  }
  messageToLog += `${separator}${message}`;
  return messageToLog;
}

export default Logger;
