package cn.aagro.pp.module.iot.framework.job.core;

import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.springframework.context.ApplicationContext;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.scheduling.quartz.SpringBeanJobFactory;

import javax.sql.DataSource;
import java.util.Map;
import java.util.Properties;

/**
 * IoT 模块的 Scheduler 管理类，基于 Quartz 实现
 *
 * 疑问：为什么 IoT 模块不复用全局的 SchedulerManager 呢？
 * 回复：aagro-cloud 项目，使用的是 XXL-Job 作为调度中心，无法动态添加任务。
 *
 * @author 芋道源码
 */
@Slf4j
public class IotSchedulerManager {

    private static final String SCHEDULER_NAME = "iotScheduler";

    private final SchedulerFactoryBean schedulerFactoryBean;

    private Scheduler scheduler;

    public IotSchedulerManager(DataSource dataSource,
                               ApplicationContext applicationContext) {
        // 1. 参考 SchedulerFactoryBean 类
        SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
        SpringBeanJobFactory jobFactory = new SpringBeanJobFactory();
        jobFactory.setApplicationContext(applicationContext);
        schedulerFactoryBean.setJobFactory(jobFactory);
        schedulerFactoryBean.setAutoStartup(true);
        schedulerFactoryBean.setSchedulerName(SCHEDULER_NAME);
        schedulerFactoryBean.setDataSource(dataSource);
        schedulerFactoryBean.setWaitForJobsToCompleteOnShutdown(true);
        Properties properties = new Properties();
        schedulerFactoryBean.setQuartzProperties(properties);
        // 2. 参考 application-local.yaml 配置文件
        // 2.1 Scheduler 相关配置
        properties.put("org.quartz.scheduler.instanceName", SCHEDULER_NAME);
        properties.put("org.quartz.scheduler.instanceId", "AUTO");
        // 2.2 JobStore 相关配置
        properties.put("org.quartz.jobStore.class", "org.springframework.scheduling.quartz.LocalDataSourceJobStore");
        properties.put("org.quartz.jobStore.isClustered", "true");
        properties.put("org.quartz.jobStore.clusterCheckinInterval", "15000");
        properties.put("org.quartz.jobStore.misfireThreshold", "60000");
        // 2.3 线程池相关配置
        properties.put("org.quartz.threadPool.threadCount", "25");
        properties.put("org.quartz.threadPool.threadPriority", "5");
        properties.put("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool");
        this.schedulerFactoryBean = schedulerFactoryBean;
    }

    public void start() throws Exception {
        log.info("[start][Scheduler 初始化开始]");
        // 初始化
        schedulerFactoryBean.afterPropertiesSet();
        schedulerFactoryBean.start();
        // 获得 Scheduler 对象
        this.scheduler = schedulerFactoryBean.getScheduler();
        log.info("[start][Scheduler 初始化完成]");
    }

    public void stop() {
        log.info("[stop][Scheduler 关闭开始]");
        schedulerFactoryBean.stop();
        this.scheduler = null;
        log.info("[stop][Scheduler 关闭完成]");
    }

    // ========== 参考 SchedulerManager 实现 ==========

    /**
     * 添加或更新 Job 到 Quartz 中
     *
     * @param jobClass 任务处理器的类
     * @param jobName 任务名
     * @param cronExpression CRON 表达式
     * @param jobDataMap 任务数据
     * @throws SchedulerException 添加异常
     */
    public void addOrUpdateJob(Class <? extends Job> jobClass, String jobName,
                               String cronExpression, Map<String, Object> jobDataMap)
            throws SchedulerException {
        if (scheduler.checkExists(new JobKey(jobName))) {
            this.updateJob(jobName, cronExpression);
        } else {
            this.addJob(jobClass, jobName, cronExpression, jobDataMap);
        }
    }

    /**
     * 添加 Job 到 Quartz 中
     *
     * @param jobClass 任务处理器的类
     * @param jobName 任务名
     * @param cronExpression CRON 表达式
     * @param jobDataMap 任务数据
     * @throws SchedulerException 添加异常
     */
    public void addJob(Class <? extends Job> jobClass, String jobName,
                       String cronExpression, Map<String, Object> jobDataMap)
            throws SchedulerException {
        // 创建 JobDetail 对象
        JobDetail jobDetail = JobBuilder.newJob(jobClass)
                .usingJobData(new JobDataMap(jobDataMap))
                .withIdentity(jobName).build();
        // 创建 Trigger 对象
        Trigger trigger = this.buildTrigger(jobName, cronExpression);
        // 新增 Job 调度
        scheduler.scheduleJob(jobDetail, trigger);
    }

    /**
     * 更新 Job 到 Quartz
     *
     * @param jobName 任务名
     * @param cronExpression CRON 表达式
     * @throws SchedulerException 更新异常
     */
    public void updateJob(String jobName, String cronExpression)
            throws SchedulerException {
        // 创建新 Trigger 对象
        Trigger newTrigger = this.buildTrigger(jobName, cronExpression);
        // 修改调度
        scheduler.rescheduleJob(new TriggerKey(jobName), newTrigger);
    }

    /**
     * 删除 Quartz 中的 Job
     *
     * @param jobName 任务名
     * @throws SchedulerException 删除异常
     */
    public void deleteJob(String jobName) throws SchedulerException {
        // 暂停 Trigger 对象
        scheduler.pauseTrigger(new TriggerKey(jobName));
        // 取消并删除 Job 调度
        scheduler.unscheduleJob(new TriggerKey(jobName));
        scheduler.deleteJob(new JobKey(jobName));
    }

    /**
     * 暂停 Quartz 中的 Job
     *
     * @param jobName 任务名
     * @throws SchedulerException 暂停异常
     */
    public void pauseJob(String jobName) throws SchedulerException {
        scheduler.pauseJob(new JobKey(jobName));
    }

    /**
     * 启动 Quartz 中的 Job
     *
     * @param jobName 任务名
     * @throws SchedulerException 启动异常
     */
    public void resumeJob(String jobName) throws SchedulerException {
        scheduler.resumeJob(new JobKey(jobName));
        scheduler.resumeTrigger(new TriggerKey(jobName));
    }

    /**
     * 立即触发一次 Quartz 中的 Job
     *
     * @param jobName 任务名
     * @throws SchedulerException 触发异常
     */
    public void triggerJob(String jobName) throws SchedulerException {
        scheduler.triggerJob(new JobKey(jobName));
    }

    private Trigger buildTrigger(String jobName, String cronExpression) {
        return TriggerBuilder.newTrigger()
                .withIdentity(jobName)
                .withSchedule(CronScheduleBuilder.cronSchedule(cronExpression))
                .build();
    }

}
