package com.artfess.job.persistence.manager;


import java.text.ParseException;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.Resource;

import com.artfess.base.context.BaseContext;
import org.apache.commons.lang.StringUtils;
import org.quartz.CalendarIntervalScheduleBuilder;
import org.quartz.CronScheduleBuilder;
import org.quartz.Job;
import org.quartz.JobBuilder;
import org.quartz.JobDataMap;
import org.quartz.JobDetail;
import org.quartz.JobKey;
import org.quartz.ScheduleBuilder;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SimpleTrigger;
import org.quartz.Trigger;
import org.quartz.Trigger.TriggerState;
import org.quartz.TriggerBuilder;
import org.quartz.TriggerKey;
import org.quartz.impl.matchers.GroupMatcher;
import org.springframework.stereotype.Service;

import com.fasterxml.jackson.core.type.TypeReference;
import com.artfess.base.model.CommonResult;
import com.artfess.base.util.JsonUtil;
import com.artfess.base.util.time.DateFormatUtil;
import com.artfess.job.model.ParameterObj;
import com.artfess.job.model.PlanObject;
import com.artfess.job.model.SchedulerVo;
import com.artfess.job.util.ConvertUtil;

/**
 * 定时框架。
 *
 * @author maoww
 * @company 阿特菲斯信息技术有限公司
 * @email maoww@jee-soft.cn
 * @date 2018年6月8日
 */
@Service
public class SchedulerService {
    @Resource
    Scheduler scheduler;

    private static HashMap<String, String> mapWeek;
    @Resource
    BaseContext baseContext;

    static {
        // "MON,TUE,WED,THU,FRI,SAT,SUN"
        mapWeek = new HashMap<String, String>();
        mapWeek.put("MON", "星期一");
        mapWeek.put("TUE", "星期二");
        mapWeek.put("WED", "星期三");
        mapWeek.put("THU", "星期四");
        mapWeek.put("FRI", "星期五");
        mapWeek.put("SAT", "星期六");
        mapWeek.put("SUN", "星期日");

    }


    /**
     * 添加任务
     *
     * @param jobName       任务名称
     * @param className     类名称
     * @param parameterJson 参数
     * @throws Exception
     */
    @SuppressWarnings("unchecked")
    public boolean addJob(String jobName, String className,
                          String parameterJson, String description)
            throws Exception {
        if (scheduler == null)
            return false;
        Class<Job> cls = (Class<Job>) Class.forName(className);
        JobBuilder jb = JobBuilder.newJob(cls);
        jb.withIdentity(jobName, baseContext.getCurrentTenantId());
        if (StringUtils.isNotEmpty(parameterJson))
            setJobMap(parameterJson, jb);
        jb.storeDurably();
        jb.withDescription(description);
        JobDetail jobDetail = jb.build();
        scheduler.addJob(jobDetail, true);
        return true;
    }

    /**
     * 添加任务
     *
     * @param jobName      任务名称
     * @param className    类的全路径
     * @param parameterMap 参数
     * @param description  任务描述
     * @throws SchedulerException
     * @throws ClassNotFoundException
     */
    @SuppressWarnings("unchecked")
    public CommonResult<String> addJob(String jobName, String className,
                                       Map<String, Object> parameterMap, String description)
            throws SchedulerException {
        if (scheduler == null)
            return new CommonResult<String>(false, "scheduler 没有配置!", null);
        boolean isJobExist = this.isJobExists(jobName);
        if (isJobExist) {
            return new CommonResult<String>(false, "任务已存在", null);
        }
        Class<Job> cls;
        try {
            cls = (Class<Job>) Class.forName(className);
        } catch (ClassNotFoundException e) {
            return new CommonResult<String>(false, "指定的任务类不存在，或者没有实现JOB接口", null);
        }
        try {
            return addJob(jobName, cls, parameterMap, description);
        } catch (Exception e) {
            return new CommonResult<String>(false, e.getMessage(), null);
        }
    }

    /**
     * 添加任务
     *
     * @param jobName      任务名称
     * @param cls          类
     * @param parameterMap 参数map
     * @param description  描述
     * @return
     */
    @SuppressWarnings({"unchecked"})
    public CommonResult<String> addJob(String jobName, Class<?> cls,
                                       Map<String, Object> parameterMap, String description) {
        if (scheduler == null)
            return new CommonResult<>(false, "scheduler 没有配置!", null);
        try {
            JobBuilder jb = JobBuilder.newJob((Class<? extends Job>) cls);
            jb.withIdentity(jobName, baseContext.getCurrentTenantId());
            if (parameterMap != null) {
                JobDataMap map = new JobDataMap();
                map.putAll(parameterMap);
                jb.usingJobData(map);
            }
            jb.storeDurably();
            jb.withDescription(description);
            JobDetail jobDetail = jb.build();
            scheduler.addJob(jobDetail, true);
            return new CommonResult<String>(true, "添加任务成功!", null);
        } catch (Exception e) {
            return new CommonResult<String>(false, "添加任务失敗!", null);
        }
    }

    /**
     * 添加非定时计划
     *
     * @param jobName  任务名
     * @param trigName 计划名
     * @throws SchedulerException
     * @throws ParseException
     */
    public void addSimpleTrigger(String jobName, String trigName) throws SchedulerException {
        if (scheduler == null)
            return;
        JobKey jobKey = new JobKey(jobName, baseContext.getCurrentTenantId());
        TriggerBuilder<Trigger> tb = TriggerBuilder.newTrigger();
        tb.withIdentity(trigName, baseContext.getCurrentTenantId()).startAt(new Date());
        tb.forJob(jobKey);
        SimpleTrigger trig = (SimpleTrigger) tb.build();
        scheduler.scheduleJob(trig);
    }

    /**
     * 判断任务是否存在
     *
     * @param jobName 任务名称
     * @throws SchedulerException
     * @return 返回判断结果
     */
    public boolean isJobExists(String jobName) throws SchedulerException {
        if (scheduler == null)
            return false;
        JobKey key = new JobKey(jobName, baseContext.getCurrentTenantId());
        return scheduler.checkExists(key);
    }

    /**
     * 取得任务列表
     *
     * @return 返回任务列表
     * @throws SchedulerException
     */
    public List<SchedulerVo> getJobList() throws SchedulerException {
        if (scheduler == null)
            return new ArrayList<SchedulerVo>();
        List<SchedulerVo> list = new ArrayList<SchedulerVo>();
        GroupMatcher<JobKey> matcher = GroupMatcher.groupEquals(baseContext.getCurrentTenantId());
        Set<JobKey> set = scheduler.getJobKeys(matcher);
        for (JobKey jobKey : set) {
            JobDetail detail = scheduler.getJobDetail(jobKey);

            list.add(ConvertUtil.toBean(detail));
        }
        return list;
    }

    public SchedulerVo getJobDetail(String key) throws Exception {
        JobKey jobKey = new JobKey(key, baseContext.getCurrentTenantId());
        JobDetail jobDetail = scheduler.getJobDetail(jobKey);
        return ConvertUtil.toJobBean(jobDetail);
    }

    /**
     * 根据任务名称获取触发器
     *
     * @param jobName 任务名称
     * @return 返回触发器列表
     * @throws SchedulerException
     */
    @SuppressWarnings("unchecked")
    public List<Trigger> getTriggersByJob(String jobName)
            throws SchedulerException {
        if (scheduler == null)
            return new ArrayList<Trigger>();
        JobKey key = new JobKey(jobName, baseContext.getCurrentTenantId());
        return (List<Trigger>) scheduler.getTriggersOfJob(key);
    }

    /**
     * 取得触发器的状态
     *
     * @param list 触发器
     * @throws SchedulerException
     * @return 触发器状态
     */
    public HashMap<String, TriggerState> getTriggerStatus(List<Trigger> list) throws SchedulerException {
        if (scheduler == null)
            return new HashMap<String, TriggerState>();
        HashMap<String, TriggerState> map = new HashMap<String, TriggerState>();
        for (Iterator<Trigger> it = list.iterator(); it.hasNext(); ) {
            Trigger trigger = it.next();
            TriggerKey key = trigger.getKey();
            TriggerState state = scheduler.getTriggerState(key);
            map.put(key.getName(), state);
        }
        return map;
    }

    /**
     * 判断计划是否存在
     *
     * @param trigName 触发器名称
     * @throws SchedulerException
     * @return 返回结果
     */
    public boolean isTriggerExists(String trigName) throws SchedulerException {
        if (scheduler == null)
            return false;
        TriggerKey triggerKey = new TriggerKey(trigName, baseContext.getCurrentTenantId());
        return scheduler.checkExists(triggerKey);
    }

    /**
     * 添加计划
     *
     * @param jobName  任务名
     * @param trigName 计划名
     * @param planJson 计划数据
     * @throws SchedulerException
     * @throws ParseException
     */
    public void addTrigger(String jobName, String trigName, String planJson)
            throws Exception {
        if (scheduler == null)
            return;
        JobKey jobKey = new JobKey(jobName, baseContext.getCurrentTenantId());
        TriggerBuilder<Trigger> tb = TriggerBuilder.newTrigger();
        tb.withIdentity(trigName, baseContext.getCurrentTenantId());
        // tb.withDescription(description);
        this.setTrigBuilder(planJson, tb);
        tb.forJob(jobKey);
        Trigger trig = tb.build();
        scheduler.scheduleJob(trig);
    }

    /**
     * 添加触发器(计划)
     *
     * @param jobName  计划名称
     * @param trigName 触发器名称
     * @param minute   触发次数
     * @throws SchedulerException
     */
    public void addTrigger(String jobName, String trigName, int minute)
            throws SchedulerException {
        if (scheduler == null)
            return;
        JobKey jobKey = new JobKey(jobName, baseContext.getCurrentTenantId());
        TriggerBuilder<Trigger> tb = TriggerBuilder.newTrigger();
        tb.withIdentity(trigName, baseContext.getCurrentTenantId());
        ScheduleBuilder<?> sb = CalendarIntervalScheduleBuilder
                .calendarIntervalSchedule().withIntervalInMinutes(minute);
        tb.startNow().withSchedule(sb).withDescription("每:" + minute + "分钟执行!")
                .forJob(jobKey);
        Trigger trig = tb.build();
        scheduler.scheduleJob(trig);
    }

    /**
     * 添加触发器(计划)
     *
     * @param jobName  计划名称
     * @param trigName 触发器名称
     * @param rate     频率
     * @param rateUnit 频率类型（s：秒，m:分钟，h:小时，d:天，w:周，M:月，Y：年）
     * @throws SchedulerException
     */
    public void addTrigger(String jobName, String trigName, int rate, String rateUnit)
            throws SchedulerException {
        if (scheduler == null)
            return;
        JobKey jobKey = new JobKey(jobName, baseContext.getCurrentTenantId());
        TriggerBuilder<Trigger> tb = TriggerBuilder.newTrigger();
        tb.withIdentity(trigName, baseContext.getCurrentTenantId());
        ScheduleBuilder<?> sb = null;
        if (rateUnit.equals("s")) {
            sb = CalendarIntervalScheduleBuilder.calendarIntervalSchedule().withIntervalInSeconds(rate);
            tb.startNow().withSchedule(sb).withDescription("每:" + rate + "秒执行!").forJob(jobKey);
        } else if (rateUnit.equals("m")) {
            sb = CalendarIntervalScheduleBuilder.calendarIntervalSchedule().withIntervalInMinutes(rate);
            tb.startNow().withSchedule(sb).withDescription("每:" + rate + "分钟执行!").forJob(jobKey);
        } else if (rateUnit.equals("h")) {
            sb = CalendarIntervalScheduleBuilder.calendarIntervalSchedule().withIntervalInHours(rate);
            tb.startNow().withSchedule(sb).withDescription("每:" + rate + "小时执行!").forJob(jobKey);
        } else if (rateUnit.equals("d")) {
            sb = CalendarIntervalScheduleBuilder.calendarIntervalSchedule().withIntervalInDays(rate);
            tb.startNow().withSchedule(sb).withDescription("每:" + rate + "天执行!").forJob(jobKey);
        } else if (rateUnit.equals("w")) {
            sb = CalendarIntervalScheduleBuilder.calendarIntervalSchedule().withIntervalInWeeks(rate);
            tb.startNow().withSchedule(sb).withDescription("每:" + rate + "周执行!").forJob(jobKey);
        } else if (rateUnit.equals("M")) {
            sb = CalendarIntervalScheduleBuilder.calendarIntervalSchedule().withIntervalInMonths(rate);
            tb.startNow().withSchedule(sb).withDescription("每:" + rate + "月执行!").forJob(jobKey);
        } else if (rateUnit.equals("Y")) {
            sb = CalendarIntervalScheduleBuilder.calendarIntervalSchedule().withIntervalInYears(rate);
            tb.startNow().withSchedule(sb).withDescription("每:" + rate + "年执行!").forJob(jobKey);
        }
        Trigger trig = tb.build();
        scheduler.scheduleJob(trig);
    }

    private void setTrigBuilder(String planJson, TriggerBuilder<Trigger> tb)
            throws Exception {
        PlanObject planObject = JsonUtil.toBean(planJson, PlanObject.class);
        int type = planObject.getType();
        String value = planObject.getTimeInterval();
        ScheduleBuilder<?> sb = null;
        switch (type) {
            // 启动一次
            case PlanObject.TYPE_FIRST:
                LocalDateTime lt = DateFormatUtil.parseDateTime(value);
                ZoneId zoneId = ZoneId.systemDefault();
                ZonedDateTime zdt = lt.atZone(zoneId);
                Date date = Date.from(zdt.toInstant());
                tb.startAt(date).withDescription("执行一次,执行时间:" + date.toLocaleString());
                break;
            // 每分钟执行
            case PlanObject.TYPE_PER_MINUTE:
                int minute = Integer.parseInt(value);
                sb = CalendarIntervalScheduleBuilder.calendarIntervalSchedule()
                        .withIntervalInMinutes(minute);
                tb.startNow().withSchedule(sb)
                        .withDescription("每:" + minute + "分钟执行!");
                break;
            // 每天时间点执行
            case PlanObject.TYPE_PER_DAY:
                String[] aryTime = value.split(":");
                int hour = Integer.parseInt(aryTime[0]);
                int m = Integer.parseInt(aryTime[1]);
                sb = CronScheduleBuilder.dailyAtHourAndMinute(hour, m);
                tb.startNow().withSchedule(sb)
                        .withDescription("每天：" + hour + ":" + m + "执行!");
                break;
            // 每周时间点执行
            case PlanObject.TYPE_PER_WEEK:
                // 0 15 10 ? * MON-FRI Fire at 10:15am every Monday, Tuesday,
                // Wednesday, Thursday and Friday
                String[] aryExpression = value.split("[|]");
                String week = aryExpression[0];
                String[] aryTime1 = aryExpression[1].split(":");
                String h1 = aryTime1[0];
                String m1 = aryTime1[1];
                String cronExperssion = "0 " + m1 + " " + h1 + " ? * " + week;
                sb = CronScheduleBuilder.cronSchedule(cronExperssion);
                tb.startNow()
                        .withSchedule(sb)
                        .withDescription(
                                "每周：" + getWeek(week) + "," + h1 + ":" + m1 + "执行!");
                break;
            // 每月执行
            case PlanObject.TYPE_PER_MONTH:
                // 0 15 10 15 * ?
                String[] aryExpression5 = value.split("[|]");
                String day = aryExpression5[0];
                String[] aryTime2 = aryExpression5[1].split(":");
                String h2 = aryTime2[0];
                String m2 = aryTime2[1];
                String cronExperssion1 = "0 " + m2 + " " + h2 + " " + day + " * ?";
                sb = CronScheduleBuilder.cronSchedule(cronExperssion1);
                tb.startNow()
                        .withSchedule(sb)
                        .withDescription(
                                "每月:" + getDay(day) + "," + h2 + ":" + m2 + "执行!");
                break;
            // 表达式
            case PlanObject.TYPE_CRON:
                try {
                    sb = CronScheduleBuilder.cronSchedule(value);
                    tb.startNow().withSchedule(sb);
                } catch (RuntimeException e) {
                    // MessageUtil.addMsg("CronTrigger 表达式异常:" + e.getMessage());
                    throw e;
                }
                tb.withDescription("CronTrigger表达式:" + value);
                break;
        }
    }

    private String getDay(String day) {
        String[] aryDay = day.split(",");
        int len = aryDay.length;
        String str = "";
        for (int i = 0; i < len; i++) {
            String tmp = aryDay[i];
            tmp = tmp.equals("L") ? "最后一天" : tmp;
            if (i < len - 1) {
                str += tmp + ",";
            } else {
                str += tmp;
            }
        }
        return str;
    }

    /**
     * 取得星期名称
     *
     * @param week 星期
     * @return 返回名称
     */
    private String getWeek(String week) {
        String[] aryWeek = week.split(",");
        int len = aryWeek.length;
        String str = "";
        for (int i = 0; i < len; i++) {
            if (i < len - 1)
                str += mapWeek.get(aryWeek[i]) + ",";
            else
                str += mapWeek.get(aryWeek[i]);
        }
        return str;
    }

    /**
     * 设置任务参数
     *
     * @param json ParameterObj的Json
     * @param job  任务
     * @throws Exception
     */
    private void setJobMap(String json, JobBuilder job) throws Exception {
        List<ParameterObj> list = JsonUtil.toBean(json, new TypeReference<List<ParameterObj>>() {
        });
        for (ParameterObj obj : list) {
            String type = obj.getType();
            String name = obj.getName();
            String value = obj.getValue();
            if (type.equals(ParameterObj.TYPE_INT)) {
                Integer val = StringUtils.isEmpty(value) ? 0 : Integer
                        .parseInt(value);
                job.usingJobData(name, val);
            } else if (type.equals(ParameterObj.TYPE_LONG)) {
                Long val = StringUtils.isEmpty(value) ? 0L : Long
                        .parseLong(value);
                job.usingJobData(name, val);
            } else if (type.equals(ParameterObj.TYPE_FLOAT)) {
                Float val = StringUtils.isEmpty(value) ? 0.0f : Float
                        .parseFloat(value);
                job.usingJobData(name, val);
            } else if (type.equals(ParameterObj.TYPE_BOOLEAN)) {
                Boolean val = StringUtils.isEmpty(value) ? false : Boolean
                        .parseBoolean(value);
                job.usingJobData(name, val);
            } else {
                job.usingJobData(name, value);
            }
        }
    }

    /**
     * 删除任务
     *
     * @param jobName 任务名称
     * @throws SchedulerException
     * @throws ClassNotFoundException
     */
    public void delJob(String jobName) throws SchedulerException {
        if (scheduler == null)
            return;
        JobKey key = new JobKey(jobName, baseContext.getCurrentTenantId());
        scheduler.deleteJob(key);
    }

    /**
     * 通过触发名称获得触发器
     *
     * @param triggerName 触发名称
     * @throws SchedulerException
     * @return 返回触发器
     */
    public Trigger getTrigger(String triggerName) throws SchedulerException {
        if (scheduler == null)
            return null;
        TriggerKey key = new TriggerKey(triggerName, baseContext.getCurrentTenantId());
        return scheduler.getTrigger(key);
    }

    /**
     * 删除计划
     *
     * @param triggerName 触发器名称
     * @throws SchedulerException
     * @throws ClassNotFoundException
     */
    public void delTrigger(String triggerName) throws SchedulerException {
        if (scheduler == null)
            return;
        TriggerKey key = new TriggerKey(triggerName, baseContext.getCurrentTenantId());
        scheduler.unscheduleJob(key);
    }

    /**
     * 停止或暂停触发器
     *
     * @param triggerName 触发器名称
     * @throws SchedulerException
     */
    public void toggleTriggerRun(String triggerName) throws SchedulerException {
        if (scheduler == null)
            return;
        TriggerKey key = new TriggerKey(triggerName, baseContext.getCurrentTenantId());
        TriggerState state = scheduler.getTriggerState(key);
        if (state == TriggerState.PAUSED) {
            scheduler.resumeTrigger(key);
        } else if (state == TriggerState.NORMAL) {
            scheduler.pauseTrigger(key);
        }
    }

    public TriggerState getTriggerState(String triggerName) throws SchedulerException {
        if (scheduler == null)
            return null;
        TriggerKey key = new TriggerKey(triggerName, baseContext.getCurrentTenantId());
        TriggerState state = scheduler.getTriggerState(key);
        return state;
    }

    public void resumeTrigger(String triggerName) throws SchedulerException {
        if (scheduler == null)
            return;
        TriggerKey key = new TriggerKey(triggerName, baseContext.getCurrentTenantId());
        TriggerState state = scheduler.getTriggerState(key);
        if (state == TriggerState.PAUSED) {
            scheduler.resumeTrigger(key);
        }
    }

    public void pauseTrigger(String triggerName) throws SchedulerException {
        if (scheduler == null)
            return;
        TriggerKey key = new TriggerKey(triggerName, baseContext.getCurrentTenantId());
        TriggerState state = scheduler.getTriggerState(key);
        if (state == TriggerState.NORMAL) {
            scheduler.pauseTrigger(key);
        }
    }

    /**
     * 直接执行任务
     *
     * @param jobName 任务名称
     * @throws SchedulerException
     */
    public void executeJob(String jobName) throws SchedulerException {
        if (scheduler == null)
            return;
        JobKey key = new JobKey(jobName, baseContext.getCurrentTenantId());
        scheduler.triggerJob(key);
    }

    /**
     * 启动
     *
     * @throws SchedulerException
     */
    public void start() throws SchedulerException {
        scheduler.start();
    }

    /**
     * 关闭
     *
     * @throws SchedulerException
     */
    public void shutdown() throws SchedulerException {
        scheduler.standby();
    }

    /**
     * 是否启动
     *
     * @return 返回结果
     * @throws SchedulerException
     */
    public boolean isStarted() throws SchedulerException {
        return scheduler.isStarted();
    }

    /**
     * 是否挂起
     *
     * @return 返回挂起结果
     * @throws SchedulerException
     */
    public boolean isInStandbyMode() throws SchedulerException {
        return scheduler.isInStandbyMode();
    }
}
