import httpContext from 'express-http-context';
import config from 'config';
import path from 'path';
import { createLogger, format, transports } from 'winston';
import os from 'os';
import 'winston-syslog';
import fileUtils from './utils/file-utils';

const logFolder = process.env.LOG_LOCAL_FOLDER || `./logs/`;
const exeptionsFileName = 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 = process.env.SERVICE_NAME || '';
const execptionsFilePath = path.join(logFolder, exeptionsFileName);
const regularLogFilePath = path.join(logFolder, logFileName);

export class Logger {
  private selectedTransports: any[];
  private exceptionTransports: any[];
  private logger: any;
  public info: (message: string, initTime: number, endTime: number, guid: string) => void;
  public debug: (message: string, initTime: number, endTime: number, guid: string) => void;
  public warn: (message: string, initTime: number, endTime: number, guid: string) => void;
  public error: (message: string, initTime: number, endTime: number, guid: string) => void;
  public fatal: (message: string, initTime: number, endTime: number, guid: string) => void;


  constructor(globalFilename: string) {
    const colorizer = format.colorize();
    this.selectedTransports = [];
    this.exceptionTransports = [];
    const customLevels = {
      levels: {
        crit: 0,
        error: 1,
        warn: 2,
        info: 3,
        debug: 4,
      },
    };
    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 filenamePath = fileUtils.getFolderAndFilename(globalFilename);

    const consoleLogging = new transports.Console({
      format: defaultLoggingFormat,
      level: logLevel,
    });

    // Default transport always to console
    this.selectedTransports.push(consoleLogging);
    this.exceptionTransports.push(consoleLogging);

    // Defining extra transports based on Environment variable LOG_ENVIRONMENT
    if (logEnvironment.toLowerCase() === 'local') {
      const fileLogging = new transports.File({
        filename: regularLogFilePath,
        format: defaultLoggingFormat,
      });
      const exceptionsfileLogging = new transports.File({
        filename: execptionsFilePath,
        format: defaultLoggingFormat,
      });
      this.selectedTransports.push(fileLogging);
      this.exceptionTransports.push(exceptionsfileLogging);
    }

    this.logger = createLogger({
      level: logLevel,
      levels: customLevels.levels,
      format: defaultLoggingFormat,
      defaultMeta: {
        applicationName: applicationName,
        filenamePath: filenamePath,
      },
      transports: this.selectedTransports,
      exceptionHandlers: this.exceptionTransports,
    });

    const prepareMessage = (message: string, initTime: number , endTime: number, guid: string) => {
      let separator = ' >>> ';
      const transactionId = httpContext.get('Transaction-ID');
      separator = transactionId ? `[Transaction-ID:${transactionId}]${separator}` : separator;
      let messageToLog = '';
      if (isNaN(initTime)) {
        messageToLog = buildMessageWithoutTime(initTime, messageToLog, separator, message);
      } else {
        if (isNaN(endTime)) {
          messageToLog = buildMessageWithInitTime(initTime, endTime, messageToLog, separator, message);
        } else {
          messageToLog = buildMessageWithBothTime(endTime, initTime, guid, messageToLog, separator, message);
        }
      }
      return messageToLog;
    };

    this.info = (message, initTime, endTime, guid) =>
      this.logger.info(prepareMessage(message, initTime, endTime, guid));

    this.debug = (message, initTime, endTime, guid) =>
      this.logger.debug(prepareMessage(message, initTime, endTime, guid));

    this.warn = (message, initTime, endTime, guid) =>
      this.logger.warn(prepareMessage(message, initTime, endTime, guid));

    this.error = (message, initTime, endTime, guid) =>
      this.logger.error(prepareMessage(message, initTime, endTime, guid));

    this.fatal = (message, initTime, endTime, guid) =>
      this.logger.crit(prepareMessage(message, initTime, endTime, guid));
  }
}

function buildMessageWithBothTime(endTime: number, initTime: number, guid:string, messageToLog:string, separator:string, message:string) {
  const difference = endTime - initTime;
  if (typeof guid !== 'undefined' && guid !== null) {
    const _guid = guid;
    messageToLog = `[${difference}ms][Guid:${_guid}]${separator}${message}`;
  } else {
    messageToLog = `[${difference}ms]${separator}${message}`;
  }
  return messageToLog;
}

function buildMessageWithInitTime(initTime: number, endTime: number, messageToLog:string, separator:string, message :string) {
  const _endTime = new Date().getTime();
  const difference = _endTime - initTime;
  if (typeof endTime !== 'undefined' && endTime !== null) {
    const _guid = endTime;
    messageToLog = `[${difference}ms][Guid:${_guid}]${separator}${message}`;
  } else {
    messageToLog = `[${difference}ms]${separator}${message}`;
  }
  return messageToLog;
}

function buildMessageWithoutTime(initTime: number, messageToLog: string, separator: string, message: string) {
  if (typeof initTime !== 'undefined' && initTime !== null) {
    const _guid = initTime;
    messageToLog = `[Guid:${_guid}]${separator}${message}`;
  } else {
    messageToLog = separator + message;
  }
  return messageToLog;
}



