/**
 * 重试模块
 * @date 2022-08
 * @author 何华东
 */

import { logger } from "mslib";

/**
  * 重试函数, 当重试失败后会抛出异常
  * 
  * ```ts
  * import { logger } from "mslib";
  * import { retry } from "refs/nslib";
  * 
  * async function exec(){
  *    try{
  *       let res = await retry(() => {
  *          //todo...
  *       }, 1, 1000 * 60);
  *    } catch(error){
  *        logger.error(error);
  *    }
  * }
  * 
  * ```
  * @param func 可重试的目标方法
  * @param retryMaxCount 重试次数
  * @param retryDelayMs 每次重试延迟毫秒数
  */
export function retry(func: () => Promise<any> | any,
    retryMaxCount: number,
    retryDelayMs: number,
    config?: {
        /**
         * 异常处理
         */
        errorHandler?: (error: any, retryCountLeft: number) => void,
        /**
         * 重试条件, 如果目标方法未出现异常，可以根据返回值判断是否重试, `true`: 是，否则不重试
         */
        retryCondition?: (res: any) => boolean
    }
) {
    return new Promise<any>(async (resolve, reject) => {
        let _error;
        let res;

        try {
            res = await func();
            //根据条件重试
            if (typeof config?.retryCondition !== 'function' || !config.retryCondition(res)) {
                resolve(res);
                return;
            }
        } catch (error) {
            _error = error;
            if (typeof config?.errorHandler === 'function') config.errorHandler(error, retryMaxCount);
        }

        if (retryMaxCount <= 0) {
            reject(_error || res);
        } else {
            setTimeout(() => {
                resolve(retry(func, --retryMaxCount, retryDelayMs, config));
            }, retryDelayMs);
        }
    });
}

/**
 * 重试器装饰器, 重试失败后返回空, 不会抛出异常
 * 
 * @param methodDesc 方法描述, 仅为记录日志
 * @param retryMaxCount 重试次数
 * @param retryDelayMs 每次重试延迟毫秒数
 */
export function Retryable(methodDesc?: string, retryMaxCount: number = 1, retryDelayMs: number = 1000 * 60, config?: {
    /**
     * 异常处理
     */
    errorHandler?: (error: any, retryCountLeft: number) => void,
    /**
     * 重试条件, 如果目标方法未出现异常，可以根据返回值判断是否重试, `true`: 是，否则不重试
     */
    retryCondition?: (res: any) => boolean
}): MethodDecorator {
    return function (target: Object, key: string, descriptor: PropertyDescriptor) {
        let method = descriptor.value;

        descriptor.value = async function (...args) {
            try {
                return await retry(async () => {
                    return await method.apply(this, args);
                }, retryMaxCount, retryDelayMs,
                    {
                        errorHandler: config?.errorHandler,
                        retryCondition: config?.retryCondition
                    });
            } catch (error) {
                logger.error(`${methodDesc || ''}'${target.constructor.name}/${key}'执行失败(重试失败),参数:${JSON.stringify(args)}\n>>>>>`, error);
                return null;
            }
        }
    }
}