import * as retry from './retry';
import path from 'path';

const log4js = require('log4js');
const logger = log4js.getLogger(path.basename(__filename));
logger.level = 'debug';

export const RETRYABLE_DEFAULTS = {
  retries: 5,
  factor: 1,
  minTimeout: 5 * 1000,
  maxTimeout: 5 * 1000
};
/**
   *
   * RETRYABLE
 *
   * @param: promiseFunc - The funtion to retry. The function returns a promise.
 * @param: retryOptions - See node module "retry" defaults is
 * {
 *   retries: 5,
 *   factor: 1,
 *   minTimeout: 5 * 1000,
 *   maxTimeout: 5 * 1000
 * }
 * @param: isRetryableErrorCb - if supplied should return true if the error can be retried.
 * If cannot retry on this error return false; Default retries on all errors;
 *
   * */

export let RETRYABLE_PROMISE_WRAPPER = {
  promiseFuncWrapper: (promiseFunc: any, count: number) => {
    return promiseFunc(count);
  }
};

export function RETRYABLE(promiseFunc: any, retryOptions: any = RETRYABLE_DEFAULTS, isRetryableErrorCb: any = () => { return true; }) {
  return new Promise((resolve, reject) => {
    var operation = retry.operation(retryOptions);
    operation.attempt(function (count: any) {
      RETRYABLE_PROMISE_WRAPPER.promiseFuncWrapper(promiseFunc, count)
        .then((result: any) => {
          resolve(result);
        })
        .catch((error: any) => {
          logger.error(`Error occurred in operation => ${error.stack}`);
          var isRetryable = isRetryableErrorCb(error);
          let stringError = error + "";
          try {
            stringError = JSON.stringify(error);
          }
          catch (e) {
          }
          if (isRetryable) {
            retryOptions['forever']
              ? logger.error(`Error: ${stringError}; forever: ${retryOptions['forever']}; retrying-- attempt #${count} `)
              : logger.error(`Error: ${stringError}; retrying-- attempt #${count}/${retryOptions['retries']}`);
            if (operation.retry(error)) {
              return;
            }
          }
          else {
            logger.error(`Error: Not retyable - ${stringError}`);
          }
          reject(error);
        });
    });
  });
}

class MyError extends Error {
  constructor(captureStart: any) {
    super();

    Error.prepareStackTrace = (error, structuredStackTrace) => {
      return structuredStackTrace;
    };
    Error.captureStackTrace(this, captureStart);
  }
}

export class RetryableUtil {
  static getRetryableCallsite(captureStart: any = RETRYABLE): any {
    let origPrepareStackTrace = Error.prepareStackTrace;

    const myObject = new MyError(captureStart);
    const siteList = myObject.stack;

    Error.prepareStackTrace = origPrepareStackTrace;
    return siteList;
  }

  // eslint-disable-next-line sonarjs/cognitive-complexity
  static breakOn(options: any = {
    functionNameRE: undefined,
    fileNameRE: undefined,
    lineNumber: undefined,
    captureStart: undefined,
    showCallStack: undefined
  }) {

    let captureStart = options.captureStart || RETRYABLE;

    const siteList: any = RetryableUtil.getRetryableCallsite(captureStart);
    if (siteList.length === 0) {
      return null;
    }

    let functionName = null;
    let fileName = null;
    let lineNumber = -1;
    let matched = false;

    const callStack: any[] = [];
    for (let site of siteList) {
      functionName = site.getFunctionName() || 'anonymouse';
      fileName = site.getFileName();
      lineNumber = site.getLineNumber();


      if (options['showCallStack'] === true) {
        callStack.push(`functionName: ${functionName} fileName: ${fileName} lineNumber: ${lineNumber}`);
      }


      let functionNameREMatched = (options.functionNameRE) ? options.functionNameRE.test(functionName) : true;
      let fileNameREMatched = (options.fileNameRE) ? options.fileNameRE.test(fileName) : true;
      let lineNumberMatched = (options.lineNumber) ? options.lineNumber === lineNumber : true;

      matched = (functionNameREMatched && fileNameREMatched && lineNumberMatched);
      if (matched) {
        if (options['showCallStack'] === true) {
          for (const stack_line of callStack) {
            logger.info(stack_line);
          }
        }
        return site;
      }
    }
    return null;
  }
}
