package com.njcn.bpm.strategy;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import com.google.common.annotations.VisibleForTesting;
import com.njcn.bpm.enums.BpmTaskCandidateStrategyEnum;
import com.njcn.bpm.utils.BpmnModelUtils;
import com.njcn.bpm.utils.CollectionUtils;
import com.njcn.common.pojo.enums.common.DataStateEnum;
import com.njcn.common.pojo.exception.BusinessException;
import com.njcn.user.api.UserFeignClient;
import com.njcn.user.pojo.vo.UserVO;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.delegate.DelegateExecution;

import java.util.*;


/**
 * @author hongawen
 */
@Slf4j
public class BpmTaskCandidateInvoker {

    private final Map<BpmTaskCandidateStrategyEnum, IBpmTaskCandidateStrategy> strategyMap = new HashMap<>();

    private final UserFeignClient adminUserApi;

    public BpmTaskCandidateInvoker(List<IBpmTaskCandidateStrategy> strategyList,
                                   UserFeignClient adminUserApi) {
        strategyList.forEach(strategy -> {
            IBpmTaskCandidateStrategy oldStrategy = strategyMap.put(strategy.getStrategy(), strategy);
            Assert.isNull(oldStrategy, "策略(%s) 重复", strategy.getStrategy());
        });
        this.adminUserApi = adminUserApi;
    }

    /**
     * 校验流程模型的任务分配规则全部都配置了
     * 目的：如果有规则未配置，会导致流程任务找不到负责人，进而流程无法进行下去！
     *
     * @param bpmnBytes BPMN XML
     */
    public void validateBpmnConfig(byte[] bpmnBytes) {
        BpmnModel bpmnModel = BpmnModelUtils.getBpmnModel(bpmnBytes);
        assert bpmnModel != null;
        List<UserTask> userTaskList = BpmnModelUtils.getBpmnModelElements(bpmnModel, UserTask.class);
        // 遍历所有的 UserTask，校验审批人配置
        userTaskList.forEach(userTask -> {
            // 1. 非空校验
            Integer strategy = BpmnModelUtils.parseCandidateStrategy(userTask);
            String param = BpmnModelUtils.parseCandidateParam(userTask);
            if (strategy == null) {
                throw new BusinessException("部署流程失败，原因：BPMN 流程图中，用户任务(" + userTask.getName() + ")的名字不存在");
            }
            IBpmTaskCandidateStrategy candidateStrategy = getCandidateStrategy(strategy);
            if (candidateStrategy.isParamRequired() && StrUtil.isBlank(param)) {
                throw new BusinessException("部署流程失败，原因：用户任务(" + userTask.getName() + ")未配置审批人，请点击【流程设计】按钮，选择该它的【任务（审批人）】进行配置");
            }
            // 2. 具体策略校验
            getCandidateStrategy(strategy).validateParam(param);
        });
    }

    /**
     * 计算任务的候选人
     *
     * @param execution 执行任务
     * @return 用户编号集合
     */
    public List<String> calculateUsers(DelegateExecution execution) {
        Integer strategy = BpmnModelUtils.parseCandidateStrategy(execution.getCurrentFlowElement());
        String param = BpmnModelUtils.parseCandidateParam(execution.getCurrentFlowElement());
        // 1.1 计算任务的候选人
        List<String> userIds = getCandidateStrategy(strategy).calculateUsers(execution, param);
        // 1.2 移除被禁用的用户
        removeDisableUsers(userIds);

        // 2. 校验是否有候选人
        if (CollUtil.isEmpty(userIds)) {
            log.error("[calculateUsers][流程任务({}/{}/{}) 任务规则({}/{}) 找不到候选人]", execution.getId(),
                    execution.getProcessDefinitionId(), execution.getCurrentActivityId(), strategy, param);
            throw new BusinessException("操作失败，原因：找不到任务的审批人！");
        }
        return userIds;
    }

    @VisibleForTesting
    void removeDisableUsers(List<String> assigneeUserIds) {
        if (CollUtil.isEmpty(assigneeUserIds)) {
            return;
        }
        List<UserVO> users = adminUserApi.getUserVOByIdList(assigneeUserIds).getData();
        Map<String, UserVO> userMap =  CollectionUtils.convertMap(users, UserVO::getId);
        assigneeUserIds.removeIf(id -> {
            UserVO user = userMap.get(id);
            return user == null || !DataStateEnum.ENABLE.getCode().equals(user.getState());
        });
    }

    private IBpmTaskCandidateStrategy getCandidateStrategy(Integer strategy) {
        BpmTaskCandidateStrategyEnum strategyEnum = BpmTaskCandidateStrategyEnum.valueOf(strategy);
        Assert.notNull(strategyEnum, "策略(%s) 不存在", strategy);
        IBpmTaskCandidateStrategy strategyObj = strategyMap.get(strategyEnum);
        Assert.notNull(strategyObj, "策略(%s) 不存在", strategy);
        return strategyObj;
    }

}