import * as schedule from 'node-schedule'
import {EnvVariables, logger, OpResult} from "../mslib";
import {Container, Singleton, Inject} from 'typescript-ioc';
import {UTSJob} from './UTSJob';
import SignUtils from "../utils/SignUtils";
import HttpUtil from "../utils/HttpUtils";
import {v4} from 'uuid'
import {UtsDblockKnex} from './UtsDblockKnex';
/**
 * UTS定时任务调度器
 */
@Singleton
export class UTSJobScheduler {

    @Inject
    private utsDblockKnex: UtsDblockKnex;

    /**
     * 启动执行定时任务
     */
    async start() {
        const utsCron = EnvVariables.getUTSCron();
        const podMinInstance = EnvVariables.getPodMinInstance();
        if (!utsCron) {
            return;
        }
        try {
            const job = Container.get(UTSJob);
            if (utsCron === "now") { //手动，立即执行
                if(podMinInstance && Number(podMinInstance) > 1){
                    logger.info("执行uts高可用。。podMinInstance:%s",podMinInstance)
                    await this.execHA(job)
                    return
                }
                logger.info("执行uts。。podMinInstance:%s",podMinInstance)
                await this.exec(job)
            } else {
                let jobIsRunning = false;
                schedule.scheduleJob(utsCron, async () => {
                    if (jobIsRunning) {
                        return
                    }
                    try{
                        jobIsRunning = true
                        if(podMinInstance && Number(podMinInstance) > 1){
                            logger.info("执行uts高可用。。podMinInstance:%s",podMinInstance)
                            await this.execHA(job)
                            return
                        }
                        logger.info("执行uts。。podMinInstance:%s",podMinInstance)
                        await this.exec(job)
                    } finally {
                        jobIsRunning = false
                    }
                });
            }
        } catch (e) {
            logger.error('执行定时任务发生异常', e);
            await this.uploadExecuteLog(v4(), new Date(), OpResult.error('执行定时任务发生异常:' + e))
        }
    }

    /**
     * 最小实例数大于1，执行uts高可用代码
     * @param job
     * @private
     */
    private async execHA(job: UTSJob) {
        let knex = this.utsDblockKnex.get();
        const deployId = EnvVariables.getDeployId();
        let getLock = false;
        let trx = null;
        try {
            try {
                logger.info("获取锁开始!deployId:%s", deployId);
                trx = await knex.transaction();
                //获取锁之前数据必须先存在
                let result = await knex('uts_task_dblock').withSchema('kt_uts').transacting(trx).forUpdate().noWait()
                    .where('id', deployId).select('*')
                if (result.length === 0) {
                    logger.info("锁数据不存在，不能执行uts任务。。key:[%s]", deployId);
                    return
                }
                logger.info("获取锁结束!deployId:%s", deployId);
            }
            catch (error) {
                logger.info("获取pg事务锁失败。。deployId：[%s]", deployId, error)
                try {
                    if (trx != null) {
                        await trx.rollback();
                        logger.info("回滚pg事务锁成功。。deployId：[%s]", deployId)
                    } else {
                        logger.info("回滚pg事务锁，trx为null，不用回滚。。deployId：[%s]", deployId)
                    }
                }
                catch (error) {
                    logger.info("回滚pg事务锁失败。。deployId：[%s]", deployId, error)
                    return;
                }
                return;
            }
            let lockTime = new Date();
            getLock = true

            //cron配置到秒级，不同pod间实际执行的毫秒数有先后，执行uts代码前加个判断近3秒内是否已执行完
            let lockTimeResult = await knex('uts_task_dblock').withSchema('kt_uts').transacting(trx)
                .where('id', '=', deployId).select(knex.raw('extract(epoch FROM (now() - lock_time )) as intervalTime')).first();
            let intervalTime = (lockTimeResult as any).intervaltime
            if (intervalTime <= 3) {
                logger.info("上次执行时间与当前时间间隔小于3秒，不用再次执行uts任务。。deployId:[%s],intervalTime:[%s]", deployId, intervalTime);
                return
            }
            //执行uts代码
            await this.exec(job)
            logger.info("uts代码执行成功!");

            let unlockTime = new Date();

            //更新
            await knex('uts_task_dblock').withSchema('kt_uts').transacting(trx)
                .where('id', '=', deployId)
                .update({
                    pod_id: EnvVariables.getPodId(),
                    lock_time: lockTime,
                    unlock_time: unlockTime
                })
        }
        catch (error) {
            logger.error("执行uts代码/更新uts dblock表失败。。", error)
        }
        finally {
            if (getLock) {
                try {
                    if (trx != null) {
                        await trx.commit();
                        logger.info("提交pg事务锁成功。。deployId：[%s]", deployId)
                    }
                }
                catch (error) {
                    try {
                        if (trx != null) {
                            await trx.rollback();
                        }
                    }
                    catch (error) {
                        logger.info("finally回滚pg事务锁失败。。deployId：[%s]", deployId, error)
                    }
                    logger.info("提交pg事务锁失败。。deployId：[%s]", deployId, error)
                    return;
                }
            }
        }
    }

    /**
     * 执行uts代码
     * @param job
     * @private
     */
    private async exec(job: UTSJob) {
        const startTime = new Date();
        let opResult;
        const logId = v4();
        await this.uploadExecuteLog(logId, startTime, null);
        try {
            opResult = await job.execute();
        } finally {
            await this.uploadExecuteLog(logId, startTime, opResult)
        }
    }

    async uploadExecuteLog(logId: string, startTime: Date, opResult: OpResult<any>) {
        try {
            const serviceUrl = EnvVariables.getMasterRegisterUrl();
            const headers = {
                'kt-k8s-id': EnvVariables.getK8SId(),
                'kt-deploy-id': EnvVariables.getDeployId(),
                'kt-me-env': EnvVariables.getEnvCode(),
                'kt-pod-id': EnvVariables.getPodId()
            }
            headers['kt-sign'] = SignUtils.sign(headers, EnvVariables.getEnvKey());
            const body = {
                id: logId.replace(/-/gi, ''),
                startTime: startTime.getTime(),
                endTime: new Date().getTime(),
                deployId: EnvVariables.getDeployId(),
                deployTime: EnvVariables.getDeployTime(),
                executor: EnvVariables.getUTSExecutor() || 'UTS',
                taskCron: EnvVariables.getUTSCron(),
                taskType: EnvVariables.getUTSTaskType(),
            };
            if (opResult != null) {
                body['endTime'] = new Date().getTime();
                body['taskState'] = opResult.isSuccess() ? 0 : 2;//成功 or 失败
                body['responseData'] = opResult
            } else {
                body['taskState'] = 4;//执行中
            }
            const response = await HttpUtil.postJson(serviceUrl + '/uts/saveExecuteLog', {}, body, headers).catch(error => {
                logger.error('上传UTS执行结果发送异常 ', error)
            });
            if (response && response.data) {
                logger.debug('执行日志上传完成---%j', response.data)
            }

        } catch (e) {
        }
    }

}