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

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

/**
 * Cron任务装饰器
 * 
 * 将目标类已实现的方法 doService 加入到 node-schedule 计划任务中
 * @type { IService } 目标类需要实现 IService 接口
 * @param cronExpression cron表达式
 * @param description 描述
 * @returns 
 */
export function CronJob(cronExpression: string, description?: string): ClassDecorator {
    return function (target: Function) {
        logger.info(`${Contants.SS_SERVICE_BASE_URL}/${getBasicConfig()?.projectId}.${target.name}/1.0`, description);

        //调试环境不加入到 node-schedule 计划任务中
        if (Contants.IS_DEBUG) return;

        schedule.scheduleJob(cronExpression, async () => {
            let service = Container.get(target) as IService;
            await service?.doService(null);
        });
    }
}

/**
 * 可重试函数
 * 
 * 目标方法执行时出现异常，将自动捕获异常并重试
 * @param func 目标方法
 * @param retryCount 重试次数
 * @param errorHandler 异常处理
 * @param delayMs 每次重试延迟毫秒数
 * @returns 目标方法至少执行成功一次才会正确返回，否则返回空
 */
export 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();
                });
            });
    });
}

/**
 * 可重试装饰器
 * 
 * 目标方法执行时，如果方法出现异常，将自动捕获异常并重试
 * @param retryCount 重试次数，默认一次
 * @param ms 每次重试延迟时间(毫秒)，默认每次延迟1分钟重试
 * @returns 目标方法至少执行成功一次才会正确返回，否则返回空
 */
export function Retryable(retryCount?: number, ms?: number): MethodDecorator {
    retryCount = retryCount || 1;
    ms = ms || 1000 * 60;

    return function (target: Object, key: string, descriptor: PropertyDescriptor) {
        let method = descriptor.value;

        descriptor.value = async function () {
            try {
                let result = await retry(async () => {
                    return await method.apply(this, arguments);
                }, retryCount, (error, retryIndex) => {
                    logger.error(`可重试方法'${target.constructor.name}/${key}'执行失败，${retryIndex ? '已重试' + retryIndex + '次，' : ''}args:${JSON.stringify(arguments)}`, error);
                }, ms);

                return result;
            } catch {
                logger.error(`可重试方法'${target.constructor.name}/${key}'重试失败,args:${JSON.stringify(arguments)}`);
                return;
            }
        }
    }
}

/**
 * 异步锁
 */
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 () {
            await lock.promise;
            lock.enable();

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