package com.jiayue.ipfcst.common.data.service;

import cn.hutool.core.date.DateUtil;
import com.jiayue.ipfcst.common.core.exception.BusinessException;
import com.jiayue.ipfcst.common.core.util.NumberUtils;
import com.jiayue.ipfcst.common.data.constant.enums.AlarmStatusEnum;
import com.jiayue.ipfcst.common.data.constant.enums.AlarmTypeEnum;
import com.jiayue.ipfcst.common.data.entity.*;
import com.jiayue.ipfcst.common.data.job.BaseJob;
import com.jiayue.ipfcst.common.data.repository.*;
import com.sun.istack.internal.NotNull;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.quartz.CronTriggerFactoryBean;
import org.springframework.scheduling.quartz.JobDetailFactoryBean;
import org.springframework.scheduling.quartz.SimpleTriggerFactoryBean;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.text.ParseException;
import java.util.*;

/**
 * 业务基础类
 *
 * @author zzy
 * @version 1.0
 * @since 2019/8/5 16:02
 */
@Service
@Slf4j
public class BaseService {

	private SysParameterRepository sysParameterRepository;

	private ElectricFieldRepository electricFieldRepository;

	private OverhaulPlanRepository overhaulPlanRepository;
	private OverhaulPlanOneRepository overhaulPlanOneRepository;
	private SysAlarmRepository sysAlarmRepository;

	private Scheduler scheduler;

	@Autowired
	public void setOverhaulPlanOneRepository(OverhaulPlanOneRepository overhaulPlanOneRepository) {
		this.overhaulPlanOneRepository = overhaulPlanOneRepository;
	}

	@Autowired
	public void setOverhaulPlanRepository(OverhaulPlanRepository overhaulPlanRepository) {
		this.overhaulPlanRepository = overhaulPlanRepository;
	}

	@Autowired
	public void setElectricFieldRepository(ElectricFieldRepository electricFieldRepository) {
		this.electricFieldRepository = electricFieldRepository;
	}

	@Autowired
	public void setSysParameterRepository(SysParameterRepository sysParameterRepository) {
		this.sysParameterRepository = sysParameterRepository;
	}

	@Autowired
	public void setSysAlarmRepository(SysAlarmRepository sysAlarmRepository) {
		this.sysAlarmRepository = sysAlarmRepository;
	}

	@Autowired
	public void setScheduler(Scheduler scheduler) {
		this.scheduler = scheduler;
	}

	/**
	 * 获取系统参数
	 * bzy 2020.04.13修改添加参数defaultValue，查询不到返回默认值。
	 *
	 * @param key 参数标识
	 * @return 参数值
	 */
	@Transactional(propagation = Propagation.NOT_SUPPORTED, readOnly = true)
	public String getSysParameter(String key,
	                              String defaultValue) {
		Optional<SysParameter> optional = sysParameterRepository.findBySysKey(key);
		return optional.map(SysParameter::getSysValue).orElse(defaultValue);
	}

	/**
	 * 查询场站信息
	 *
	 * @return 场站信息
	 */
	@Transactional(propagation = Propagation.NOT_SUPPORTED, readOnly = true)
	public ElectricField getElectricField() throws BusinessException {
		Optional<ElectricField> electricFieldOptional = Optional.ofNullable(this.electricFieldRepository.findByStationCodeIsNotNull());
		if (electricFieldOptional.isPresent()) {
			return electricFieldOptional.get();
		} else {
			log.error("场站信息未维护！！！");
			throw new BusinessException("场站信息未维护！！！");
		}
	}

	/**
	 * 查询开机容量
	 *
	 * @param startTime 开始时间
	 * @param endTime   结束时间
	 * @return 开始时间-结束时间段内，以开始时间作为第一个时刻标记时间，15分为一个时刻长度，返回开机容量Map集合 key为对应时刻标记时间
	 */
	public Map<Long, BigDecimal> queryOpenCapacity(Long startTime, Long endTime, BigDecimal capacity) throws BusinessException {
		// 查询检修计划
		List<OverhaulPlan> overhaulPlans = this.overhaulPlanRepository.findByEndTimeGreaterThanEqualAndStartTimeLessThanEqualAndStatus(startTime, endTime,1);

		Map<Long, BigDecimal> resultMap = new HashMap<>();
		BigDecimal openCapacity;
		Date tempTime = new Date(startTime);
		while (!tempTime.after(new Date(endTime))) {
			resultMap.put(tempTime.getTime(), capacity);
			for (OverhaulPlan overhaulPlan : overhaulPlans) {
				// 当查询时间在检修时间范围内，修正开机容量
				Date oStartTime = new Date(overhaulPlan.getStartTime());
				Date oEndTime1 = new Date(overhaulPlan.getEndTime());
				if (!tempTime.before(oStartTime) && !tempTime.after(oEndTime1)) {
					openCapacity = NumberUtils.subtract(capacity, overhaulPlan.getOverhaulCapactity(), 2);
					// 当同一时间段有多条检修纪录时，取检修容量最大值也就是开机容量最小值
					if (resultMap.get(tempTime.getTime()).compareTo(openCapacity) > 0) {
						resultMap.put(tempTime.getTime(), openCapacity);
					}
				}
			}
			tempTime = DateUtil.offsetMinute(tempTime, 15);
		}
		return resultMap;
	}

	/**
	 * 查询开机容量
	 *
	 * @param startTime 开始时间
	 * @param endTime   结束时间
	 * @return 开始时间-结束时间段内，以开始时间作为第一个时刻标记时间，15分为一个时刻长度，返回开机容量Map集合 key为对应时刻标记时间
	 */
	public Map<Long, BigDecimal> queryOpenCapacityOne(Long startTime, Long endTime, BigDecimal capacity) throws BusinessException {
		// 查询检修计划
		List<OverhaulPlanOne> overhaulPlans = this.overhaulPlanOneRepository.findByEndTimeGreaterThanEqualAndStartTimeLessThanEqualAndStatus(startTime, endTime,1);

		Map<Long, BigDecimal> resultMap = new HashMap<>();
		BigDecimal openCapacity;
		Date tempTime = new Date(startTime);
		while (!tempTime.after(new Date(endTime))) {
			resultMap.put(tempTime.getTime(), capacity);
			for (OverhaulPlanOne overhaulPlan : overhaulPlans) {
				// 当查询时间在检修时间范围内，修正开机容量
				Date oStartTime = new Date(overhaulPlan.getStartTime());
				Date oEndTime1 = new Date(overhaulPlan.getEndTime());
				if (!tempTime.before(oStartTime) && !tempTime.after(oEndTime1)) {
					openCapacity = NumberUtils.subtract(capacity, overhaulPlan.getOverhaulCapactity(), 2);
					// 当同一时间段有多条检修纪录时，取检修容量最大值也就是开机容量最小值
					if (resultMap.get(tempTime.getTime()).compareTo(openCapacity) > 0) {
						resultMap.put(tempTime.getTime(), openCapacity);
					}
				}
			}
			tempTime = DateUtil.offsetMinute(tempTime, 15);
		}
		return resultMap;
	}

	/**
	 * 创建或更新定时任务
	 *
	 * @param t                    定时任务执行类
	 * @param jobCode              任务标识(每个人任务标识为一)
	 * @param jobRunStartTime      任务执行开始时间，单位：毫秒
	 * @param jobRunRepeatInterval 任务重复执行间隔时间，单位：毫秒
	 * @param paramMap             任务参数
	 * @param <T>                  定时任务执行类必须继承BaseJob
	 * @throws SchedulerException 任务创建或更新异常
	 * @throws ParseException     cron表达式异常
	 */
	public <T extends BaseJob> void scheduleJob(@NotNull Class<T> t, @NotNull final String jobCode,
	                                            @NotNull final Long jobRunStartTime, @NotNull final Long jobRunRepeatInterval,
	                                            final Map<String, Object> paramMap) throws SchedulerException, ParseException {
		// 获取触发器
		SimpleTrigger simpleTrigger = this.getSimpleTrigger(jobCode, jobRunStartTime, jobRunRepeatInterval, paramMap);
		// 安排任务
		this.scheduleJob(t, simpleTrigger, jobCode);
		this.resumeJob(jobCode);// 启动任务
	}

	/**
	 * 创建或更新定时任务
	 *
	 * @param t               定时任务执行类
	 * @param jobCode         任务标识(每个人任务标识为一)
	 * @param jobRunStartTime 任务执行开始时间，单位：毫秒
	 * @param cronExpression  任务执行策略
	 * @param paramMap        任务参数
	 * @param <T>             定时任务执行类必须继承BaseJob
	 * @throws SchedulerException 任务创建或更新异常
	 * @throws ParseException     cron表达式异常
	 */
	public <T extends BaseJob> void scheduleJob(@NotNull Class<T> t, @NotNull final String jobCode,
	                                            @NotNull final Long jobRunStartTime, @NotNull final String cronExpression, final Map<String, Object> paramMap)
			throws SchedulerException, ParseException {
		// 获取触发器
		CronTrigger cronTrigger = this.getCronTrigger(jobCode, jobRunStartTime, cronExpression, paramMap);
		// 安排任务
		this.scheduleJob(t, cronTrigger, jobCode);
		this.resumeJob(jobCode);// 启动任务
	}

	/**
	 * 安排定时任务
	 *
	 * @param t       定时任务执行类
	 * @param e       定时器
	 * @param jobCode 任务标识
	 * @param <T>     定时器实现类
	 * @param <E>     触发器实现类
	 * @throws SchedulerException 安排定时任务异常
	 * @throws ParseException     cron表达式异常
	 */
	public <T extends BaseJob, E extends Trigger> void scheduleJob(@NotNull Class<T> t, @NonNull E e,
	                                                               @NotNull final String jobCode) throws SchedulerException, ParseException {
		// 如果触发器已存在，则重新安排该任务，否则将创建新的任务安排
		if (scheduler.checkExists(e.getKey())) {
			scheduler.rescheduleJob(e.getKey(), e);
		} else {
			JobDetailFactoryBean jobDetailFactoryBean = new JobDetailFactoryBean();
			jobDetailFactoryBean.setJobClass(t);
			jobDetailFactoryBean.setName(jobCode);
			jobDetailFactoryBean.afterPropertiesSet();
			JobDetail jobDetail = jobDetailFactoryBean.getObject();
			scheduler.scheduleJob(jobDetail, e);
		}
	}

	/**
	 * 删除任务
	 *
	 * @param jobCode 任务标识
	 * @throws SchedulerException 任务删除异常
	 */
	public void deleteJob(@NotNull final String jobCode) throws SchedulerException {
		JobKey jobKey = JobKey.jobKey(jobCode);
		// 如果任务存在，则删除
		if (scheduler.checkExists(jobKey))
			scheduler.deleteJob(jobKey);
	}

	/**
	 * 暂停任务
	 *
	 * @param jobCode 任务标识
	 * @throws SchedulerException 任务暂停异常
	 */
	public void pauseJob(@NotNull final String jobCode) throws SchedulerException {
		TriggerKey triggerKey = TriggerKey.triggerKey(jobCode);
		if (scheduler.checkExists(triggerKey))
			scheduler.pauseTrigger(triggerKey);
	}

	/**
	 * 重启任务
	 *
	 * @param jobCode 任务标识
	 * @throws SchedulerException 任务重启异常
	 */
	public void resumeJob(@NotNull final String jobCode) throws SchedulerException {
		TriggerKey triggerKey = TriggerKey.triggerKey(jobCode);
		if (scheduler.checkExists(triggerKey))
			scheduler.resumeTrigger(triggerKey);
	}

	/**
	 * 获取触发器
	 *
	 * @param jobCode              触发器标识
	 * @param jobRunStartTime      开始执行时间， 单位：毫秒
	 * @param jobRunRepeatInterval 重复执行间隔， 单位：毫秒
	 * @param paramMap             触发器参数
	 * @return 触发器
	 * @throws SchedulerException 获取触发器异常
	 */
	private SimpleTrigger getSimpleTrigger(@NotNull final String jobCode, @NotNull final Long jobRunStartTime,
	                                       @NotNull final Long jobRunRepeatInterval, final Map<String, Object> paramMap) throws SchedulerException {
		SimpleTriggerFactoryBean simpleTriggerFactoryBean = new SimpleTriggerFactoryBean();
		if (paramMap != null)
			simpleTriggerFactoryBean.setJobDataAsMap(paramMap);// 设置任务执行参数
		simpleTriggerFactoryBean.setStartTime(new Date(jobRunStartTime));// 开始执行时间
		simpleTriggerFactoryBean.setRepeatInterval(jobRunRepeatInterval);//重复执行间隔时间 单位毫秒
		simpleTriggerFactoryBean.setName(jobCode);
		simpleTriggerFactoryBean.setMisfireInstruction(
				SimpleTrigger.MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT);// 定时任务失火后，将忽略未执行任务，继续下一次任务
		simpleTriggerFactoryBean.afterPropertiesSet();
		return simpleTriggerFactoryBean.getObject();
	}

	/**
	 * 获取触发器
	 *
	 * @param jobCode         触发器标识
	 * @param jobRunStartTime 开始执行时间，单位：毫秒
	 * @param cronExpression  执行策略表达式
	 * @param paramMap        触发器参数
	 * @return 触发器
	 * @throws SchedulerException 获取触发器异常
	 * @throws ParseException     执行策略表达式异常
	 */
	private CronTrigger getCronTrigger(@NotNull final String jobCode, @NotNull final Long jobRunStartTime,
	                                   @NotNull final String cronExpression, final Map<String, Object> paramMap)
			throws SchedulerException, ParseException {
		CronTriggerFactoryBean cronTriggerFactoryBean = new CronTriggerFactoryBean();
		if (paramMap != null)
			cronTriggerFactoryBean.setJobDataAsMap(paramMap);// 设置任务执行参数
		cronTriggerFactoryBean.setStartTime(new Date(jobRunStartTime));// 开始执行时间
		cronTriggerFactoryBean.setCronExpression(cronExpression);// 执行策略
		cronTriggerFactoryBean.setName(jobCode);
		cronTriggerFactoryBean
				.setMisfireInstruction(CronTrigger.MISFIRE_INSTRUCTION_DO_NOTHING);// 定时任务失火后，目前不执行，然后就按照正常的计划执行。
		cronTriggerFactoryBean.afterPropertiesSet();
		return cronTriggerFactoryBean.getObject();
	}

	/**
	 * 获取任务最后执行时间
	 *
	 * @param jobCode 任务标识
	 * @return 任务最后执行时间
	 * @throws SchedulerException 获取任务最后执行时间异常
	 */
	public Date getJobPreviousFireTime(@NotNull final String jobCode) throws SchedulerException {
		TriggerKey triggerKey = new TriggerKey(jobCode);
		Trigger trigger = this.scheduler.getTrigger(triggerKey);
		if (trigger == null)
			throw new SchedulerException(jobCode + "任务不存在!");
		return trigger.getPreviousFireTime();
	}

	/**
	 * 获取任务下次执行时间
	 *
	 * @param jobCode 任务标识
	 * @return 任务下次执行时间
	 * @throws SchedulerException 获取任务下次执行时间异常
	 */
	Date getJobNextFireTime(@NotNull final String jobCode) throws SchedulerException {
		TriggerKey triggerKey = new TriggerKey(jobCode);
		Trigger trigger = this.scheduler.getTrigger(triggerKey);
		if (trigger == null)
			throw new SchedulerException(jobCode + "任务不存在!");
		return trigger.getNextFireTime();
	}

	/**
	 * 获取任务状态
	 *
	 * @param jobCode 任务标识
	 * @return 任务状态，中文描述
	 * @throws SchedulerException 获取任务状态异常
	 */
	public String getJobStatus(@NotNull final String jobCode) throws SchedulerException {
		TriggerKey triggerKey = new TriggerKey(jobCode);
		String triggerStateName = this.scheduler.getTriggerState(triggerKey).name();
		if ("NONE".equals(triggerStateName)) {// 不存在
			return "不存在";
		} else if ("NORMAL".equals(triggerStateName)) {// 正常
			return "正常";
		} else if ("PAUSED".equals(triggerStateName)) {// 暂停
			return "暂停";
		} else if ("COMPLETE".equals(triggerStateName)) {// 完成
			return "完成";
		} else if ("ERROR".equals(triggerStateName)) {// 错误
			return "错误";
		} else if ("BLOCKED".equals(triggerStateName)) {// 阻塞
			return "阻塞";
		}
		return triggerStateName;
	}

	/**
	 * 插入系统告警信息
	 *
	 * @param type      类型
	 * @param name      名称
	 * @param describe  描述
	 * @param errorInfo 错误日志
	 * @param solution  处理办法（插入的时候如果有处理建议，请给这个传值，比如（处理建议：联系运维人员处理））
	 */
	@Transactional(propagation = Propagation.REQUIRED)
	public void saveSysAlarm(@NotNull AlarmTypeEnum type,
	                         @NotNull String name, @NotNull String describe, String errorInfo, String solution) {
		SysAlarm sysAlarm = new SysAlarm();
		sysAlarm.setAlarmTypeEnum(type);
		sysAlarm.setName(name);
		sysAlarm.setDescribe(describe);
		sysAlarm.setAlarmStatusEnum(AlarmStatusEnum.E1);
		sysAlarm.setErrorInfo(errorInfo);
		sysAlarm.setSolution(solution);
		sysAlarmRepository.save(sysAlarm);
	}
}
