package com.friday.distributor.queue;

import com.friday.blank.api.dto.taskType.TaskTypeDetailDto;
import com.friday.distributor.common.TaskExecConfigService;
import com.friday.distributor.constant.Events;
import com.friday.distributor.constant.ExecScheduler;
import com.friday.distributor.constant.States;
import com.friday.distributor.exception.DistributorRuntimeException;
import com.friday.distributor.mapper.JobInvokeMapper;
import com.friday.distributor.mapper.QueueMapper;
import com.friday.distributor.mapper.TaskInvokeMapper;
import com.friday.distributor.scheduler.QueuePoller;
import com.friday.distributor.statemachine.TaskStateMachineHelper;
import com.google.common.collect.Lists;
import com.friday.base.common.response.DubboResult;
import com.friday.base.redis.annotation.Rlock;
import com.friday.distributor.bean.Job;
import com.friday.distributor.bean.Task;
import com.friday.distributor.po.JobInvoke;
import com.friday.distributor.po.QueuePo;
import com.friday.distributor.po.TaskInvoke;
import com.friday.operation.api.distributor.dto.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.scheduling.annotation.Async;
import org.springframework.statemachine.StateMachine;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;

import static com.friday.blank.api.constants.BusinessTypeEnum.*;

/**
 * @Author YaoYao
 * @Date Created In 18:26 2023/2/15
 * @Description 队列管理
 * @Modified By
 */

@Service
@Slf4j
public class TaskQueueManager {
    @Autowired
    private TaskRedisHelper taskRedisHelper;
    @Autowired
    private TaskInvokeMapper taskInvokeMapper;
    @Autowired
    private JobInvokeMapper jobInvokeMapper;
    @Autowired
    private TaskExecConfigService taskExecConfigService;
    @Autowired
    private ApplicationContext applicationContext;
    @Autowired
    private QueueMapper queueMapper;
    @Autowired
    private TaskStateMachineHelper taskStateMachineHelper;



    @Async(value = "queuePollingExecutor")
    public void queuePolling(String qKey){
//        log.info("队列{}异步轮询开始,当前任务线程 = {}", qKey, Thread.currentThread());
        QueuePoller poller = this.getQueuePoller(qKey);
        poller.polling(qKey, this.getTaskListByQueueKey(qKey));
//        log.info("队列{}异步轮询结束,当前任务线程 = {}", qKey, Thread.currentThread());
    }

    //新增队列
    public void addQueue(TaskQueueDto queueDto){
        if(StringUtils.isEmpty(queueDto.getKey())){
            throw new DistributorRuntimeException("队列初始化所需键值为空");
        }
        String pollerName = StringUtils.isEmpty(queueDto.getPoller()) ? this.getDefaultPollerName(queueDto) : queueDto.getPoller();
        this.getPollerByName(pollerName);
        String execSchedulerName = StringUtils.isEmpty(queueDto.getExecScheduler()) ? this.getDefaultExecSchedulerName(queueDto) : queueDto.getExecScheduler();
        this.getExecSchedulerByName(execSchedulerName);
        try {
            QueuePo queue = queueMapper.selectQueueByKey(queueDto.getKey());
            if(Objects.isNull(queue)){
                QueuePo queuePo = QueuePo.builder()
                        .key(queueDto.getKey())
                        .poller(pollerName)
                        .execScheduler(execSchedulerName)
                        .mutex(false)
                        .build();
                queueMapper.insertQueue(queuePo);
            }
        } catch (Exception e){
            throw new DistributorRuntimeException("队列信息落库失败, queueKey = " + queueDto.getKey());
        }
        taskRedisHelper.setTaskData(queueDto.getKey(), Lists.newArrayList());
    }

    private String getDefaultPollerName(TaskQueueDto queueDto){
        return com.friday.distributor.constant.QueuePoller.DefaultQueuePoller.getName();
    }

    private String getDefaultExecSchedulerName(TaskQueueDto queueDto){
        if(RPA.getKey().equals(queueDto.getBizType())){
            return ExecScheduler.RPA_UNATTENDED_DEFAULT.getName();
        }
        if(PROGRAM.getKey().equals(queueDto.getBizType())){
            return ExecScheduler.PROGRAM_UNATTENDED_DEFAULT.getName();
        }
        if(AI.getKey().equals(queueDto.getBizType())){
            return ExecScheduler.AI_UNATTENDED_DEFAULT.getName();
        }
        throw new DistributorRuntimeException("不支持的任务类型：" + queueDto.getBizType());
    }

    private QueuePoller getPollerByName(String pollerName){
        Map<String, QueuePoller> pollers = applicationContext.getBeansOfType(QueuePoller.class);
        QueuePoller poller = pollers.get(pollerName);
        if(Objects.isNull(poller)){
            throw new DistributorRuntimeException("未找到指定的轮询器, poller = " + pollerName);
        }
        return poller;
    }

    private com.friday.distributor.scheduler.ExecScheduler getExecSchedulerByName(String execSchedulerName){
        Map<String, com.friday.distributor.scheduler.ExecScheduler> execSchedulers = applicationContext.getBeansOfType(com.friday.distributor.scheduler.ExecScheduler.class);
        com.friday.distributor.scheduler.ExecScheduler execScheduler = execSchedulers.get(execSchedulerName);
        if(Objects.isNull(execScheduler)){
            throw new DistributorRuntimeException("未找到指定的轮执行调度器, execScheduler = " + execSchedulerName);
        }
        return execScheduler;
    }

    public QueuePoller getQueuePoller(String qKey){
        QueuePo queue = queueMapper.selectQueueByKey(qKey);
        if(Objects.isNull(queue) || StringUtils.isEmpty(queue.getPoller())){
            throw new DistributorRuntimeException("未查询到队列信息或轮询器为空， queueKey = " + qKey);
        }
        return this.getPollerByName(queue.getPoller());
    }

    public com.friday.distributor.scheduler.ExecScheduler getQueueExecScheduler(String qKey){
        QueuePo queuePo = queueMapper.selectQueueByKey(qKey);
        if(Objects.isNull(queuePo) || StringUtils.isEmpty(queuePo.getExecScheduler())){
            throw new DistributorRuntimeException("未查询到队列信息或执行调度器为空， queueKey = " + qKey);
        }
        return this.getExecSchedulerByName(queuePo.getExecScheduler());
    }

    //移除队列
    @Rlock(localKey = "UpdateQueue + #queueKey")
    public void removeQueue(String queueKey){
        if(!this.emptyQueue(queueKey)){
            throw new DistributorRuntimeException(queueKey + "队列有未完成的任务,不可移除");
        }
        taskRedisHelper.removeTaskData(queueKey);
        queueMapper.updateQueueByKey(QueuePo.builder()
                .key(queueKey)
                .valid(false)
                .build());
    }

    //清空队列
    @Rlock(localKey = "UpdateQueue + #queueKey")
    public void clearQueue(ClearQueueDto clearQueueDto, String queueKey){
        taskRedisHelper.clearTaskData(clearQueueDto);
    }

    //新增任务
    @Rlock(localKey = "UpdateQueue + #queueKey")
    public void addTask(Task task, String queueKey){
        //若缓存中没有队列信息,以默认配置初始化队列
        if(!taskRedisHelper.queueExist(queueKey)){
            TaskQueueDto queueDto = TaskQueueDto.builder()
                    .key(queueKey)
                    .bizType(task.getBizType())
                    .build();
            this.addQueue(queueDto);
        }
        List<Task> taskList = taskRedisHelper.getTaskData(queueKey);
        taskList.add(task);
        taskRedisHelper.setTaskData(queueKey, taskList);
    }

    //更新任务
    @Rlock(localKey = "UpdateQueue + #queueKey")
    public void updateTask(Task task, String queueKey){
        List<Task> taskList = taskRedisHelper.getTaskData(queueKey);
        taskList.removeIf(t -> t.getTaskId().equals(task.getTaskId()));
        taskList.add(task);
        taskRedisHelper.setTaskData(queueKey, taskList);
    }

    //移除任务
    @Rlock(localKey = "UpdateQueue + #queueKey")
    public void removeTask(Task task, String queueKey){
        List<Task> taskList = taskRedisHelper.getTaskData(queueKey);
        taskList.removeIf(t -> t.getTaskId().equals(task.getTaskId()));
        taskRedisHelper.setTaskData(queueKey, taskList);
    }

    //更新队列子任务信息
    @Rlock(localKey = "UpdateQueue + #queueKey")
    public void updateJob(Job job, String queueKey){
        List<Task> taskList = taskRedisHelper.getTaskData(queueKey);
        taskList.stream().filter(t -> t.getTaskId().equals(job.getTaskId())).findAny().ifPresent(curTask -> {
            if(!CollectionUtils.isEmpty(curTask.getJobs())){
                curTask.getJobs().removeIf(j -> j.getInvokeId().equals(job.getInvokeId()));
                curTask.getJobs().add(job);
            }
        });
        taskRedisHelper.setTaskData(queueKey, taskList);
    }


    public void transferTask(String originQKey, String targetQKey, boolean transferAll, TaskIdentifier taskIdentifier){

    }

    public void selectTask(){

    }

    public DubboResult modifyJobBizData(Job job, StandardJobInput jobInput){
        if(!job.modifiable()){
            return DubboResult.newFailure(DubboResult.CLIENT_EXCEPTION, "任务不可修改");
        }
        job.setJobInput(jobInput);
        this.updateJob(job, job.getQueueKey());
        return DubboResult.newSuccess();
    }

    //刷新队列里任务的管理端运行配置
    public void refreshTaskConfig(String taieTenant, String taskType){
        TaskTypeDetailDto detail = taskExecConfigService.getTaskExecConfig(taieTenant, taskType);
        List<Task> taskList = getTaskListByBizConfig(taieTenant, taskType);
        for(Task task : taskList){
            for(Job job : task.getJobs()){
                if(job.modifiable()){
                    job.setGivenRobots(detail.getMachines());//待确定
                    job.setTaskExecConfig(detail);
                }
            }
        }
    }

    //获取队列任务详情
    public List<Task> getTaskListByQueueKey(String qKey) {
        try {
            List<Task> taskInCache = taskRedisHelper.getTaskData(qKey);
            if (CollectionUtils.isEmpty(taskInCache)) {
                return Lists.newArrayList();
            }
            for (Task task : taskInCache) {
                StateMachine<States.TaskState, Events.TaskEvent> taskStateMachine = taskStateMachineHelper.restoreTaskStateMachine(task.getUniqueKey());
                task.setStateMachine(taskStateMachine);
                for (Job job : task.getJobs()) {
                    StateMachine<States.JobState, Events.JobEvent> jobStateMachine = taskStateMachineHelper.restoreJobStateMachine(job.getUniqueKey());
                    job.setStateMachine(jobStateMachine);
                }
            }
            return taskInCache;
        } catch (Exception e) {
            log.error("获取任务队列失败", e);
            return Lists.newArrayList();
        }
    }

    public boolean emptyQueue(String qKey){
        return CollectionUtils.isEmpty(this.getTaskListByQueueKey(qKey));
    }

    public boolean mutexQueue(String qKey){
        QueuePo queuePo = queueMapper.selectQueueByKey(qKey);
        return Objects.nonNull(queuePo) && queuePo.getMutex();
    }

    public List<Task> getTaskListByBizConfig(String taieTenant, String taskType) {
        BizConfigDto bizConfig = taskExecConfigService.getBizConfig(taieTenant, taskType);
        return this.getTaskListByQueueKey(bizConfig.getQueueKey());
    }

    public Optional<Task> getTaskById(String taskId) {
        TaskInvoke invoke = taskInvokeMapper.selectTaskByTaskId(taskId);
        BizConfigDto bizConfig = taskExecConfigService.getBizConfig(invoke.getTaieTenant(), invoke.getTaskType());
        List<Task> taskList = this.getTaskListByQueueKey(bizConfig.getQueueKey());
        return taskList.stream().filter(t -> t.getTaskId().equals(taskId)).findAny();
    }

    public Optional<Job> getJobById(Integer invokeId) {
        JobInvoke invoke = jobInvokeMapper.selectJobById(invokeId);
        Optional<Task> task = this.getTaskById(invoke.getTaskId());
        return task.flatMap(value -> value.getJobs().stream().filter(j -> j.getInvokeId().equals(invokeId)).findAny());
    }

    public List<Job> getRunningJobs(String qKey) {
        List<Task> tasks = taskRedisHelper.getTaskData(qKey);
        List<Job> jobs = Lists.newArrayList();
        tasks.forEach(t -> jobs.addAll(t.getJobs()));
        return jobs.stream().filter(Job::running).collect(Collectors.toList());
    }

    public List<Task> getAbbrTaskList(String qKey) {
        List<Task> tasks = taskRedisHelper.getTaskData(qKey);
        return tasks.stream().map(t -> {
            Task abbrTask = new Task();
            abbrTask.setTaskId(t.getTaskId());
            abbrTask.setState(t.getState());
            List<Job> abbrJobs = t.getJobs().stream().map(j -> {
                Job abbrJob = new Job();
                abbrJob.setInvokeId(j.getInvokeId());
                abbrJob.setState(j.getState());
                return abbrJob;
            }).collect(Collectors.toList());
            abbrTask.setJobs(abbrJobs);
            return abbrTask;
        }).collect(Collectors.toList());
    }

    public List<TaskStatisticDto> getTaskStatistic(){
        List<QueuePo> queues = queueMapper.selectAllQueues();
        List<TaskStatisticDto> results = Lists.newArrayList();
        queues.forEach(q -> {
            List<Task> tasks = this.getTaskListByQueueKey(q.getKey());
            long jobNum = tasks.stream().map(t -> t.getJobs().size()).count();
            TaskStatisticDto statistic = TaskStatisticDto.builder()
                    .queueKey(q.getKey())
                    .taskNum(tasks.size())
                    .jobNum((int) jobNum)
                    .build();
            results.add(statistic);
        });
        return results;
    }
}
