package cn.stylefeng.workflow.instance.modular.task.taskopt.impl;

import cn.hutool.core.util.ObjectUtil;
import cn.stylefeng.roses.kernel.rule.exception.base.ServiceException;
import cn.stylefeng.workflow.api.NodeApi;
import cn.stylefeng.workflow.api.entity.design.Node;
import cn.stylefeng.workflow.api.entity.instance.RuntimeNode;
import cn.stylefeng.workflow.api.entity.task.RuTask;
import cn.stylefeng.workflow.api.enums.*;
import cn.stylefeng.workflow.api.pojo.InstanceContext;
import cn.stylefeng.workflow.event.api.EventPublishApi;
import cn.stylefeng.workflow.instance.api.RuntimeNodeApi;
import cn.stylefeng.workflow.instance.api.WorkflowControlApi;
import cn.stylefeng.workflow.instance.api.WorkflowStatusApi;
import cn.stylefeng.workflow.instance.api.pojo.request.WorkflowTurnRequest;
import cn.stylefeng.workflow.instance.api.pojo.task.RuTaskRequest;
import cn.stylefeng.workflow.instance.core.path.NodePathCalc;
import cn.stylefeng.workflow.instance.modular.task.enums.RuTaskExceptionEnum;
import cn.stylefeng.workflow.instance.modular.task.pojo.operate.RecallRequest;
import cn.stylefeng.workflow.instance.modular.task.service.RuTaskService;
import cn.stylefeng.workflow.instance.modular.task.service.TaskDaoWithEvent;
import cn.stylefeng.workflow.instance.modular.task.taskopt.TaskReCallService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.stream.Collectors;

@Service
public class TaskReCallServiceImpl implements TaskReCallService {

    @Resource
    private RuTaskService ruTaskService;

    @Resource
    private WorkflowStatusApi workflowStatusApi;

    @Resource
    private RuntimeNodeApi runtimeNodeApi;

    @Resource
    private WorkflowControlApi workflowControlApi;

    @Resource
    private NodeApi nodeApi;

    @Resource
    private TaskDaoWithEvent taskDaoWithEvent;

    @Resource
    private EventPublishApi eventPublishApi;

    @Override
    public void recallTask(RecallRequest recallRequest) {

        if (recallRequest.getInstanceId() != null) {
            this.recallFromInitiator(recallRequest.getInstanceId());
            return;
        }

        else if (recallRequest.getTaskId() != null) {
            this.recallFromApprover(recallRequest.getTaskId());
            return;
        }

        throw new ServiceException(RuTaskExceptionEnum.CANT_RECALL);
    }

    private void recallFromInitiator(Long instanceId) {

        InstanceContext instanceContext = workflowStatusApi.buildInstanceContext(instanceId);

        this.validateInstanceStatus(instanceContext);

        RuTaskRequest ruTaskRequest = new RuTaskRequest();
        ruTaskRequest.setInstanceId(instanceId);
        ruTaskRequest.setTaskStatus(TaskStatusEnum.PROCESSED.getCode());
        List<RuTask> alreadyApproved = ruTaskService.findList(ruTaskRequest);

        if (ObjectUtil.isEmpty(alreadyApproved)) {
            this.recallToStartNode(instanceId, instanceContext);
            return;
        }

        NodePathCalc nodePathCalc = new NodePathCalc(instanceContext);
        Node submitPersonNode = nodePathCalc.getPointNode(NodeTypeEnum.APPLICANT);

        Boolean retractAlreadyApprovedFlag = submitPersonNode.getNodeData().getNodeButtonPermission().getRetractAlreadyApprovedFlag();

        if (retractAlreadyApprovedFlag) {
            this.recallToStartNode(instanceId, instanceContext);
        } else {
            throw new ServiceException(RuTaskExceptionEnum.HAVE_ALREADY_APPROVE);
        }
    }

    private void recallFromApprover(Long taskId) {

        RuTask ruTask = ruTaskService.getById(taskId);
        if (ObjectUtil.isEmpty(ruTask)) {
            throw new ServiceException(RuTaskExceptionEnum.RU_TASK_NOT_EXISTED);
        }

        InstanceContext instanceContext = workflowStatusApi.buildInstanceContext(ruTask.getInstanceId());

        this.validateInstanceStatus(instanceContext);

        List<RuntimeNode> instanceCurrentRuNodeList = instanceContext.getRuntimeInstance().getCurrentRuntimeNodeList();
        RuntimeNode currentRuntimeNode = instanceCurrentRuNodeList.stream().filter(runtimeNode -> runtimeNode.getRuNodeId().equals(ruTask.getRuNodeId())).findFirst().orElse(null);

        if (currentRuntimeNode != null) {
            taskDaoWithEvent.updateTaskStatus(ruTask, TaskStatusEnum.PROCESSING.getCode(), null);
            return;
        }

        List<Long> ruNodeIdList = instanceCurrentRuNodeList.stream().map(RuntimeNode::getRuNodeId).collect(Collectors.toList());
        List<RuTask> alreadyApproved = ruTaskService.findListByRuNodeIds(ruNodeIdList, TaskStatusEnum.PROCESSED);

        if (ObjectUtil.isEmpty(alreadyApproved)) {
            this.recallToCustomNode(ruTask.getInstanceId(), ruTask.getReNodeId(), instanceContext, ruTask);
        }

        else {

            Node nodeInfo = nodeApi.getNodeInfo(ruTask.getReNodeId());
            Boolean retractAlreadyApprovedFlag = nodeInfo.getNodeData().getNodeButtonPermission().getRetractAlreadyApprovedFlag();
            if (retractAlreadyApprovedFlag) {
                this.recallToCustomNode(ruTask.getInstanceId(), ruTask.getReNodeId(), instanceContext, ruTask);
            } else {
                throw new ServiceException(RuTaskExceptionEnum.HAVE_ALREADY_APPROVE);
            }
        }

    }

    private void recallToStartNode(Long instanceId, InstanceContext instanceContext) {

        this.ruTaskService.deleteInstanceProcessingTask(instanceId);

        RuntimeNode startRuntimeNode = runtimeNodeApi.getStartRuntimeNode(instanceId);

        this.ruTaskService.createUserTask(instanceContext.getRuntimeInstance().getApplicant(), TaskTypeEnum.PROCESSING, startRuntimeNode, instanceContext);

        WorkflowTurnRequest workflowTurnRequest = new WorkflowTurnRequest();
        workflowTurnRequest.setInstanceId(instanceId);
        workflowTurnRequest.setReNodeId(startRuntimeNode.getNodeId());
        workflowTurnRequest.setInstanceContext(instanceContext);
        workflowTurnRequest.setContinueFlow(false);
        workflowControlApi.turn(workflowTurnRequest);

        eventPublishApi.publishModelEvent(ModelEventTypeEnum.RECALL, instanceContext);
    }

    private void recallToCustomNode(Long instanceId, Long reNodeId, InstanceContext instanceContext, RuTask ruTask) {

        this.ruTaskService.deleteInstanceProcessingTask(instanceId);

        taskDaoWithEvent.updateTaskStatus(ruTask, TaskStatusEnum.CALL_BACK.getCode());

        WorkflowTurnRequest workflowTurnRequest = new WorkflowTurnRequest();
        workflowTurnRequest.setInstanceId(instanceId);
        workflowTurnRequest.setReNodeId(reNodeId);
        workflowTurnRequest.setInstanceContext(instanceContext);
        workflowTurnRequest.setContinueFlow(true);
        workflowControlApi.turn(workflowTurnRequest);

        eventPublishApi.publishModelEvent(ModelEventTypeEnum.RECALL, instanceContext);
    }

    private void validateInstanceStatus(InstanceContext instanceContext) {
        Integer instanceStatus = instanceContext.getRuntimeInstance().getInstanceStatus();

        if (!InstanceStatusEnum.PROCESSING.getStatus().equals(instanceStatus)) {
            throw new ServiceException(RuTaskExceptionEnum.INSTANCE_STATUS_NOT_RIGHT);
        }
    }

}
