/**
 * 装饰器模块
 * @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任务装饰器
  * @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_LOCAL_DEBUG) return;
 
         schedule.scheduleJob(cronExpression, () => {
             Container.get(target)?.doService();
         });
     }
 }
 
 /**
  * 可重试函数
  * 
  * 目标方法执行时出现异常，将自动捕获异常并重试
  * @param func 目标方法
  * @param retryCount 重试次数
  * @param errorHandler 异常处理
  * @param delayMs 每次重试延迟毫秒数
  * @returns 目标方法至少执行成功一次才会正确返回，否则返回空
  */
 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 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 method.apply(this, arguments);
             } catch (error) {
                 throw error;
             } finally {
                 lock.disable();
             }
         }
     }
 }