package com.friday.distributor.service;

import com.alibaba.dubbo.common.utils.CollectionUtils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.friday.distributor.constant.DistributorConstant;
import com.friday.distributor.constant.Events;
import com.friday.distributor.constant.States;
import com.friday.distributor.mapper.CyclicSyncDataConfigMapper;
import com.friday.distributor.po.CyclicSyncDataConfigPo;
import com.friday.distributor.qrtzjob.SyncDataJob;
import com.friday.distributor.statemachine.TaskStateMachineHelper;
import com.google.common.collect.Lists;
import com.friday.base.common.annotation.MethodLog;
import com.friday.base.common.response.DubboResult;
import com.friday.distributor.bean.Job;
import com.friday.distributor.bean.JobInvokeFilter;
import com.friday.distributor.common.ExecutorMethodInvokeHelper;
import com.friday.distributor.common.TaskExecConfigService;
import com.friday.distributor.po.JobInvoke;
import com.friday.distributor.po.TaskInvoke;
import com.friday.distributor.queue.TaskPersistHelper;
import com.friday.distributor.queue.TaskQueueManager;
import com.friday.operation.api.distributor.constant.JobStateChangeEnum;
import com.friday.operation.api.distributor.dto.*;
import com.friday.operation.api.distributor.service.IDistributorInternalService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.quartz.*;
import org.quartz.impl.matchers.GroupMatcher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

import static com.friday.operation.api.distributor.constant.JobStateChangeEnum.RUNNING2FAIL;
import static com.friday.operation.api.distributor.constant.JobStateChangeEnum.RUNNING2SUCCESS;
import static com.friday.operation.api.distributor.constant.TaieJobCallbackState.FAIL;
import static com.friday.operation.api.distributor.constant.TaieJobCallbackState.SUCCESS;
import static com.friday.operation.api.distributor.constant.TaskStateChangeEnum.EXECUTING_EXECUTED;

@Slf4j
@Service
public class DistributorInternalService implements IDistributorInternalService {
    @Autowired
    private TaskPersistHelper taskPersistHelper;
    @Autowired
    private TaskQueueManager taskQueueManager;
    @Autowired
    private TaskStateMachineHelper taskStateMachineHelper;
    @Autowired
    private ExecutorMethodInvokeHelper executorInvokeHelper;
    @Autowired
    private TaskExecConfigService taskExecConfigService;
    @Autowired
    private Scheduler scheduler;
    @Autowired
    private CyclicSyncDataConfigMapper cyclicSyncDataConfigMapper;

    @Override
    @MethodLog(desc = "子任务回调")
    public DubboResult callback(TaieJobCallbackDto callBack) {
        if(Objects.isNull(callBack.getInvokeId()) || StringUtils.isEmpty(callBack.getJobState())){
            return DubboResult.newFailure(DubboResult.CLIENT_EXCEPTION, "请检查以下参数：invokeId/jobState");
        }
        String jobState = callBack.getJobState().toUpperCase();
        Optional<Job> jobInQueue = taskQueueManager.getJobById(callBack.getInvokeId());
        if(!jobInQueue.isPresent()){
            JobStateChangeEnum stateChange;
            if(SUCCESS.getState().equals(jobState)){
                stateChange = RUNNING2SUCCESS;
            } else if(FAIL.getState().equals(jobState)){
                stateChange = RUNNING2FAIL;
            } else {
                return DubboResult.newFailure(DubboResult.SERVER_EXCEPTION, "不支持的任务状态：" + callBack.getJobState());
            }
            executorInvokeHelper.onJobStateChange(callBack.getInvokeId(), stateChange);
            taskPersistHelper.updateJobInvoke(JobInvoke.builder()
                    .id(callBack.getInvokeId())
                    .jobState(jobState)
                    .failMsg(callBack.getFailMsg())
                    .build());
            return DubboResult.newSuccess();
        }
        Job job = jobInQueue.get();
        if(SUCCESS.getState().equals(jobState)){
            job.setState(jobState);
            taskStateMachineHelper.sendJobEvent(Events.JobEvent.COMPLETE_JOB, job);
            taskQueueManager.updateJob(job, job.getQueueKey());
        } else if(FAIL.getState().equals(jobState)){
            if(job.getRetryTime() < job.getMaxRetryTime()){
                job.setRetryTime(job.getRetryTime() + 1);
                job.setExecJobId(null);
                job.setCreateTime(LocalDateTime.now());
            } else {
                job.setState(jobState);
                job.setFailMsg(callBack.getFailMsg());
                taskStateMachineHelper.sendJobEvent(Events.JobEvent.FAIL_JOB, job);
            }
            taskQueueManager.updateJob(job, job.getQueueKey());
        } else {
            return DubboResult.newFailure(DubboResult.CLIENT_EXCEPTION, "不支持的任务状态：" + callBack.getJobState());
        }
        return DubboResult.newSuccess();
    }

    @Override
    public DubboResult postHandleTask(String taskId) {
        return executorInvokeHelper.onTaskStateChange(taskId, EXECUTING_EXECUTED);
    }

    @Override
    public DubboResult addBizConfig(BizConfigDto bizConfig) {
        return taskExecConfigService.createBizConfig(bizConfig);
    }

    @Override
    public DubboResult addTaskQueue(TaskQueueDto queue) {
        try {
            taskQueueManager.addQueue(queue);
            return DubboResult.newSuccess(queue.getKey());
        } catch (Exception e) {
            log.error("新增队列失败：{}", JSON.toJSONString(queue), e);
            return DubboResult.newFailure(DubboResult.SERVER_EXCEPTION, e.getMessage());
        }
    }

    @Override
    public DubboResult updateTaskBizData(UpdateTaskDto update) {
        try {
            TaskInvoke invoke = TaskInvoke.builder()
                    .taskId(update.getTaskId())
                    .taskOutput(update.getTaskOutput())
                    .taskBizData(update.getTaskBizData())
                    .remark(update.getRemark())
                    .build();
            taskPersistHelper.updateTaskInvoke(invoke);
            return DubboResult.newSuccess();
        } catch (Exception e) {
            log.error("更新框架Task业务数据失败，taskId={}", update.getTaskId(), e);
            return DubboResult.newFailure(DubboResult.SERVER_EXCEPTION, e.getMessage());
        }
    }

    @Override
    public DubboResult updateJobBizData(UpdateJobDto update) {
        try {
            JobInvoke invoke = JobInvoke.builder()
                    .id(update.getInvokeId())
                    .jobOutput(update.getJobOutput())
                    .jobBizData(update.getJobBizData())
                    .remark(update.getRemark())
                    .build();
            taskPersistHelper.updateJobInvoke(invoke);
            return DubboResult.newSuccess();
        } catch (Exception e) {
            log.error("更新框架Job业务数据失败，invokeId={}", update.getInvokeId(), e);
            return DubboResult.newFailure(DubboResult.SERVER_EXCEPTION, e.getMessage());
        }
    }

    @Override
    public DubboResult<TaieTaskDto> getTaskFrameData(String taskId) {
        try {
            TaieTaskDto taieTaskDto = taskPersistHelper.getTaskFrameData(taskId);
            return DubboResult.newSuccess(taieTaskDto);
        } catch (Exception e) {
            log.error("获取Task框架数据失败，taskId={}", taskId, e);
            return DubboResult.newFailure(DubboResult.SERVER_EXCEPTION, e.getMessage());
        }
    }

    @Override
    public DubboResult<TaieJobDto> getJobFrameData(Integer invokeId) {
        try {
            TaieJobDto taieJobDto = taskPersistHelper.getJobFrameData(invokeId);
            return DubboResult.newSuccess(taieJobDto);
        } catch (Exception e) {
            log.error("获取Job框架数据失败，invokeId={}", invokeId, e);
            return DubboResult.newFailure(DubboResult.SERVER_EXCEPTION, e.getMessage());
        }
    }

    @Override
    public DubboResult<ArrayList<TaieJobDto>> getJobFrameDataList(String taskId) {
        try {
            ArrayList<TaieJobDto> taieJobs = (ArrayList<TaieJobDto>) taskPersistHelper.getJobFrameDataList(taskId);
            return DubboResult.newSuccess(taieJobs);
        } catch (Exception e) {
            log.error("获取Job框架数据列表失败，taskId={}", taskId, e);
            return DubboResult.newFailure(DubboResult.SERVER_EXCEPTION, e.getMessage());
        }
    }

    @Override
    public DubboResult generateInProcessForm() {
        return null;
    }

    @Override
    public DubboResult getFormDefinition() {
        return null;
    }

    @Override
    public DubboResult commitFormData() {
        return null;
    }

    @Override
    public DubboResult getFormCommitData() {
        return null;
    }

    @Override
    public DubboResult<Boolean> checkTaskRunning(String taieTenant, List<String> taskTypeList) {
        if(StringUtils.isEmpty(taieTenant) || CollectionUtils.isEmpty(taskTypeList)){
            return DubboResult.newFailure(DubboResult.CLIENT_EXCEPTION, "无效入参");
        }
        JobInvokeFilter filter = JobInvokeFilter.builder()
                .taieTenant(taieTenant)
                .taskTypeList(taskTypeList)
                .stateList(Lists.newArrayList(States.JobState.RUNNING.name(), States.JobState.RUNNING_TIMEOUT.name()))
                .build();
        List<JobInvoke> jobInvokes = taskPersistHelper.selectJobInvokes(filter);
        return DubboResult.newSuccess(CollectionUtils.isNotEmpty(jobInvokes));
    }

	@Override
	public DubboResult reloadSyncDataConfig() {
		long start = System.currentTimeMillis();
		try {
			log.info("loadSyncDataConfig--开始");
			List<CyclicSyncDataConfigPo> configList = cyclicSyncDataConfigMapper.selectAll();
			// 查询同组的jobKey，去除已持久化但已失效的jobKey
			Set<JobKey> existsJobKeys = scheduler
					.getJobKeys(GroupMatcher.groupEquals(DistributorConstant.SYNC_DATA_GROUP));
			List<JobKey> deleteKeys = existsJobKeys.stream()
					.filter(key -> configList.stream().noneMatch(po -> po.getJobKey().equals(key.getName())))
					.collect(Collectors.toList());
			if (!CollectionUtils.isEmpty(deleteKeys)) {
				log.info("loadSyncDataConfig--将要删除失效的Job:{}", deleteKeys);
				scheduler.deleteJobs(deleteKeys);
			}

			// reload job and trigger
			configList.stream().collect(Collectors.groupingBy(CyclicSyncDataConfigPo::getJobKey)).values().stream()
					.forEach(sameJobConfig -> {
						try {
							JobDetail job = JobBuilder.newJob(SyncDataJob.class)
									.withIdentity(sameJobConfig.get(0).getJobKey(), DistributorConstant.SYNC_DATA_GROUP)
									.storeDurably().build();
							Set<Trigger> triggers = sameJobConfig.stream().map(config -> {
								DataRequest dataReq = new DataRequest();
								dataReq.setTaskType(config.getTaskType());
								dataReq.setTaieTenant(config.getTaieTenant());
								dataReq.setAction(config.getAction());
								if (StringUtils.isNotBlank(config.getBizParam())) {
									dataReq.setParamsMap(JSON.parseObject(config.getBizParam(),
											new TypeReference<Map<String, Object>>() {
											}));
								}
								CronScheduleBuilder scheduleBuilder = CronScheduleBuilder
										.cronSchedule(config.getCron());
								Trigger trigger = TriggerBuilder.newTrigger().forJob(job)
										.withIdentity(config.getTriggerKey(), DistributorConstant.SYNC_DATA_GROUP)
										.withSchedule(scheduleBuilder).build();
								trigger.getJobDataMap().put(DistributorConstant.SYNC_DATA_DATA_MAP_KEY, dataReq);
								return trigger;
							}).collect(Collectors.toSet());
							log.info("loadSyncDataConfig--重新加载Job:{},triggers:{}", job.getKey(), triggers);
							scheduler.scheduleJob(job, triggers, true);
						} catch (Exception e) {
							log.error("loadSyncDataConfig--初始化加载syncData任务失败", e);
						}
					});
			return DubboResult.newSuccess();
		} catch (Exception e) {
			log.error("loadSyncDataConfig--初始化加载syncData任务失败", e);
			return DubboResult.newFailure(DubboResult.SERVER_EXCEPTION,"加载失败！");
		} finally {
			log.info("loadSyncDataConfig--结束,用时{}s", (System.currentTimeMillis()-start)/1000);
		}
	}

//	public void deleteAuditInfo(){
//    	String before = LocalDateTime.now().minusDays(auditDeletePeriodDays).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
//    	auditMapper.deleteAudit(before);
//	}

}
