package link.at17.mid.tushare.service;

import java.lang.reflect.InvocationTargetException;
import java.util.List;
import java.util.Set;

import org.quartz.CronScheduleBuilder;
import org.quartz.CronTrigger;
import org.quartz.JobBuilder;
import org.quartz.JobDataMap;
import org.quartz.JobDetail;
import org.quartz.JobExecutionContext;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.TriggerBuilder;
import org.quartz.TriggerKey;
import org.quartz.impl.matchers.GroupMatcher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;

import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.Valid;
import jakarta.validation.Validator;
import link.at17.mid.tushare.dao.UpdatePlanDao;
import link.at17.mid.tushare.data.models.UpdateMethodInfo;
import link.at17.mid.tushare.data.models.UpdatePlan;
import link.at17.mid.tushare.task.job.UpdatePlanJob;
import lombok.extern.slf4j.Slf4j;

@Service
@Lazy(false)
@DependsOn({"updateMethodService", "updateMethodInfoValidator"})
@Slf4j
@Validated
public class UpdatePlanService extends BaseServiceImpl<UpdatePlanDao, UpdatePlan> {
    
    public static final String JOB_GROUP_NAME = "UpdatePlanGroup";
    
    @Resource(name="scheduler")
    Scheduler scheduler;
    
    @Autowired
    UpdateMethodService updateMethodService;
    
    @Autowired
    Validator validator;
    
    @PostConstruct
    void postConstruct() throws SchedulerException {
        // 从数据库加载所有已保存 UpdatePlan 并逐一判断其有效性
        List<UpdatePlan> updatePlans = list();
        for (UpdatePlan updatePlan : updatePlans) {
            Set<ConstraintViolation<Object>> v = validator.validate(updatePlan);
            if (!v.isEmpty()) {
                v.forEach(violation -> {
                    log.error("UpdatePlan {} 发生错误: {}", updatePlan.getName(), violation.getMessage());
                });
                // 设置 UpdatePlan 为无效
                updatePlan.setValid(false);
                saveOrUpdate(updatePlan);
            }
        }
        updatePlans = list();
        updatePlans.forEach(updatePlan -> {
            try {
                rescheduleTask(updatePlan);
            } catch (SchedulerException e) {
                log.error("从 UpdatePlan 数据表编排任务失败，id = {}, name = {}", updatePlan.getId(), updatePlan.getName(), e);
            }
        });
    }

    /**
     * 删除指定的任务
     * @param updatePlan
     * @throws SchedulerException
     */
    public void deleteTask(UpdatePlan updatePlan) throws SchedulerException {
        deleteTask(updatePlan.getId());
    }
    
    /**
     * 删除指定的任务
     * @param id UpdatePlan::getId
     * @throws SchedulerException
     */
    public void deleteTask(Integer id) throws SchedulerException {
        JobKey jobKey = JobKey.jobKey(id.toString(), JOB_GROUP_NAME);
        if (scheduler.checkExists(jobKey)) {
            scheduler.deleteJob(jobKey);
        }
    }
    
    /**
     * 根据 UpdatePlan 实例新建或更新任务
     * @param updatePlan
     * @throws SchedulerException
     */
    public void rescheduleTask(@Validated UpdatePlan updatePlan) throws SchedulerException {
        JobKey jobKey = getJobKey(updatePlan);
        TriggerKey triggerKey = getTriggerKey(updatePlan);
        
        deleteTask(updatePlan.getId());
        
        if (!updatePlan.getEnabled() || !updatePlan.getValid()) {
            return;
        }
        
        JobDataMap jobData = new JobDataMap();
        jobData.put("updatePlan", updatePlan);
        
        JobDetail job = JobBuilder.newJob(UpdatePlanJob.class)
                .withIdentity(jobKey)
                .usingJobData(jobData)
                .build();
        
        CronTrigger trigger = TriggerBuilder.newTrigger()
                .withIdentity(triggerKey)
                .withSchedule(CronScheduleBuilder.cronSchedule(updatePlan.getCronExpr()))
                .build();
        
        scheduler.scheduleJob(job, trigger);
        log.debug("已(重新)编排任务 [{}]{}, cronExpr = \"{}\"",
                updatePlan.getId(), updatePlan.getName(), updatePlan.getCronExpr());
        
        log.debug("当前所有任务：");
        Set<JobKey> jobKeys = scheduler.getJobKeys(GroupMatcher.groupEquals(JOB_GROUP_NAME));
        jobKeys.forEach(jk -> {
            UpdatePlan relative = getById(Integer.valueOf(jk.getName()));
            log.debug("UpdatePlan [{}]{}, cronExpr = \"{}\"",
                    relative.getId(), relative.getName(), relative.getCronExpr());
        });
    }
    
    /**
     * 立即触发任务
     * @param updatePlan
     */
    public void triggerTask(@Valid UpdatePlan updatePlan) throws SchedulerException {
        JobKey jobKey = getJobKey(updatePlan);
        for (JobExecutionContext context : scheduler.getCurrentlyExecutingJobs()) {
            if (context.getJobDetail().getKey().equals(jobKey)) {
                throw new IllegalStateException("已有同名任务在运行中");
            }
        }
        if (scheduler.getJobDetail(jobKey) == null) {
            throw new SchedulerException("指定的任务不存在");
        }
        scheduler.triggerJob(jobKey);
    }
    
    public void interruptTask(@Valid UpdatePlan updatePlan) throws SchedulerException {
        JobKey jobKey = getJobKey(updatePlan);
        for (JobExecutionContext context : scheduler.getCurrentlyExecutingJobs()) {
            if (context.getJobDetail().getKey().equals(jobKey)) {
                scheduler.interrupt(jobKey);
            }
        }
        throw new SchedulerException("任务未在执行");
    }
    
    /**
     * 执行方法
     * @param updatePlan
     * @throws ClassNotFoundException
     * @throws NoSuchMethodException
     * @throws SecurityException
     * @throws IllegalAccessException
     * @throws InvocationTargetException
     */
    public void execute(@Valid UpdatePlan updatePlan) throws ClassNotFoundException, NoSuchMethodException, SecurityException, IllegalAccessException, InvocationTargetException {
        for (UpdateMethodInfo info : updatePlan.getMethods()) {
            updateMethodService.execute(info);
        }
    }
    
    @Override
    public List<UpdatePlan> list() {
        return list(new LambdaQueryWrapper<UpdatePlan>().orderByAsc(UpdatePlan::getId));
    }

    /**
     * 根据 UpdatePlan 生成 JobKey
     * @param updatePlan
     * @return
     */
    private JobKey getJobKey(UpdatePlan updatePlan) {
        return JobKey.jobKey(updatePlan.getId().toString(), JOB_GROUP_NAME);
    }
    
    /**
     * 根据 UpdatePlan 生成 TriggerKey
     * @param updatePlan
     * @return
     */
    private TriggerKey getTriggerKey(UpdatePlan updatePlan) {
        return TriggerKey.triggerKey(updatePlan.getId().toString(), JOB_GROUP_NAME);
    }
}
