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

import { Container } from "typescript-ioc";
import { logger } from "mslib"
import * as schedule from 'node-schedule'
import Contants from "./Contants";
import { ApiError } from "./Errors";
import dayjs = require("dayjs");

/**
 * Cron计划任务
 * 
 * @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}/${Contants.P_ID}.${target.name}/1.0 ${description}(${cronExpression})`);

        //支持开关,一般用于测试环境下禁用计划任务
        if (!Container.getValue('config.cron.enabled')) return;

        schedule.scheduleJob(cronExpression, async () => {
            try {
                let service = Container.get(target);
                await service.doService();
            } catch (error) {
                logger.error(`${description || '定时任务'}${target.name}出现异常`, `\n异常信息>>>>>`, error instanceof ApiError && !Contants.IS_DEBUG ? error.toString() : error);
            }
        });
    }
}

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

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

/**
 * 重试器
 * 
 * 重试条件:
 * 
 * 1.如果可重试目标方法出现异常,会自动进行重试
 * 
 * 2.可重试目标方法未抛出异常,根据返回结果并传入重试条件retryCondition方法判断是否进行重试
 * @param methodDesc 方法描述,仅为记录日志
 * @param retryMaxCount 重试最大次数
 * @param retryDelayMs 每次重试延迟毫秒数
 * @param errorHandler 可重试的目标方法出现异常时的异常处理,不会打断重试逻辑，一般用于记录日志
 * @param retryCondition 重试条件,传入可重试的目标方法返回的结果判断是否重试
 */
export function Retryable(methodDesc?: string, retryMaxCount?: number, retryDelayMs?: number, 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 {
                let result = await retry(async () => {
                    return await method.apply(this, args);
                },
                    retryMaxCount || 1,
                    retryDelayMs || 1000 * 60,
                    {
                        errorHandler: (error, retryCountLeft) => {
                            logger.error(`${methodDesc || ''}${target.constructor.name}/${key}出现异常(已重试${retryMaxCount - retryCountLeft}次}),方法参数:${JSON.stringify(args)}`,`\n异常信息>>>>>`, error instanceof ApiError && !Contants.IS_DEBUG ? error.toString() : error);
                        },
                        retryCondition: 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);
    }
}

/**
 * 异步方法同步执行器
 * 
 * 基于Promise实现
 */
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();
            }
        }
    }
}

/**
 * 节流器
 * @param milliseconds 统计周期
 * @param count 最大执行次数
 * @param filter 过滤器,返回true表示不参与节流
 */
export function Throttle(milliseconds: number, count: number, filter?: (...args) => boolean): MethodDecorator {
    return function (target: Object, key: string, descriptor: PropertyDescriptor) {
        let method = descriptor.value;
        let count_time = dayjs();
        let counter = 0;

        descriptor.value = async function (...args) {
            let now = dayjs();

            //新的统计周期,重置统计时间和计数器
            if (dayjs(now).diff(count_time, 'millisecond') > milliseconds) {
                count_time = now;
                counter = 1;
            } else {
                counter++;

                if (counter > count) {
                    //不节流
                    if (typeof filter === 'function' && filter(args) === true) {
                        return await method.apply(this, args);
                    }

                    logger.warn(`${target.constructor.name}已被截断${counter}>${count}(${milliseconds}ms)},args:${JSON.stringify(args)}`);
                    return;
                }
            }

            return await method.apply(this, args);
        }
    }
}