/**
 * 装饰器模块
 * @date 2022-03
 * @author 何华东
 */

import { Container } from "typescript-ioc";
import { logger } from "mslib"
import { delay } from './PromiseUtils'
import * as schedule from 'node-schedule'
import Contants from "./Contants";

/**
 * Cron任务装饰器
 * 
 * @type { IService } 被装饰的类需实现该接口
 * @param cronExpression cron表达式
 * @param description 任务描述
 * @returns 
 */
function CronJob(cronExpression: string, description?: string): ClassDecorator {
    return function (target: Function) {
        logger.info(`${Contants.SS_SERVICE_BASE_URL}/${process.env["KT_PID"]}.${target.name}/1.0`, description, `====> cron:${cronExpression}`);

        //本地调试环境不加入
        // if (Contants.IS_LOCAL_DEBUG) return;

        let service = Container.get(target);

        schedule.scheduleJob(cronExpression, async () => {
            try {
                await service.doService();
            } catch (error) {
                logger.error(`${description || '定时任务'}'${target.name}'出现异常`, '\n异常信息 >>>>>', error);
            }
        });
    }
}

/**
 * 重试方法
 * @param func 目标方法
 * @param retryMaxCount 重试最大次数
 * @param retryDelayMs 重试延迟毫秒数
 * @param errorHandler 异常处理
 * @param retryCondition 重试条件,默认出现异常会自动重试,但有时候方法内不会抛出异常,就无法重试,所以可以重写该条件进行重试
 */
export function retry(func: () => Promise<any>,
    retryMaxCount: number,
    retryDelayMs: number,
    errorHandler: (error: any, retryCountLeft: number) => any,
    retryCondition?: (res: any) => boolean
) {
    return new Promise<any>(async (resolve, reject) => {
        func().then(res => {
            if (typeof retryCondition !== 'function' || !retryCondition(res)) {
                resolve(res);
            } else {
                if (retryMaxCount <= 0) {
                    reject('已用尽所有尝试次数');
                } else {
                    setTimeout(() => {
                        resolve(retry(func, --retryMaxCount, retryDelayMs, errorHandler, retryCondition));
                    }, retryDelayMs);
                }
            }
        }).catch(err => {
            errorHandler(err, retryMaxCount);

            if (retryMaxCount <= 0) {
                reject('已用尽所有尝试次数');
            } else {
                setTimeout(() => {
                    resolve(retry(func, --retryMaxCount, retryDelayMs, errorHandler, retryCondition));
                }, retryDelayMs);
            }
        });
    });
}

/**
 * 重试装饰器
 * 
 * 对被装饰的方法进行自动异常重试或条件重试,该方法自动捕获异常并记录日志,异常不会往上抛
 * 
 * 至少重试成功一次,才会正确返回,否则返回null
 */
export function Retryable(methodDesc?: string, retryMaxCount?: number, retryDelayMs?: number,
    config?: {
        retryCondition?: (res) => boolean
    }): MethodDecorator {
    return function (target: Object, key: string, descriptor: PropertyDescriptor) {
        let method = descriptor.value;

        descriptor.value = async function (...args) {
            retryMaxCount = retryMaxCount || 1;

            try {
                let result = await retry(async () => {
                    return await method.apply(this, args);
                }, retryMaxCount, retryDelayMs || 1000 * 60,
                    (error, retryCountLeft) => {
                        let retryInfo = retryMaxCount !== retryCountLeft ? `已重试${retryMaxCount - retryCountLeft}次` : '';
                        logger.error(`${methodDesc || '可重试方法'}'${target.constructor.name}/${key}'出现异常`, retryInfo, '\n异常信息 >>>>>', error);
                    }, config?.retryCondition);

                return result;
            } catch (error) {
                logger.error(`${methodDesc || '可重试方法'}'${target.constructor.name}/${key}'出现异常,方法参数:${JSON.stringify(args)} \n异常信息 >>>>>`, error);
                return null;
            }
        }
    }
}

/**
 * 同步锁
 */
class AsyncLock {
    /**
     * 释放锁
     */
    disable: any;

    promise: Promise<void> = Promise.resolve();

    /**
     * 启用锁
     */
    enable() {
        this.promise = new Promise(resolve => this.disable = resolve);
    }
}

/**
 * 同步装饰器
 * 
 * 被装饰的方法将同步执行
 */
export function Sync(): MethodDecorator {
    return function (target: Object, key: string, descriptor: PropertyDescriptor) {
        let method = descriptor.value;
        let lock = new AsyncLock();

        descriptor.value = async function (...args) {
            await lock.promise;
            lock.enable();

            try {
                return method.apply(this, args);
            } catch (error) {
                throw error;
            } finally {
                lock.disable();
            }
        }
    }
}

export { CronJob }