package com.testor.module.safeCheck.service.impl;

import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.testor.common.util.DateUtil;
import com.testor.common.util.MsgRemindTypeEnum;
import com.testor.common.util.cron.FrequencyUtil;
import com.testor.module.messageNotice.model.domain.TMsgNotice;
import com.testor.module.messageNotice.model.domain.TSysMsgRemindConf;
import com.testor.module.messageNotice.model.dto.TSysMsgRemindConfParam;
import com.testor.module.messageNotice.model.vo.MsgTemplateVO;
import com.testor.module.messageNotice.service.TMsgNoticeService;
import com.testor.module.messageNotice.service.TSysMsgRemindConfService;
import com.testor.module.safeCheck.model.domain.TSafeHazardCheckPeople;
import com.testor.module.safeCheck.model.domain.TSafeHazardCheckTask;
import com.testor.module.safeCheck.service.HazardCheckTaskService;
import com.testor.module.safeCheck.service.TSafeHazardCheckPeopleService;
import com.testor.module.safeCheck.service.TSafeHazardCheckPlanService;
import com.testor.module.safeCheck.service.TSafeHazardCheckTaskService;
import com.testor.xxl.job.task.module.job.model.JobStopRequest;
import com.testor.xxl.job.task.module.job.model.XxlJobVo;
import com.testor.xxl.job.task.module.job.service.XxlJobService;
import com.testor.xxl.job.task.response.ReturnT;
import com.testor.xxl.job.task.util.CronUtil;
import com.tongtech.tfw.backend.common.biz.constants.BizConstants;
import com.xxl.job.core.context.XxlJobHelper;
import com.xxl.job.core.handler.annotation.XxlJob;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
import java.util.*;

/**
 * 通过xxl-job生成任务 处理类(执行器)
 *
 * @author testor-framework
 * @date 2022-08-04 11:07:59
 */
@Slf4j
@Service
public class HazardCheckTaskServiceImpl implements HazardCheckTaskService {

    @Resource
    private TSafeHazardCheckPeopleService tSafeHazardCheckPeopleService;

    @Resource
    private TSafeHazardCheckPlanService tSafeHazardCheckPlanService;

    @Resource
    private TSafeHazardCheckTaskService tSafeHazardCheckTaskService;

    @Resource
    private TSysMsgRemindConfService tSysMsgRemindConfService;

    @Resource
    private TMsgNoticeService tMsgNoticeService;

    @Resource
    private XxlJobService xxlJobService;

    @Resource
    private FrequencyUtil frequencyUtil;

    /**
     * 每天0点 查看、处理需要下发的任务
     * 去掉安全巡检计划中提前派发任务功能，修正为任务开始时间当日0点派发，派发后允许执行
     */
    @Override
    @XxlJob("createCheckTask")
    public void createCheckTaskExecutor() {
        LocalDateTime nowTime = LocalDateTime.now();
        List<TSafeHazardCheckPeople> checkPeopleList = tSafeHazardCheckPeopleService.listDoingVOByPlanDate(nowTime);
        if(checkPeopleList == null || checkPeopleList.size() == 0) {
            return ;
        }

        // 创建任务 并 更新 状态
        List<TSafeHazardCheckPeople> updateList = new ArrayList<>();
        for (TSafeHazardCheckPeople checkPeople : checkPeopleList) {
            if (!BizConstants.STATUS_ENABLE.equals(checkPeople.getStatus())) {
                continue;
            }
            String startCronStr = checkPeople.getStartCronExpression();
            ZonedDateTime zonedDateTime = CronUtil.calculateTime(startCronStr, 2);
            if (zonedDateTime == null) {
                continue;
            }

            LocalDateTime startTaskTime = zonedDateTime.toLocalDateTime();
            // 一天的结束时间，例如：2020-02-02 23:59:59,999
            LocalDateTime endOfDay = LocalDateTimeUtil.endOfDay(nowTime);
            // 开始时间非今天的 过滤调
            if(LocalDateTimeUtil.between(startTaskTime, endOfDay, ChronoUnit.SECONDS) < 0) {
                continue;
            }

            // 创建任务
            try{
                TSafeHazardCheckTask doResult = tSafeHazardCheckTaskService.generateCheckTask(checkPeople.getId());
                if(doResult == null || StrUtil.isBlank(doResult.getId())) {
                    log.error("巡检计划【{}】中人员【{}】的任务创建失败{}", checkPeople.getPlanId(), checkPeople.getId(), nowTime);
                    continue;
                }
                checkPeople.setGenerateTask("1");
                updateList.add(checkPeople);
            }catch(Exception e) {
                log.error("巡检计划【{}】中人员【{}】的任务创建失败{}", checkPeople.getPlanId(), checkPeople.getId(), nowTime);
            }
        }

    }

    /**
     * @param advanceHour 提前时长（小时）
     * @param sourceTime  需对比的时间源
     * @param startDate   任务开始时间
     * @param endDate     任务结束时间
     * @param jobId       任务id
     * @return false: 不执行业务逻辑； true：执行业务逻辑
     */
    public Boolean checkTaskTime(Long advanceHour, LocalDateTime sourceTime,
                                 LocalDateTime startDate, LocalDateTime endDate, Long jobId) {

        LocalDateTime offsetTime;
        if(advanceHour!=null) {
            offsetTime = LocalDateTimeUtil.offset(sourceTime, advanceHour, ChronoUnit.HOURS);
        } else {
            offsetTime = sourceTime;
        }
        long startDiffVal = LocalDateTimeUtil.between(offsetTime, startDate, ChronoUnit.SECONDS);
        if (startDiffVal > 0) { // 未开始（sourceTime < startDate）
            return false;
        }

        long endDiffVal = LocalDateTimeUtil.between(sourceTime, endDate, ChronoUnit.SECONDS);
        if (endDiffVal < 0) { // 此任务已结束 （sourceTime > endDate）
            // 停止任务
            JobStopRequest request = new JobStopRequest();
            request.setId(jobId.intValue());
            ReturnT result = xxlJobService.execute(request);
            log.info("jobId.intValue():{}, stopResult:{}",jobId.intValue(), result );
//            JobRemoveRequest removeRequest = new JobRemoveRequest();
//            removeRequest.setId(jobId.intValue());
//            ReturnT removeResult = xxlJobService.execute(removeRequest);
//            log.info("jobId.intValue():{}, removeResult:{}",jobId.intValue(), removeResult );
            return false;
        }
        return true;
    }

    // 任务过期
    @Transactional
    @XxlJob("overdue")
    public void overdueExecutor() {
        String params = XxlJobHelper.getJobParam();
        if (StrUtil.isBlank(params)) {
            return;
        }
        Map<String, Object> remindParamMap = JSON.parseObject(params, Map.class);
        String taskId = remindParamMap.get("taskId").toString();
        LocalDateTime oldEndDate = LocalDateTimeUtil.parse(remindParamMap.get("endDate").toString());

        TSafeHazardCheckTask checkTask = tSafeHazardCheckTaskService.getById(taskId);
        Date endDateDate = checkTask.getEndDate();
        LocalDateTime endDate = DateUtil.dateToLocalDateTime(endDateDate);

        if(LocalDateTimeUtil.between(oldEndDate, endDate, ChronoUnit.HOURS) == 0) { // 结束时间未变，没有进行延期申请
            List<String> userIdList = new ArrayList<>();
            userIdList.add(checkTask.getUserId());
            MsgTemplateVO msgTemplateVO = new MsgTemplateVO();
            msgTemplateVO.setTemplateType("overdue");
            msgTemplateVO.setTemplateTypeName("逾期通知");
            msgTemplateVO.setTemplateContent("您的安全巡检任务“"+checkTask.getName()+"”于"+endDate+"逾期，请尽快处理。");
            List<TMsgNotice> msgNoticeList = tMsgNoticeService.generateMsgNotice("overdue",taskId, userIdList, msgTemplateVO);
            tMsgNoticeService.saveBatch(msgNoticeList, msgNoticeList.size());
            return;
        }

        //结束时间发生变化，发生延期申请，重新创建 过期任务
        LocalDateTime overdueTime = DateUtil.dateToLocalDateTime(checkTask.getEndDate());
        String overdueCronStr = CronUtil.generateCronByOne(overdueTime);
        frequencyUtil.createTaskJob(JSON.toJSONString(remindParamMap), overdueCronStr,
                "巡检任务过期提醒", "overdue");
    }

    // 创建启动 即将过期 job任务
    @Transactional
    @XxlJob("createNearOverdueJobExecutor")
    public void createNearOverdueJobExecutor() {
        String params = XxlJobHelper.getJobParam();
        if (StrUtil.isBlank(params)) {
            return;
        }

        createNearOverdue(params);

    }

    public void createNearOverdue(String params) {

        if (StrUtil.isBlank(params)) {
            return;
        }
        Map<String, Object> remindParamMap = JSON.parseObject(params, Map.class);
        String taskId = remindParamMap.get("taskId").toString();
        LocalDateTime oldEndDate = LocalDateTimeUtil.parse(remindParamMap.get("endDate").toString());
        String nearOverdueRemindCronStr = remindParamMap.get("nearOverdueRemindCronStr").toString();

        TSafeHazardCheckTask checkTask = tSafeHazardCheckTaskService.getById(taskId);
        LocalDateTime endDate = DateUtil.dateToLocalDateTime(checkTask.getEndDate());

        if(!checkEndData(oldEndDate, endDate)) { // 结束时间未变，没有进行延期申请
            // 创建 安全巡检 即将 逾期 消息提醒 job
            frequencyUtil.createTaskJob(JSON.toJSONString(remindParamMap), nearOverdueRemindCronStr,
                    "安全巡检即将逾期提醒", "nearOverdueRemindExecutor");
            return;
        }

        // 结束时间变动，重新创建 即将过期 job任务
        TSysMsgRemindConf remindConf = findCheckRemindConf();
        remindParamMap.put("nearOverdueRemindCronStr", remindConf.getCronExpression());
        remindParamMap.put("endDate", endDate);

        Map<String, Object> nearOverdueMap = generateNearOverdueCronStrByConf(remindConf, null, endDate);
        if(nearOverdueMap == null) {
            return;
        }
        //  jobTime-任务job的运行时间 ； nearOverdueCronStr-任务job的 cron表达式
        String nearOverdueCronStr = nearOverdueMap.get("nearOverdueCronStr").toString();
        LocalDateTime jobTime = LocalDateTimeUtil.parse(nearOverdueMap.get("jobTime").toString());
        LocalDateTime nowDate = LocalDateTime.now();
        //  当前时间 大于 即将逾期提醒设置的时间点 ，则直接创建 安全巡检即将逾期提醒任务
        if(LocalDateTimeUtil.between(nowDate, jobTime, ChronoUnit.SECONDS) <= 0) {
            // 创建 安全巡检 即将 逾期 消息提醒 job
            frequencyUtil.createTaskJob(JSON.toJSONString(remindParamMap), nearOverdueRemindCronStr,
                    "安全巡检即将逾期提醒", "nearOverdueRemindExecutor");
            return;
        }
        frequencyUtil.createTaskJob(JSON.toJSONString(remindParamMap), nearOverdueCronStr,
                "安全巡检即将逾期", "createNearOverdueJobExecutor");

    }

    // 即将逾期提醒
    @Transactional
    @XxlJob("nearOverdueRemindExecutor")
    public void nearOverdueRemindExecutor() {
        String params = XxlJobHelper.getJobParam();
        if (StrUtil.isBlank(params)) {
            return;
        }
        Map<String, Object> remindParamMap = JSON.parseObject(params, Map.class);
        String taskId = remindParamMap.get("taskId").toString();
        LocalDateTime oldEndDate = LocalDateTimeUtil.parse(remindParamMap.get("endDate").toString());

        TSafeHazardCheckTask checkTask = tSafeHazardCheckTaskService.getById(taskId);
        LocalDateTime endDate = DateUtil.dateToLocalDateTime(checkTask.getEndDate());

        LocalDateTime nowDate = LocalDateTime.now();
        Boolean checkResult = checkTaskTime(null, nowDate, DateUtil.dateToLocalDateTime(checkTask.getStartDate()), endDate, XxlJobHelper.getJobId());
        if(checkResult == null || !checkResult) {
            return;
        }

        if(!checkEndData(oldEndDate, endDate)) { // 结束时间未变，没有进行延期申请
            long diffValHour = LocalDateTimeUtil.between(LocalDateTimeUtil.now(), endDate, ChronoUnit.HOURS);
            List<String> userIdList = new ArrayList<>();
            userIdList.add(checkTask.getUserId());
            MsgTemplateVO msgTemplateVO = new MsgTemplateVO();
            msgTemplateVO.setTemplateType("nearOverdue");
            msgTemplateVO.setTemplateTypeName("即将逾期提醒");
            msgTemplateVO.setTemplateContent("您的安全巡检任务“"+checkTask.getName()+"”将在"+diffValHour+"个小时后逾期，请尽快处理。");
            List<TMsgNotice> msgNoticeList = tMsgNoticeService.generateMsgNotice("overdue",taskId, userIdList, msgTemplateVO);
            tMsgNoticeService.saveBatch(msgNoticeList, msgNoticeList.size());
            return;
        }

        //结束时间发生变化，发生延期申请，重新创建 即将过期 job任务
        LocalDateTime overdueRemindTime = DateUtil.dateToLocalDateTime(checkTask.getEndDate());

        String overdueRemindCronStr = CronUtil.generateCronByOne(overdueRemindTime);
        frequencyUtil.createTaskJob(JSON.toJSONString(remindParamMap), overdueRemindCronStr,
                "安全巡检即将逾期", "createNearOverdueJobExecutor");

        //结束时间发生变化，发生延期申请，关闭停止 当前的即将逾期消息提醒 job
        Long jobId = XxlJobHelper.getJobId();
        JobStopRequest request = new JobStopRequest();
        request.setId(jobId.intValue());
        ReturnT stopResult = xxlJobService.execute(request);
        if(stopResult.getCode() != 200) {
            throw new RuntimeException("关闭 安全巡检即将逾期job失败 ， taskId="+taskId);
        }
    }

    /**
     * 以小时为对比单位
     * @return false: 到期时间未发生延期；  true: 发生延期
     */
    public Boolean checkEndData(LocalDateTime oldEndDate, LocalDateTime newEndDate) {
        long diffVal = LocalDateTimeUtil.between(oldEndDate, newEndDate, ChronoUnit.HOURS);
        if( diffVal == 0) { // 结束时间未变，没有进行延期申请
           return false;
        }
        return true;
    }

    /**
     * 修改巡检计划消息提醒配置
     * 查找所有已存的 任务到期前Job
     * 停止、删除所有 巡检任务到期前 Job
     * 创建配置变更后的job
     *
     * 此方法可 异步执行
     */
    @Override
    @Async
    public void handleNearOverdueJobByCof() {
        // 获取 执行器id
        List<XxlJobVo> jobVoList = frequencyUtil.findExecutorList(null, "createNearOverdueJobExecutor");
        if(jobVoList == null || jobVoList.size() == 0) {
            return ;
        }

        for(XxlJobVo jobVo: jobVoList) {
            Map<String, Object> remindParamMap = JSON.parseObject(jobVo.getExecutorParam(), Map.class);
            String taskId = remindParamMap.get("taskId").toString();

            TSafeHazardCheckTask checkTask = tSafeHazardCheckTaskService.getById(taskId);
            LocalDateTime endDate = DateUtil.dateToLocalDateTime(checkTask.getEndDate());

            // 关闭之前的job
            int jobId = jobVo.getId();
            JobStopRequest request = new JobStopRequest();
            request.setId(jobId);
            ReturnT stopResult = xxlJobService.execute(request);
            if(stopResult.getCode() != 200) {
                XxlJobHelper.log("关闭 安全巡检即将逾期job失败 ， taskId="+taskId);
                continue;
            }

            // 创建配置变更后的job
            TSysMsgRemindConf remindConf = findCheckRemindConf();
            remindParamMap.put("nearOverdueRemindCronStr", remindConf.getCronExpression());
            remindParamMap.put("endDate", endDate);

            LocalDateTime nowDate = LocalDateTime.now();
          //  String cronStr = generateNearOverdueCronStrByConf(remindConf,null, endDate);
            Map<String, Object> nearOverdueMap = generateNearOverdueCronStrByConf(remindConf, null, endDate);
            if(nearOverdueMap == null) {
                return;
            }
            String nearOverdueCronStr = nearOverdueMap.get("nearOverdueCronStr").toString();
            LocalDateTime jobTime = LocalDateTimeUtil.parse(nearOverdueMap.get("jobTime").toString());
            //  当前时间 大于 即将逾期提醒设置的时间点 ，则直接调用处理方法，不创建任务
            if(LocalDateTimeUtil.between(nowDate, jobTime, ChronoUnit.SECONDS) <= 0) {
                createNearOverdue(JSON.toJSONString(remindParamMap));
                return;
            }

            frequencyUtil.createTaskJob(JSON.toJSONString(remindParamMap), nearOverdueCronStr,
                    "安全巡检即将逾期", "createNearOverdueJobExecutor");
        }

    }

    /**
     * 获取 巡检排查计划 消息提醒 配置信息
     * @return
     */
    public TSysMsgRemindConf findCheckRemindConf() {
        TSysMsgRemindConf remindConf ;
        TSysMsgRemindConfParam remindConfParam = new TSysMsgRemindConfParam();
        remindConfParam.setStatus(BizConstants.STATUS_ENABLE);
        remindConfParam.setType(MsgRemindTypeEnum.checkTask.getCode());
        List<TSysMsgRemindConf> remindConfList = tSysMsgRemindConfService.listAllEntity(remindConfParam);
        if(remindConfList == null || remindConfList.size() != 1) {
            log.error("巡检排查即将过期提醒配置数据错误");
            return null;
        }
        remindConf = remindConfList.get(0);
        return remindConf;
    }

    /**
     * 根据巡检计划消息提醒 配置信息 生成 即将过期 job任务 cron表达式
     * taskId 、 endDate 有一个有值即可（优先使用endDate）
     * @param taskId
     * @param endDate 任务到期时间
     * @return map :  jobTime-任务job的运行时间 ； nearOverdueCronStr-任务job的 cron表达式
     */
    public Map<String, Object> generateNearOverdueCronStrByConf(TSysMsgRemindConf remindConf, String taskId, LocalDateTime endDate){
        if(StrUtil.isBlank(taskId) && endDate == null) {
            return null;
        }
        if(endDate == null) {
            TSafeHazardCheckTask checkTask = tSafeHazardCheckTaskService.getById(taskId);
            if(checkTask == null) {
                return null;
            }
            endDate= DateUtil.dateToLocalDateTime(checkTask.getEndDate());
        }

        Map<String, Object> dataMap = new HashMap<>();
        int unit = remindConf.getUnit();
        LocalDateTime jobTime;
        if(unit == 1) { // 小时
            jobTime = LocalDateTimeUtil.offset(endDate, -remindConf.getValue(), ChronoUnit.HOURS);
        } else if(unit == 2) { // 天
            jobTime = LocalDateTimeUtil.offset(endDate, -remindConf.getValue(), ChronoUnit.DAYS);
        } else {
            return null;
        }
        String nearOverdueCronStr = CronUtil.generateCronByOne(jobTime);
        dataMap.put("jobTime", jobTime);
        dataMap.put("nearOverdueCronStr", nearOverdueCronStr);

        return dataMap;
    }

}
