/**
 * 装饰器模块
 * @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 () => {
            logger.info(`${description || '定时任务'} >>>>> 开始执行`);

            try {
                await service?.doService();
            } catch (error) {
                logger.error(`${description || '定时任务'}'${target.name}'出现异常`, error);
            }

            logger.info(`${description || '定时任务'} >>>>> 执行结束`);
        });
    }
}

/**
 * 重试执行器
 * @param func 目标方法
 * @param retryCount 重试次数
 * @param errorHandler 异常处理
 * @param delayMs 每次重试延迟毫秒数
 */
function retry(func: () => Promise<any>,
    retryCount: number,
    errorHandler: (error: any, retryIndex?: number) => void,
    delayMs?: number) {
    return new Promise<any>((resolve, reject) => {
        func()
            .then(res => { resolve(res); })
            .catch(async error => {
                errorHandler(error);

                setTimeout(async () => {
                    for (let i = retryCount; i > 0; i--) {
                        if (delayMs) await delay(delayMs);

                        try {
                            let res = await func();
                            resolve(res);
                            break;
                        } catch (error) {
                            errorHandler(error, retryCount - i + 1);
                        }
                    }

                    reject('已用尽尝试次数');
                });
            });
    });
}

/**
 * 重试装饰器
 */
export function Retryable(config?: {
    /**
     * 可重试方法描述，记录日志使用
     */
    methodDesc?: string,

    /**
     * 重试次数，默认一次
     */
    retryCount?: number,

    /**
     * 每次重试延迟毫秒数，默认1分钟
     */
    ms?: number
}): MethodDecorator {
    return function (target: Object, key: string, descriptor: PropertyDescriptor) {
        let method = descriptor.value;

        descriptor.value = async function (...args) {
            let result = await retry(async () => {
                return await method.apply(this, args);
            }, config?.retryCount || 1, (error, retryIndex) => {
                logger.error(`${config?.methodDesc || '可重试方法'}'${target.constructor.name}/${key}'出现异常,已重试${retryIndex || 0}次,方法参数:${JSON.stringify(args)} `, error);
            }, config?.ms || 1000 * 60);

            return result;
        }
    }
}

/**
 * 同步锁
 */
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 }