package com.ningdatech.pmapi.todocenter.handle;

import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.ningdatech.pmapi.common.helper.UserInfoHelper;
import com.ningdatech.pmapi.projectlib.enumeration.InstTypeEnum;
import com.ningdatech.pmapi.projectlib.model.entity.ProjectInst;
import com.ningdatech.pmapi.projectlib.service.IProjectInstService;
import com.ningdatech.pmapi.todocenter.constant.TodoCenterConstant;
import com.ningdatech.pmapi.user.security.auth.model.UserFullInfoDTO;
import com.ningdatech.pmapi.user.security.auth.model.UserInfoDetails;
import com.ningdatech.pmapi.user.util.LoginUserUtil;
import com.wflow.contants.HisProInsEndActId;
import com.wflow.workflow.bean.dto.ReqProcessHandlerDTO;
import com.wflow.workflow.bean.process.ProgressNode;
import com.wflow.workflow.bean.process.enums.NodeTypeEnum;
import com.wflow.workflow.bean.vo.ProcessProgressVo;
import com.wflow.workflow.enums.ProcessStatusEnum;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.assertj.core.util.Lists;
import org.flowable.bpmn.model.*;
import org.flowable.engine.HistoryService;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.history.HistoricActivityInstance;
import org.flowable.engine.history.HistoricProcessInstance;
import org.springframework.stereotype.Component;

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

/**
 * @Classname WithDrawHandle
 * @Description
 * @Date 2023/4/2 14:46
 * @Author PoffyZhang
 */
@Component
@Slf4j
@AllArgsConstructor
public class WithDrawHandle {

    private final UserInfoHelper userInfoHelper;

    private final HistoryService historyService;
    private final RepositoryService repositoryService;
    private final IProjectInstService projectInstService;

    /**
     * 判断 当前流程 可否被当前登录人 所撤回
     * @param instanceId
     * @param progressInstanceDetail
     * @return
     */
    public Boolean checkCanWithdraw(String instanceId, ProcessProgressVo progressInstanceDetail,String taskId) {
        HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery()
                .processInstanceId(instanceId).singleResult();
        UserInfoDetails userInfoDetails = LoginUserUtil.loginUserDetail();
        String employeeCode = userInfoDetails.getEmployeeCode();
        //如果不是审核中
        if(!ProcessStatusEnum.UNDER_REVIEW.getDesc()
                .equals(progressInstanceDetail.getStatus())){
            return Boolean.FALSE;
        }
        // 如果当前登录用户是流程发起人,判断流程是否已经开始审批,如果开始审批,不能撤回
        Boolean userIsRoot = checkUserIsRoot(instanceId, null);
        if (Boolean.TRUE.equals(userIsRoot) && canRootWithDraw(historicProcessInstance, employeeCode)){
            return Boolean.TRUE;
        }
        ReqProcessHandlerDTO dto = new ReqProcessHandlerDTO();
        dto.setTaskId(taskId);
        dto.setInstanceId(instanceId);
        return checkUserIsBefore(progressInstanceDetail.getProgressInfo(),dto);
    }

    // 判断当前操作人 是上一个节点的审批人
    public Boolean checkUserIsBefore(List<ProgressNode> currentProgressInfo, ReqProcessHandlerDTO param) {
        UserFullInfoDTO user = userInfoHelper.getUserFullInfo(LoginUserUtil.getUserId());

        // 先排除掉抄送任务节点
        currentProgressInfo = currentProgressInfo.stream()
                .filter(c -> !NodeTypeEnum.CC.name().equals(c.getNodeType().name()))
                .collect(Collectors.toList());

        if (CollUtil.isEmpty(currentProgressInfo)){
            return Boolean.FALSE;
        }

        String taskId = param.getTaskId();

        //1.判断出 当前审批人和上一个审批人 如果传了taskid
        ProgressNode progressNode = StringUtils.isBlank(taskId) ?
                currentProgressInfo.get(currentProgressInfo.size() - 1)
            : findSubNodeByTaskId(taskId,currentProgressInfo);

        if (Objects.isNull(progressNode)){
            return Boolean.FALSE;
        }

        ProgressNode beforeProgressNode = null;
        ProgressNode currentProgressNode = null;

        // 说明当前节点是子流程节点
        // 如果是会签 或签 当前和上个
        List<ProgressNode> thisAndOr = Lists.newArrayList();
        List<ProgressNode> beforeAndOr = Lists.newArrayList();
        if (progressNode.getNodeType().name().equals(NodeTypeEnum.SUB.name())) {
            List<ProgressNode> children = progressNode.getChildren();
            // 先排除掉子流程中抄送的节点，避免塞入错误的taskId
            children = children.stream()
                    .filter(c -> !NodeTypeEnum.CC.name().equals(c.getNodeType().name()))
                    .collect(Collectors.toList());
            if (CollUtil.isEmpty(children)){
                return Boolean.FALSE;
            }
            currentProgressNode = children.get(children.size() - 1);

            //把当前和上一个节点和 会签或签的情况 都check出来
            beforeProgressNode = checkBeforeNodeAndOr(children,currentProgressNode,thisAndOr,beforeAndOr);
        } else {
            currentProgressNode = currentProgressInfo.get(currentProgressInfo.size() - 1);

            // 把当前和上一个节点和 会签或签的情况 都check出来
            beforeProgressNode = checkBeforeNodeAndOr(currentProgressInfo,currentProgressNode,thisAndOr,beforeAndOr);
        }
        // 判断当前工作流任务前一个审核人的部门和当前登录用户的部门是否是同一个，如果是同一个才可以撤回，否则抛出异常
        // 获取当前当前工作流任务当前审核人信息
        UserFullInfoDTO currentUserInfo = userInfoHelper.getUserFullInfoByEmployeeCode(currentProgressNode.getUserId());
        if(Objects.isNull(currentUserInfo) || !currentUserInfo.getEmpPosUnitCode().equals(user.getEmpPosUnitCode())){
            return Boolean.FALSE;
        }

        Boolean isAndOr = Boolean.FALSE;

        //判断当前子流程是否结束 如果结束了 必不能撤回
        Boolean thisSubNodeOver = Boolean.FALSE;
        //当前节点是 会签|或签的情况
        if(CollUtil.isNotEmpty(thisAndOr)){
            Integer finishNodes = 0;
            for(ProgressNode n : thisAndOr){
                if(Objects.nonNull(n.getFinishTime())){
                    //当前会签 有审批过的 那么上个会签|或签 无论如何 就不可能可以撤回了
                    beforeAndOr = Collections.emptyList();
                    finishNodes ++;
                }
                //找到了 当前的操作人 在当前的 会签|或签 中 并且已经审批了
                if(n.getUserId().equals(user.getEmployeeCode()) &&
                        Objects.nonNull(n.getFinishTime())){
                    beforeProgressNode = n;
                    //说明当前操作人 在上个会签或者或签节点
                    isAndOr = Boolean.TRUE;
                }
            }
            //如果都审批过了 结束了 那说明此子流程已经结束了 不能再撤回了
            if(finishNodes.equals(thisAndOr.size())){
                return Boolean.FALSE;
            }
        }else{
            //如果当前不是会签|或签 就判断 当前最后一个节点 结束了没
            //如果结束了 也同样 直接不能撤回
            if(Objects.nonNull(currentProgressNode.getFinishTime())){
                return Boolean.FALSE;
            }
        }

        //如果上个会签没取到 还有种情况是 会签 或签 并且在上个节点
        if(!isAndOr && CollUtil.isNotEmpty(beforeAndOr)) {
            for (ProgressNode n : beforeAndOr) {
                if (n.getUserId().equals(user.getEmployeeCode())
                        && !TodoCenterConstant.Handler.OR_SIGN_FLAG.equals(n.getWithdrawFlag())) {
                    beforeProgressNode = n;
                    //说明当前操作人 在上个会签或者或签节点
                    isAndOr = Boolean.TRUE;
                    break;
                }
            }
        }

        if(!isAndOr && (Objects.isNull(beforeProgressNode)
                || !user.getEmployeeCode().equals(beforeProgressNode.getUserId()))){
            return Boolean.FALSE;
        }

        //如果是true 把对应操作人 在上个审批节点的task 也传入
        if(Objects.nonNull(param)){
            // 获取bpmn文件中节点的连线关系，如果前一个节点的下一个节点是抄送节点，
            // 获取抄送节点传入param中，在执行撤回操作时，将抄送信息从抄送表中删除
            HistoricProcessInstance processInstance = historyService.createHistoricProcessInstanceQuery()
                    .processInstanceId(param.getInstanceId())
                    .singleResult();
            BpmnModel bpmnModel = repositoryService.getBpmnModel(processInstance.getProcessDefinitionId());
            // 传节点定义key 获取撤回操作人在流程配置中所在的节点
            FlowNode flowNode = (FlowNode) bpmnModel.getFlowElement(beforeProgressNode.getNodeId());
            List<SequenceFlow> outgoingFlows = flowNode.getOutgoingFlows();
            for (SequenceFlow outgoingFlow : outgoingFlows) {
                //获取输出节点元素
                FlowElement targetFlowElement = outgoingFlow.getTargetFlowElement();
                // 获取下一个抄送任务的节点
                if(targetFlowElement instanceof ServiceTask){
                    param.setCcNodeId(targetFlowElement.getId());
                    break;
                }
            }
            param.setTaskId(beforeProgressNode.getTaskId());
        }
        return Boolean.TRUE;
    }

    /**
     * 找到其 所在的子流程父节点
     * @param taskId
     * @return
     */
    private ProgressNode findSubNodeByTaskId(String taskId,List<ProgressNode> currentProgressInfos) {
        for(ProgressNode progressNode : currentProgressInfos){
            if(progressNode.getNodeType().equals(NodeTypeEnum.SUB)){
                List<ProgressNode> children = progressNode.getChildren();
                for(ProgressNode c : children){
                    if(taskId.equals(c.getTaskId())){
                        //如果任务ID对得上 返回其 所在的子流程
                        return progressNode;
                    }
                }
            }
        }
        return null;
    }

    /**
     * 判断撤回用户 是不是发起人
     * @param processInstanceId
     * @param task
     * @return
     */
    public Boolean checkUserIsRoot(String processInstanceId,String task){
        UserFullInfoDTO user = userInfoHelper.getUserFullInfo(LoginUserUtil.getUserId());
        // 获取当前要处理的流程实例
        HistoricProcessInstance instance = historyService.createHistoricProcessInstanceQuery()
                .processInstanceId(processInstanceId)
                .singleResult();
        if (user.getEmployeeCode().equals(instance.getStartUserId()) &&
                (Objects.isNull(task) || user.getEmployeeCode().equals(instance.getStartUserId()))) {
            return Boolean.TRUE;
        }
        return Boolean.FALSE;
    }


    /**
     * 把上一个节点 和 或签 会签情况都check出来
     * @param progressNodes andOr
     * @param thisAndOr beforeAndOr
     * @return
     */
    public ProgressNode checkBeforeNodeAndOr(List<ProgressNode> progressNodes,ProgressNode curr,
                                              List<ProgressNode> thisAndOr,List<ProgressNode> beforeAndOr) {
        ProgressNode beforeNode = null;
        if(progressNodes.size() == 1){
            return null;
        }
        //进入上一个节点的标识
        Boolean enterBefore = Boolean.FALSE;
        //假设 作为当前会签的第一个点
        String thisAndOrNodeId = curr.getNodeId();
        //假设 作为上一个会签/或签的第一个点
        ProgressNode beforeAndOrNode = null;
        for(int i = progressNodes.size() - 2;i >= 0;i--){
            //说明有会签 或签
            if(progressNodes.get(i).getNodeId().equals(thisAndOrNodeId)){
                //还在当前节点
                if(!enterBefore){
                    if(thisAndOr.isEmpty()){
                        //放入当前的那个点
                        thisAndOr.add(curr);
                    }
                    thisAndOr.add(progressNodes.get(i));
                }
                //如果在上个节点了
                else{
                    if(beforeAndOr.isEmpty()){
                        //放入假设的那个点
                        beforeAndOr.add(beforeAndOrNode);
                        //如果 上个节点也是 会签|或签 那么就置为null
                        beforeNode = null;
                    }
                    beforeAndOr.add(progressNodes.get(i));
                }
            }else{
                thisAndOrNodeId = progressNodes.get(i).getNodeId();
                //存在假设可能
                beforeAndOrNode = progressNodes.get(i);
                if(!enterBefore){
                    beforeNode = progressNodes.get(i);
                    enterBefore = Boolean.TRUE;
                    continue;
                }
                break;
            }
        }
        return beforeNode;
    }

    /**
     * 1.判断 当前 撤回人 是否是 发起人
     * 2.并且流程 尚未开始审批
     * @return
     */
    public boolean canRootWithDraw(HistoricProcessInstance instance,String employeeCode) {
        String startUserId = instance.getStartUserId();
        // 如果是申请延期或者申请借阅审批流程,流程发起人不能撤回，直接返回false
        String instanceId = instance.getId();
        ProjectInst projectInst = projectInstService.getOne(Wrappers.lambdaQuery(ProjectInst.class)
                .eq(ProjectInst::getInstCode, instanceId));
        Integer instType = projectInst.getInstType();

        if (InstTypeEnum.APPLY_DELAY.getCode().equals(instType) ||
                InstTypeEnum.APPLY_BORROW.getCode().equals(instType)){
            return Boolean.FALSE;
        }
        //如果流程发起人 不是 当前登录人 直接返回false
        if(StringUtils.isBlank(employeeCode) || StringUtils.isBlank(startUserId)
                || !employeeCode.equals(startUserId)){
            return Boolean.FALSE;
        }

        List<HistoricActivityInstance> finished = historyService.createHistoricActivityInstanceQuery()
                .processInstanceId(instance.getId()).finished()
                .activityType(TodoCenterConstant.Handler.ACTIVITY_APPROVAL)
                .orderByHistoricActivityInstanceEndTime().asc().list();
        List<HistoricActivityInstance> instances = finished.stream()
                .filter(a -> !HisProInsEndActId.WITHDRAW.equals(a.getDeleteReason()))
                .collect(Collectors.toList());
        //如果有已经被审核过的 节点 返回false
        if(CollUtil.isNotEmpty(instances)){
            return Boolean.FALSE;
        }

        return Boolean.TRUE;
    }
}
