/**
 * 
 */
package org.springblade.etl.controller;

import java.time.LocalDateTime;
import java.util.Date;
import java.util.Optional;

import org.springblade.core.boot.ctrl.BladeController;
import org.springblade.core.log.annotation.ApiLog;
import org.springblade.core.mp.support.Condition;
import org.springblade.core.mp.support.Query;
import org.springblade.core.secure.utils.AuthUtil;
import org.springblade.core.tool.api.R;
import org.springblade.core.tool.exception.ServiceException;
import org.springblade.etl.dto.SqlJobDto;
import org.springblade.etl.entity.SqlJob;
import org.springblade.etl.entity.SqlJobInstance;
import org.springblade.etl.entity.SqlJobScript;
import org.springblade.etl.enums.InstanceRunType;
import org.springblade.etl.enums.InstanceStatus;
import org.springblade.etl.enums.JobStatus;
import org.springblade.etl.service.SqlJobInstanceService;
import org.springblade.etl.service.SqlJobScriptService;
import org.springblade.etl.service.SqlJobService;
import org.springblade.system.client.DataPermClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.xxl.job.core.biz.AdminBiz;
import com.xxl.job.core.biz.model.JobInfo;
import com.xxl.job.core.biz.model.ReturnT;

import cn.hutool.core.util.StrUtil;
import cn.hutool.cron.pattern.parser.PatternParser;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;

/**
 *	SQL任务 操作处理
 *
 * @author aokunsang
 * @date   2023-12-03
 */
@Slf4j
@RestController
@RequestMapping("/sqljob")
@Api(value = "SQL任务相关接口", tags = "SQL任务接口")
public class SqlJobController extends BladeController {

	@Autowired
	private SqlJobService sqlJobService;
	
	@Autowired
	private SqlJobScriptService sqlJobScriptService;
	
	@Autowired
	private SqlJobInstanceService sqlJobInstanceService;
	
	@Autowired
	private AdminBiz adminBiz;
	
	@Autowired
	private DataPermClient dataPermClient;
	
	@Value("${xxl.job.executor.groupid}")
    private int groupId;
	
	/**
	 * 	列表数据
	 * @param sqlJob
	 * @return
	 */
	@GetMapping("/page")
	@ApiOperation(value = "分页查询SQL任务", httpMethod = "GET")
	public R<IPage<SqlJob>> pageList(SqlJob sqlJob, Query query){
		Long userId = getUser().getUserId();
		LambdaQueryWrapper<SqlJob> queryWrapper = Condition.getQueryWrapper(sqlJob).lambda().orderByDesc(SqlJob::getId);
		Boolean checkSuperPermRet = dataPermClient.checkSuperPermission(getUser().getUserId());
		if(checkSuperPermRet == null || !checkSuperPermRet) {
			queryWrapper.and(t ->{
				t.eq(SqlJob::getCreateUserId, userId)
				.or().inSql(SqlJob::getId, "select a.job_id from sql_job_permission a inner join data_permission_user b on a.data_perm_id = b.data_perm_id where b.user_id = " + userId)
				.or().eq(SqlJob::getPublicStatus, 1);
			});
		}
		return R.data(sqlJobService.page(Condition.getPage(query), queryWrapper));
	}
	
	/**
	 * 单条数据查询接口
	 * @param id
	 * @return
	 */
	@GetMapping("/info")
	@ApiOperation(value = "根据ID获取SQL任务", httpMethod = "GET")
	public R<SqlJobDto> info(@RequestParam Integer id) {
		SqlJob sqlJob = sqlJobService.getById(id);
		SqlJobDto sqlJobDto = new SqlJobDto();
		sqlJobDto.setJob(sqlJob);
		sqlJobDto.setSqlScriptList(sqlJobScriptService.list(Wrappers.<SqlJobScript>lambdaQuery().eq(SqlJobScript::getJobId, id)));
		return R.data(sqlJobDto);
	}
	
	/**
	 *	修改数据接口
	 * @param sqlJobDto
	 * @return
	 */
	@ApiLog("SQL任务")
	@PostMapping("/submit")
	@ApiOperation(value = "增加或修改SQL任务", httpMethod = "POST")
	public R<Integer> doSubmit(@Validated @RequestBody SqlJobDto sqlJobDto) {
		JobStatus jobStatus = sqlJobDto.getJob().getJobStatus();
		Integer jobId = sqlJobService.doSaveOrUpdate(sqlJobDto);
		String suffixMsg = "", errMsg = "";
		if(jobStatus == JobStatus.ONLINE_RUN) { //保存并运行
			try {
				sqlJobService.jobRun(jobId, InstanceRunType.MANUAL);
				suffixMsg = ",并且已提交执行";
			} catch (Exception e) {
				log.error(e.getMessage(), e);
				errMsg = e.getMessage();
				suffixMsg = ",但是提交执行失败";
			}
		}
		R<Integer> ret = R.data(jobId, "任务保存成功"+suffixMsg);
		ret.setExt(errMsg);
		return ret;
	}
	
	/**
	 * 运行任务
	 * @param jobId
	 * @return
	 */
	@ApiLog("SQL任务")
	@PostMapping("/run/{jobId}")
	@ApiOperation(value = "运行任务", httpMethod = "POST")
	public R<Boolean> run(@PathVariable Integer jobId) {
		sqlJobService.jobRun(jobId, InstanceRunType.MANUAL);
		return R.success("任务已提交执行");
	}
	
	/**
	 * 	删除数据接口
	 * @param id
	 * @return
	 */
	@PostMapping("/delete")
	@ApiOperation(value = "根据ID删除SQL任务", httpMethod = "POST")
	public R<String> doDelete(@RequestParam Integer id) {
		SqlJob dbJob = sqlJobService.getById(id);
		if(dbJob.getJobStatus() == JobStatus.ONLINE) {
			return R.fail("任务需要下线后才能删除");
		}
		return R.status(sqlJobService.doRemove(id));
	}
	
	/**
	 * 
	 * @param jobInstanceId
	 * @return
	 * @throws Exception 
	 */
//	@ApiLog("SQL任务")
//	@PostMapping("/cancel")
//	@ApiOperation(value = "根据ID取消/暂停任务", httpMethod = "POST")
//	public R<String> doCancel(Integer jobId) throws Exception {
////		dsyJobInstanceService.cancelJobInstance(jobInstanceId);
//		return R.success("任务已取消");
//	}
	
	/**
	 * 
	 * @param jobId
	 * @param jobStatus
	 * @return
	 */
	@ApiLog("SQL任务")
	@PostMapping("/switch/{jobId}")
	@ApiOperation(value = "切换任务状态", httpMethod = "POST")
	public R<String> switchStatus(@PathVariable Integer jobId, @RequestParam JobStatus jobStatus){
		if(jobStatus != JobStatus.ONLINE && jobStatus != JobStatus.OFFLINE) {
			return R.fail("任务状态不正确");
		}
		if(jobStatus == JobStatus.OFFLINE && sqlJobInstanceService.count(Wrappers.<SqlJobInstance>lambdaQuery().eq(SqlJobInstance::getJobId, jobId)
				.in(SqlJobInstance::getInstanceStatus, InstanceStatus.RUNNING, InstanceStatus.WAITING)) > 0) {
			return R.fail("该任务有实例正在运行或待运行，结束后才能下线。");
		}
		SqlJob job = sqlJobService.getById(jobId);
		if(job == null) {
			return R.fail("SQL任务不存在");
		}
		SqlJob entity = new SqlJob();
		entity.setId(jobId);
		entity.setJobStatus(jobStatus);
		if(jobStatus == JobStatus.OFFLINE && job.getTriggerStatus() == 1) { //任务需要下线，但是调度还在开启状态，需要关闭
			entity.setTriggerStatus(0);
			//需要关闭xxljob
			if(job.getXxlJobId() != null && job.getXxlJobId() > 0) {
				ReturnT<String> jobStop = adminBiz.jobStop(job.getXxlJobId());
				log.info("SQL任务:暂停任务|jobId={},xxljobId={},ret={}", jobId, job.getXxlJobId(), jobStop);
			}
		}
		entity.setUpdateBy(AuthUtil.getNickName());
		entity.setUpdateUserId(AuthUtil.getUserId());
		entity.setUpdateTime(LocalDateTime.now());
		return R.status(sqlJobService.updateById(entity));
	}
	
	/**
	 * 
	 * @param job
	 * @return
	 */
	@ApiLog("SQL任务")
	@PostMapping("/submitcron")
	@ApiOperation(value = "新增修改任务调度", httpMethod = "POST")
	public R<String> submitCron(@RequestBody SqlJob job){
		if(job.getId() == null || StrUtil.isBlank(job.getJobCron())) {
			return R.fail("请求参数不正确");
		}
		Integer jobId = job.getId();
		SqlJob dbJob = sqlJobService.getById(jobId);
		if(job.getTriggerStatus() == 1 && dbJob.getJobStatus() == JobStatus.OFFLINE) {
			return R.fail("任务必须上线后才能开启任务调度");
		}
		dbJob.setJobCron(job.getJobCron());
		int xxlJobId = Optional.<Integer>ofNullable(dbJob.getXxlJobId()).orElse(0);
		int triggerStatus = Optional.<Integer>ofNullable(job.getTriggerStatus()).orElse(0);
		ReturnT<String> returnT = _handleJob(dbJob, AuthUtil.getUserId(), xxlJobId, triggerStatus);
		log.info("SQL任务|新增/修改xxljob任务|jobId={},symbol={},result={}", jobId, (xxlJobId>0?"update":"add"), returnT);
		if (ReturnT.SUCCESS_CODE == returnT.getCode()){
			//首次添加任务，需要把任务ID更新进去
			SqlJob entity = new SqlJob();
			//修改的时候content=null，新增时候content=xxljobId
			int newXxlJobId = xxlJobId > 0 ? xxlJobId : Integer.valueOf(returnT.getContent());
			entity.setXxlJobId(newXxlJobId);
			entity.setTriggerStatus(triggerStatus);
			entity.setId(jobId);
			entity.setJobCron(job.getJobCron());
			sqlJobService.updateById(entity);
			//修改任务，并且之前为暂停状态：需要启动任务
			if(xxlJobId > 0 && triggerStatus == 1 && dbJob.getTriggerStatus() == 0) {
				ReturnT<String> jobStart = adminBiz.jobStart(newXxlJobId);
				log.info("SQL任务:启动任务|jobId={},xxljobId={},ret={}", jobId, newXxlJobId, jobStart);
			}
			if(xxlJobId > 0 && triggerStatus == 0 && dbJob.getTriggerStatus() == 1) {
				ReturnT<String> jobStop = adminBiz.jobStop(newXxlJobId);
				log.info("SQL任务:暂停任务|jobId={},xxljobId={},ret={}", jobId, newXxlJobId, jobStop);
			}
		}
		return R.data("操作成功");
	}
	
	/**
	 * 处理xxljob任务
	 * @return
	 */
	private ReturnT<String> _handleJob(SqlJob jobInfo, Long userId, int jobId, int triggerStatus) {
		try {//校验cron的正确性
			PatternParser.parse(jobInfo.getJobCron());
		} catch (Exception e) {
			throw new ServiceException("请检查Cron表达式不正确");
		}
		JobInfo xxlJobInfo = new JobInfo();
		xxlJobInfo.setJobGroup(groupId);
		xxlJobInfo.setJobDesc("SQL任务:"+jobInfo.getJobName() + ":" + jobInfo.getId());
		xxlJobInfo.setAuthor(userId.toString());
		xxlJobInfo.setScheduleType("CRON"); //调度类型：固定速度【以固定速度，触发任务调度；按照固定的间隔时间，周期性触发】
		xxlJobInfo.setScheduleConf(jobInfo.getJobCron());
		xxlJobInfo.setMisfireStrategy("DO_NOTHING");  //调度过期策略：忽略【调度过期后，忽略过期的任务，从当前时间开始重新计算下次触发时间】
		xxlJobInfo.setExecutorRouteStrategy("ROUND"); //路由策略：轮询
		xxlJobInfo.setExecutorBlockStrategy("SERIAL_EXECUTION"); //阻塞处理策略：单机串行【调度请求进入单机执行器后，调度请求进入FIFO队列并以串行方式运行】
		xxlJobInfo.setExecutorHandler("sqlJobHandler");
		xxlJobInfo.setExecutorParam(jobInfo.getId().toString());
		xxlJobInfo.setExecutorTimeout(60); //超时时间1分钟
		xxlJobInfo.setExecutorFailRetryCount(0);
		xxlJobInfo.setGlueUpdatetime(new Date());
		xxlJobInfo.setGlueType("BEAN");
		xxlJobInfo.setTriggerLastTime(0);
		xxlJobInfo.setTriggerNextTime(0);
		xxlJobInfo.setTriggerStatus(triggerStatus);//1.运行 0.暂停
		if(jobId == 0) { //新增
			xxlJobInfo.setAddTime(new Date());
			return adminBiz.jobAdd(xxlJobInfo);
		}else {
			xxlJobInfo.setId(jobId);
			return adminBiz.jobUpdate(xxlJobInfo);
		}
	}
	
}