package io.wdd.rpc.scheduler.service;

import io.wdd.common.handler.MyRuntimeException;
import io.wdd.common.utils.FunctionReader;
import io.wdd.rpc.scheduler.beans.OctopusQuartzJob;
import io.wdd.rpc.scheduler.beans.ScriptSchedulerDTO;
import io.wdd.rpc.scheduler.beans.ScriptSchedulerVO;
import io.wdd.rpc.scheduler.config.QuartzSchedulerUtils;
import io.wdd.rpc.scheduler.job.AgentScriptSchedulerJob;
import io.wdd.server.beans.po.ScriptSchedulerPO;
import io.wdd.server.service.ScriptSchedulerService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.quartz.*;
import org.quartz.impl.matchers.GroupMatcher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.quartz.QuartzJobBean;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
import java.util.stream.Collectors;

import static io.wdd.rpc.scheduler.beans.ScriptSchedulerVO.SCRIPT_SCHEDULE_MISSION_GROUP_NAME;
import static org.quartz.TriggerBuilder.newTrigger;

@Slf4j
@Service
public class QuartzSchedulerServiceImpl implements QuartzSchedulerService {

    /**
     * 保存 定时脚本任务的中间DTO，在Quartz框架中JobDataMap的Key名称
     */
    public static final String SCRIPT_SCHEDULER_MISSION_KEY = "scriptSchedulerDTO";
    @Resource
    ScriptSchedulerService scriptSchedulerService;
    @Resource
    QuartzSchedulerUtils quartzSchedulerUtils;
    @Autowired
    private Scheduler scheduler;

    @Override
    public HashMap<String, String> createScriptScheduledMission(ScriptSchedulerVO scriptSchedulerVO) {

        // validate important value
        ScriptSchedulerDTO scriptSchedulerDTO = validateAndConvertToScriptSchedulerDTO(scriptSchedulerVO);

        // build the job
        // build the trigger
        // bind job and trigger
        HashMap<String, Object> dataMap = new HashMap<>();
        dataMap.put(
                SCRIPT_SCHEDULER_MISSION_KEY,
                scriptSchedulerDTO
        );

        // 核心部分，使用Quartz框架，新增定时任务
        JobDetail jobDetail = this.addMission(
                AgentScriptSchedulerJob.class,
                // 方便删除任务，比较好定位
                scriptSchedulerDTO.getSchedulerUuid(),
                SCRIPT_SCHEDULE_MISSION_GROUP_NAME,
                scriptSchedulerDTO.getDescription(),
                // 立即开始本次任务 1ms wait
                scriptSchedulerDTO.getCronExpress(),
                dataMap
        );

        // 生成下一次真正执行的任务ResultKey
        HashMap<String, String> futureExecutionResultKey = quartzSchedulerUtils.generateAndSetFutureExecutionResultKey(scriptSchedulerDTO);

        log.debug(
                "futureExecutionResultKey is => {}",
                futureExecutionResultKey
        );

        log.info(
                "[ 定时脚本任务 ] - 新建任务成功, 任务DTO为 => {}",
                scriptSchedulerDTO
        );
        log.info(
                "[ 定时脚本任务 ] - 新建任务成功, 任务内容为 => {}",
                jobDetail
        );

        // persistent the script scheduled mission
        // dto should store more info
        ScriptSchedulerPO scriptSchedulerPO = convertToScriptSchedulerPO(scriptSchedulerDTO);
        log.debug(
                "scriptSchedulerPO is => {}",
                scriptSchedulerPO
        );
        // 持久化保存定时脚本任务至数据库中
        scriptSchedulerService.save(scriptSchedulerPO);

        return futureExecutionResultKey;
    }

    /**
     * 转换 中间层 --> 持久层
     *
     * @param dto 定时脚本任务-中间转换状态-实体类
     * @return 定时脚本任务-持久化-实体类
     */
    private ScriptSchedulerPO convertToScriptSchedulerPO(ScriptSchedulerDTO dto) {

        ScriptSchedulerPO schedulerPO = ScriptSchedulerPO
                .builder()
                .cronExpress(dto.getCronExpress())
                .schedulerUuid(dto.getSchedulerUuid())
                .scriptContent(String.valueOf(dto.getCompleteCommandList()))
                .targetMachine(String.valueOf(dto.getTargetMachineList()))
                .build();


        return schedulerPO;
    }

    /**
     * 转换 视图层 --> 中间层
     *
     * @param scriptSchedulerVO 定时脚本任务-前端页面-实体类
     * @return 定时脚本任务-中间转换状态-实体类
     */
    private ScriptSchedulerDTO validateAndConvertToScriptSchedulerDTO(ScriptSchedulerVO scriptSchedulerVO) {

        // 验证cron表达式
        if (!CronExpression.isValidExpression(scriptSchedulerVO.getCronExpress())) {
            throw new MyRuntimeException("cron express wrong !");
        }

        // 归一化 completeCommandList
        List<List<String>> completeCommandList = FunctionReader
                .ReadStringToCommandList(scriptSchedulerVO.getScriptContent());

        if (completeCommandList.size() == 0) {
            throw new MyRuntimeException("完整的命令脚本列表解析错误!");
        }

        // 执行机器目标归一化
        String[] targetMachineSplit = scriptSchedulerVO
                .getTargetMachine()
                .split(",");
        List<String> goodTargetMachine = Arrays
                .stream(targetMachineSplit)
                .map(
                        machine -> machine.replace(
                                " ",
                                ""
                        )
                )
                .collect(Collectors.toList());
        if (goodTargetMachine.size() == 0) {
            throw new MyRuntimeException("target machine wrong !");
        }
        // 防止于最终对线中
        ArrayList<String> targetMachineList = new ArrayList<>(goodTargetMachine);

        // 生成DTO对象
        ScriptSchedulerDTO dto = new ScriptSchedulerDTO();
        try {
            BeanUtils.copyProperties(
                    dto,
                    scriptSchedulerVO
            );
        } catch (IllegalAccessException | InvocationTargetException e) {
            throw new RuntimeException(e);
        }

        // 初始化属性值
        dto.setAgentTopicNameToFutureResultKeyMap(
                new HashMap<String, String>(16)
        );

        // 设置属性值
        dto.setCompleteCommandList(completeCommandList);
        dto.setTargetMachineList(targetMachineList);

        // 生成 scheduler uuid
        if (StringUtils.isEmpty(scriptSchedulerVO.getSchedulerUuid())) {
            String uuid = RandomStringUtils.randomAlphabetic(32);
            dto.setSchedulerUuid(uuid);
        }

        return dto;
    }


    @PostConstruct
    public void startScheduler() {
        try {
            scheduler.start();
        } catch (SchedulerException e) {
            e.printStackTrace();
        }
    }

    @Override
    public boolean addMission(OctopusQuartzJob quartzJob) {


        return false;
    }


    @Override
    public void addMission(Class<? extends QuartzJobBean> jobClass, String jobName, String jobGroupName, int jobRunTimePinch, int jobRunRepeatTimes, Map jobData) {
        try {
            // 任务名称和组构成任务key
            JobDetail jobDetail = JobBuilder
                    .newJob(jobClass)
                    .withIdentity(
                            jobName,
                            jobGroupName
                    )
                    .build();

            // 设置job参数
            if (jobData != null && jobData.size() > 0) {
                jobDetail
                        .getJobDataMap()
                        .putAll(jobData);
            }

            // 使用simpleTrigger规则
            Trigger trigger = newTrigger()
                    .withIdentity(
                            jobName,
                            jobGroupName
                    )
                    .withSchedule(
                            SimpleScheduleBuilder
                                    .repeatSecondlyForTotalCount(
                                            jobRunRepeatTimes,
                                            jobRunTimePinch
                                    )
                    )
                    .startNow()
                    .build();

            log.debug(
                    "jobDataMap: {}",
                    jobDetail
                            .getJobDataMap()
                            .getWrappedMap()
            );

            scheduler.scheduleJob(
                    jobDetail,
                    trigger
            );

        } catch (SchedulerException e) {
            e.printStackTrace();
            throw new MyRuntimeException("add job error!");
        }
    }

    /**
     * 增加一个job
     *
     * @param jobClass          任务实现类
     * @param jobName           任务名称(建议唯一)
     * @param jobGroupName      任务组名
     * @param startTime         单位为ms，传入 0
     *                          表示立即开始
     * @param cronJobExpression 时间表达式 （如：0/5 * * * * ? ）
     * @param jobData           参数
     * @return
     */
    @Override
    public JobDetail addMission(Class<? extends QuartzJobBean> jobClass, String jobName, String jobGroupName, int startTime, String cronJobExpression, Map jobData) {
        return addMission(
                jobClass,
                jobName,
                jobGroupName,
                null,
                cronJobExpression,
                jobData
        );
    }

    @Override
    public JobDetail addMission(Class<? extends QuartzJobBean> jobClass, String jobName, String jobGroupName, String jobDescription, String cronJobExpression, Map jobData) {
        try {
            // 创建jobDetail实例，绑定Job实现类
            // 指明job的名称，所在组的名称，以及绑定job类
            // 任务名称和组构成任务key
            JobDetail jobDetail = JobBuilder
                    .newJob(jobClass)
                    .withIdentity(
                            jobName,
                            jobGroupName
                    )
                    .withDescription(jobDescription)
                    .build();

            // 设置job参数
            if (jobData != null && jobData.size() > 0) {
                jobDetail
                        .getJobDataMap()
                        .putAll(jobData);
            }

            // 定义调度触发规则
            // 使用cornTrigger规则
            // 触发器key
            // uniform the start time

            Trigger trigger = newTrigger()
                    .withIdentity(
                            jobName,
                            jobGroupName
                    )
                    .withSchedule(
                            CronScheduleBuilder.cronSchedule(cronJobExpression)
                    )
                    .build();

            // 把作业和触发器注册到任务调度中
            scheduler.scheduleJob(
                    jobDetail,
                    trigger
            );

            return jobDetail;


        } catch (Exception e) {
            e.printStackTrace();
            throw new MyRuntimeException("add job error!");
        }
    }

    /**
     * 修改 一个job的 时间表达式
     *
     * @param jobName
     * @param jobGroupName
     * @param jobTime
     */
    @Override
    public void updateMission(String jobName, String jobGroupName, String jobTime) {
        try {
            TriggerKey triggerKey = TriggerKey.triggerKey(
                    jobName,
                    jobGroupName
            );
            CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
            log.info(
                    "prepare to UPDATE Job {} Group {} to new jobTime: {}",
                    jobName,
                    jobGroupName,
                    jobTime
            );
            trigger = trigger
                    .getTriggerBuilder()
                    .withIdentity(triggerKey)
                    .withSchedule(CronScheduleBuilder.cronSchedule(jobTime))
                    .build();
            // 重启触发器
            scheduler.rescheduleJob(
                    triggerKey,
                    trigger
            );
        } catch (SchedulerException e) {
            e.printStackTrace();
            throw new MyRuntimeException("update job error!");
        }
    }

    /**
     * 删除任务一个job
     *
     * @param jobName      任务名称
     * @param jobGroupName 任务组名
     * @return
     */
    @Override
    public boolean deleteMission(String jobName, String jobGroupName) {

        try {
            scheduler.deleteJob(new JobKey(
                    jobName,
                    jobGroupName
            ));
            return true;

        } catch (Exception e) {
            throw new MyRuntimeException("delete job error!");
        }

    }

    /**
     * 暂停一个job
     *
     * @param jobName
     * @param jobGroupName
     */
    @Override
    public void pauseMission(String jobName, String jobGroupName) {
        try {
            JobKey jobKey = JobKey.jobKey(
                    jobName,
                    jobGroupName
            );
            scheduler.pauseJob(jobKey);
        } catch (SchedulerException e) {
            e.printStackTrace();
            throw new MyRuntimeException("pause job error!");
        }
    }

    /**
     * 恢复一个job
     *
     * @param jobName
     * @param jobGroupName
     */
    @Override
    public void resumeMission(String jobName, String jobGroupName) {
        try {
            JobKey jobKey = JobKey.jobKey(
                    jobName,
                    jobGroupName
            );
            scheduler.resumeJob(jobKey);
        } catch (SchedulerException e) {
            e.printStackTrace();
            throw new MyRuntimeException("resume job error!");
        }
    }

    /**
     * 立即执行一个job
     *
     * @param jobName
     * @param jobGroupName
     */
    @Override
    public void runAJobNow(String jobName, String jobGroupName) {
        try {
            JobKey jobKey = JobKey.jobKey(
                    jobName,
                    jobGroupName
            );
            scheduler.triggerJob(jobKey);
        } catch (SchedulerException e) {
            e.printStackTrace();
            throw new MyRuntimeException("run a job error!");
        }
    }

    /**
     * 获取所有计划中的任务列表
     *
     * @return
     */
    @Override
    public List<Map<String, Object>> queryAllMission() {
        List<Map<String, Object>> jobList = null;
        try {
            GroupMatcher<JobKey> matcher = GroupMatcher.anyJobGroup();
            Set<JobKey> jobKeys = scheduler.getJobKeys(matcher);
            jobList = new ArrayList<Map<String, Object>>();
            for (JobKey jobKey : jobKeys) {
                log.info(
                        "maps: {}",
                        scheduler
                                .getJobDetail(jobKey)
                                .getJobDataMap()
                                .getWrappedMap()
                );
                List<? extends Trigger> triggers = scheduler.getTriggersOfJob(jobKey);
                for (Trigger trigger : triggers) {
                    Map<String, Object> map = new HashMap<>();
                    map.put(
                            "jobName",
                            jobKey.getName()
                    );
                    map.put(
                            "jobGroupName",
                            jobKey.getGroup()
                    );
                    map.put(
                            "description",
                            "触发器:" + trigger.getKey()
                    );
                    Trigger.TriggerState triggerState = scheduler.getTriggerState(trigger.getKey());
                    map.put(
                            "jobStatus",
                            triggerState.name()
                    );
                    if (trigger instanceof CronTrigger) {
                        CronTrigger cronTrigger = (CronTrigger) trigger;
                        String cronExpression = cronTrigger.getCronExpression();
                        map.put(
                                "jobTime",
                                cronExpression
                        );
                    }
                    jobList.add(map);
                }
            }
        } catch (SchedulerException e) {
            e.printStackTrace();
            throw new MyRuntimeException("query all jobs error!");
        }
        return jobList;
    }

    @Override
    public List<JobDetail> queryAllScriptScheduleMission() {

        try {

            GroupMatcher<JobKey> matcher = GroupMatcher
                    .groupEquals(SCRIPT_SCHEDULE_MISSION_GROUP_NAME);

            return scheduler
                    .getJobKeys(
                            matcher
                    )
                    .stream()
                    .map(
                            jobKey -> {
                                try {
                                    return scheduler.getJobDetail(jobKey);
                                } catch (SchedulerException e) {
                                    throw new RuntimeException(e);
                                }
                            }
                    )
                    .collect(Collectors.toList());


        } catch (SchedulerException e) {
            throw new RuntimeException(e);
        }

    }

    @Override
    public boolean deleteScriptScheduleMission(String schedulerUuid) {

        // 直接构造
        JobKey jobKey = new JobKey(
                schedulerUuid,
                SCRIPT_SCHEDULE_MISSION_GROUP_NAME
        );

        try {

            return scheduler.deleteJob(jobKey);

        } catch (SchedulerException e) {
            throw new RuntimeException(e);
        }

    }

    @Override
    public JobDetail modifyScriptScheduleMission(ScriptSchedulerVO scriptSchedulerVO) {

        String uuid = scriptSchedulerVO.getSchedulerUuid();

        // 直接构造
        JobKey jobKey = new JobKey(
                uuid,
                SCRIPT_SCHEDULE_MISSION_GROUP_NAME
        );

        JobDetail jobDetail = null;
        try {
            jobDetail = scheduler.getJobDetail(jobKey);
        } catch (SchedulerException e) {
            // job 不存在或者什么信息
            return null;
        }
        // 该Job存在
        // 修改JobDetail信息
        ScriptSchedulerDTO scriptSchedulerDTO = validateAndConvertToScriptSchedulerDTO(scriptSchedulerVO);
        JobDataMap map = jobDetail.getJobDataMap();
        map.put(
                SCRIPT_SCHEDULER_MISSION_KEY,
                scriptSchedulerDTO
        );

        // 修改 Mission相应的 cron时间信息
        updateMission(
                uuid,
                SCRIPT_SCHEDULE_MISSION_GROUP_NAME,
                scriptSchedulerDTO.getCronExpress()
        );

        // 修改任务计划执行时间Key
        quartzSchedulerUtils.generateAndSetFutureExecutionResultKey(scriptSchedulerDTO);

        return jobDetail;
    }

    /**
     * 获取所有正在运行的job
     *
     * @return
     */
    @Override
    public List<Map<String, Object>> queryRunMission() {
        List<Map<String, Object>> jobList = null;
        try {
            List<JobExecutionContext> executingJobs = scheduler.getCurrentlyExecutingJobs();
            jobList = new ArrayList<Map<String, Object>>(executingJobs.size());
            for (JobExecutionContext executingJob : executingJobs) {
                Map<String, Object> map = new HashMap<String, Object>();
                JobDetail jobDetail = executingJob.getJobDetail();
                JobKey jobKey = jobDetail.getKey();
                Trigger trigger = executingJob.getTrigger();
                map.put(
                        "jobName",
                        jobKey.getName()
                );
                map.put(
                        "jobGroupName",
                        jobKey.getGroup()
                );
                map.put(
                        "description",
                        "触发器:" + trigger.getKey()
                );
                Trigger.TriggerState triggerState = scheduler.getTriggerState(trigger.getKey());
                map.put(
                        "jobStatus",
                        triggerState.name()
                );
                if (trigger instanceof CronTrigger) {
                    CronTrigger cronTrigger = (CronTrigger) trigger;
                    String cronExpression = cronTrigger.getCronExpression();
                    map.put(
                            "jobTime",
                            cronExpression
                    );
                }
                jobList.add(map);
            }
        } catch (SchedulerException e) {
            e.printStackTrace();
            throw new MyRuntimeException("query run jobs error!");
        }
        return jobList;
    }

    @Override
    public List<Trigger> queryAllTrigger() {

        try {

            return scheduler
                    .getTriggerKeys(
//                            GroupMatcher.groupEquals(JOB_GROUP_NAME)
                            GroupMatcher.anyGroup()
                    )
                    .stream()
                    .map(
                            triggerKey -> {
                                try {
                                    return scheduler.getTrigger(triggerKey);
                                } catch (SchedulerException e) {
                                    throw new RuntimeException(e);
                                }
                            }
                    )
                    .collect(Collectors.toList());

        } catch (SchedulerException e) {
            throw new RuntimeException(e);
        }
    }


}
