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

import { logger } from "mslib";

/**
  * 重试函数
  * 
  * 重试条件:
  * 
  * 1.如果可重试目标方法出现异常,会自动进行重试
  * 
  * 2.可重试目标方法未抛出异常,根据返回结果并传入重试条件retryCondition方法判断是否进行重试
  * @param func 可重试的目标方法
  * @param retryMaxCount 重试最大次数
  * @param retryDelayMs 每次重试延迟毫秒数
  */
export function retry(func: () => Promise<any> | any,
    retryMaxCount: number,
    retryDelayMs: number,
    config?: {
        /**
         * 可重试的目标方法出现异常时的异常处理,不会打断重试逻辑，一般用于记录日志
         */
        errorHandler?: (error: any, retryCountLeft: number) => any,
        /**
         * 重试条件,传入可重试的目标方法返回的结果判断是否重试
         */
        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);
        }
    });
}

/**
 * 重试器
 * 
 * 重试条件:
 * 
 * 1.如果可重试目标方法出现异常,会自动进行重试
 * 
 * 2.可重试目标方法未抛出异常,根据返回结果并传入重试条件retryCondition方法判断是否进行重试
 * @param methodDesc 方法描述,仅为记录日志
 * @param retryMaxCount 重试最大次数
 * @param retryDelayMs 每次重试延迟毫秒数
 */
export function Retryable(methodDesc?: string, retryMaxCount: number = 1, retryDelayMs: number = 1000 * 60, config?: {
    /**
     * 异常处理
     */
    errorHandler?: (error: any, retryCountLeft: number) => any,
    /**
     * 重试条件,传入可重试的目标方法返回的结果判断是否重试
     */
    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;
            }
        }
    }
}