package cn.axzo.workflow.core.engine.listener;

import cn.axzo.workflow.common.constant.BpmnConstants;
import cn.axzo.workflow.common.enums.ApproverEmptyHandleTypeEnum;
import cn.axzo.workflow.common.enums.ApproverSpecifyEnum;
import cn.axzo.workflow.common.enums.BpmnFlowNodeType;
import cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner;
import cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper;
import cn.axzo.workflow.core.deletage.BpmnTaskAssigneeSelector;
import cn.axzo.workflow.core.deletage.BpmnTaskCalculateDTO;
import cn.axzo.workflow.core.deletage.BpmnTaskDelegate;
import cn.axzo.workflow.core.deletage.MockTaskAssigneeSelector;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.delegate.ExecutionListener;
import org.flowable.engine.impl.util.ProcessDefinitionUtil;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;

import static cn.axzo.workflow.common.constant.BpmnConstants.BPM_ALLOW_SKIP_USER_TASK;
import static cn.axzo.workflow.common.constant.BpmnConstants.DUMMY_ASSIGNEE;
import static cn.axzo.workflow.common.constant.BpmnConstants.DUMMY_ASSIGNEE_TYPE;
import static cn.axzo.workflow.common.constant.BpmnConstants.FLOW_SERVER_VERSION_121;
import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT;
import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_INITIATOR;
import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_TASK_RELATION_ASSIGNEE_LIST_INFO;
import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_BUSINESS;
import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_STARTER;
import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_TASK;
import static cn.axzo.workflow.common.model.request.bpmn.task.BpmnTaskDelegateAssigner.buildDummyAssigner;
import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getApprovalMethod;
import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getApproverEmptyHandleType;
import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getApproverSpecify;
import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getNodeType;
import static cn.axzo.workflow.core.common.utils.BpmnMetaParserHelper.getProcessServerVersion;
import static cn.axzo.workflow.core.engine.cmd.helper.CustomTaskHelper.removeDuplicateByPersonId;


/**
 * 每一个任务节点创建 Execution 时执行的监听, 用于 BPMN 文件中为节点设置指定类型监听
 * <p>
 * 在当前工程中,主要是在这里设置审批人
 */
@Component
@RefreshScope
@Slf4j
public class EngineExecutionStartListener implements ExecutionListener {
    private static final long serialVersionUID = 1L;
    @Resource
    private ObjectProvider<BpmnTaskDelegate> bpmTaskDelegate;
    @Resource
    @Lazy
    private List<BpmnTaskAssigneeSelector> selectors;
    @Value("${workflow.api.timeout:10}")
    private Long apiTimeout;
    @Value("${workflow.mock:false}")
    private Boolean mock;
    @Value("${workflow.assignee.global:true}")
    private Boolean global;
    @Value("${workflow.assignee.category:''}")
    private String category;
    @Value("#{${workflow.assignee.map:{}}}")
    private Map<String, String> assigneeMap;

    @Override
    public void notify(DelegateExecution execution) {
        String currentActivityId = execution.getCurrentActivityId();
        String assigneeListVariableName = INTERNAL_TASK_RELATION_ASSIGNEE_LIST_INFO + currentActivityId;
        if (execution.hasVariable(assigneeListVariableName)) {
            return;
        }
        Process mainProcess = ProcessDefinitionUtil.getBpmnModel(execution.getProcessDefinitionId()).getMainProcess();
        UserTask userTask = (UserTask) mainProcess.getFlowElement(currentActivityId);

        // 从 version=1.2.1-SNAPSHOT 开始，才给 process 节点增加了 serverVersion 属性
        Optional<String> processServerVersion = getProcessServerVersion(mainProcess);
        if (processServerVersion.isPresent()) {
            calcTaskAssigner121(execution, userTask, processServerVersion.get(), assigneeListVariableName,
                    currentActivityId);
        } else {
            calcTaskAssignerDefault(execution, userTask, currentActivityId, assigneeListVariableName);
        }
    }

    private void calcTaskAssigner121(DelegateExecution execution, UserTask userTask, String processServerVersion,
                                     String assigneeListVariableName, String currentActivityId) {
        Optional<BpmnFlowNodeType> nodeType = getNodeType(userTask);
        DefaultArtifactVersion supportVersion = new DefaultArtifactVersion(FLOW_SERVER_VERSION_121);
        DefaultArtifactVersion currentVersion = new DefaultArtifactVersion(processServerVersion);
        if (currentVersion.compareTo(supportVersion) >= 0 && nodeType.isPresent()
                && (Objects.equals(NODE_TASK, nodeType.get()) || Objects.equals(NODE_BUSINESS, nodeType.get()) || Objects.equals(NODE_STARTER, nodeType.get()))) {
            if (Objects.equals(NODE_STARTER, nodeType.get())) {
                // UserTask 多实例, 该变量用于引擎
                BpmnTaskDelegateAssigner initiator = BpmnTaskDelegateAssigner.toObjectCompatible(execution.getVariable(INTERNAL_INITIATOR));

                // 仅用于节点人员快照
                execution.setVariable(INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT + currentActivityId,
                        Lists.newArrayList(initiator));
                return;
            }

            getApprovalMethod(userTask).ifPresent(method -> {
                List<String> assigneeIdList = new ArrayList<>();
                switch (method) {
                    case autoPassed:
                    case autoRejection:
                        // Do nothing.
                        // 统一由 cn.axzo.workflow.server.controller.listener.task.AutoOperatorEventListener 来处理
                        break;
                    case bizSpecify:
                        // 构建一个虚拟的审批人，避免因为没有审批人而导致流程触发了"审批人为空时"的处理逻辑, 当业务传入审批人时,需要更新这里的变量
                        BpmnTaskDelegateAssigner dummyApprover = buildDummyAssigner(DUMMY_ASSIGNEE,
                                DUMMY_ASSIGNEE_TYPE, "dummyApprover");

                        assigneeIdList.add(dummyApprover.buildAssigneeId());
                        execution.setVariable(INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT + currentActivityId, Lists.newArrayList(dummyApprover));
                        break;
                    default:
                        // 这里只会是 human 这一种情况， 因为 nobody 在转 BPMN 协议时，Activity 直接变成了 ReceiveTask 节点了。
                        List<BpmnTaskDelegateAssigner> assigners = new ArrayList<>();
                        getApproverSpecify(userTask).ifPresent(specify -> {
                            log.info("当前审批节点ID: {}, 节点名称: {}, 审批人指定方式: {}", userTask.getId(),
                                        userTask.getName(), specify.getDesc());

                            assigners.addAll(approverSelect(specify.getType(), userTask, execution, true));
                        });

                        // 审批候选人为空时的兜底
                        emptyAssigneeHandle(assigners, userTask, execution);

                        for (BpmnTaskDelegateAssigner user : assigners) {
                            assigneeIdList.add(user.buildAssigneeId());
                        }

                        execution.setVariable(INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT + currentActivityId,
                                assigners);
                        break;
                }
                // UserTask 多实例, 该变量用于引擎
                execution.setVariable(assigneeListVariableName, assigneeIdList);
            });
        }
    }

    /**
     * 计算节点的待审批人为空时, 执行模型配置中的审批人为空时的处理方式
     *
     * @param assigners 节点计算的待审批人集合, 可能为空
     * @param userTask  当前节点
     * @param execution 当前执行实例
     */
    private void emptyAssigneeHandle(List<BpmnTaskDelegateAssigner> assigners, UserTask userTask,
                                     DelegateExecution execution) {
        // 审批人为空并且当前节点设置了自动跳过条件
        if (!CollectionUtils.isEmpty(assigners)) {
            return;
        }
        log.info("当前节点id: [{}], name: [{}] 审批人为空, 将执行审批人为空的兜底配置!", userTask.getId(), userTask.getName());
        getApproverEmptyHandleType(userTask).ifPresent(type -> {
            switch (type) {
                case autoPassed:
                case autoRejection:
                    // Do nothing.
                    // 自动通过和驳回, 统一由 cn.axzo.workflow.server.controller.listener.task.AutoOperatorEventListener 来处理
                    break;
                case transferToAdmin:
                    assigners.addAll(approverSelect(ApproverEmptyHandleTypeEnum.transferToAdmin.getType(), userTask,
                            execution, true));
                    break;
                case specifyAssignee:
                    List<BpmnTaskDelegateAssigner> emptyAssignees =
                            BpmnMetaParserHelper.getEmptyApproverSpecify(userTask)
                                    .map(listStr -> JSON.parseArray(listStr, BpmnTaskDelegateAssigner.class))
                                    .orElse(Collections.emptyList());
                    assigners.addAll(emptyAssignees);
                default:
                    break;
            }
        });
    }

    /**
     * 根据审批人指定类型查询审批人
     *
     * @param type     "审批人指定"方式 {@link ApproverSpecifyEnum}
     * @param flowElement 当前节点, 这个对象会包含配置元数据, 可以在该方法中或者基于 BpmnTaskAssigneeSelector 建一个抽象类, 做解析元数据公共方法
     * @return
     */
    public List<BpmnTaskDelegateAssigner> approverSelect(String type, FlowElement flowElement,
                                                         DelegateExecution execution,
                                                         Boolean throwException) {
        List<BpmnTaskDelegateAssigner> assigners = new ArrayList<>();

        // 如果开启了 mock 模式（workflow.mock=true）, 则根据 mock 配置去选择审批人
        /*
         * nacos 中的的示例配置
         * workflow:
         *   mock: true # 默认为 false, 开启 mock
         *   assignee:
         *     global: false  # 默认为 true, 是否开启所有流程都使用该配置,
         *     category: '16'  # 只有 global=false 时，只针对这一个业务分类编号的流程实例有效
         *     # 自定义节点的审批人。【当节点配置的审批方式不等于自动通过或拒绝时，优先级最高！同时上面的 mock 也必须为 true】
         *     # map 中的 key = activityId = taskDefinitionKey； value 是 List<BpmnTaskDelegateAssigner> 的 JSON 字符串
         *     map: "{
         *       node_747571121516: '[{\"assignerName\":\"王庆\",\"ouId\":\"5140\",\"tenantId\":\"195\",
         * \"personId\":\"85047\",\"assignee\":\"2001205\",\"assigneeType\":\"3\"}]',
         *       node_747618945956: '[{\"assignerName\":\"杨秋菊\",\"ouId\":\"5140\",\"tenantId\":\"195\",
         * \"personId\":\"80792\",\"assignee\":\"2000028\",\"assigneeType\":\"3\"}]',
         *       node_747602616511: '[{\"assignerName\":\"杨凯\",\"ouId\":\"5140\",\"tenantId\":\"195\",
         * \"personId\":\"2390\",\"assignee\":\"2004125\",\"assigneeType\":\"3\"}]',
         *       node_868689511037: '[{\"assignerName\":\"邓九洲\",\"ouId\":\"5140\",\"tenantId\":\"195\",
         * \"personId\":\"89508\",\"assignee\":\"2000560\",\"assigneeType\":\"3\"}]'
         *     }"
         */
        if ((mock && global) ||
                (mock && !global && Objects.equals(category, execution.getProcessDefinitionId().split(":")[0]))) {
            log.info("当前系统 Nacos 配置中开启了 mock: {}, 将使用 mock 方式查找审批人", mock);
            assigners.addAll(new MockTaskAssigneeSelector(assigneeMap, global, category)
                    .select(flowElement, execution, throwException));
        } else {
            selectors.forEach(select -> {
                if (select.support(type)) {
                    log.info("审批任务节点 Id:{}, Name: {}, 审批人指定枚举: {}", flowElement.getId(), flowElement.getName(), type);
                    List<BpmnTaskDelegateAssigner> selected = select.select(flowElement, execution, throwException);
                    log.info("审批任务节点 Id:{} 的审批人集合为: {}", flowElement.getId(), JSONUtil.toJsonStr(selected));
                    assigners.addAll(selected);
                }
            });
        }

        // 按 personId 去重
        return removeDuplicateByPersonId(assigners);
    }

    /**
     * 枢智版本的逻辑, 不太兼容了，暂时先放在这里
     *
     * @param execution
     * @param userTask
     * @param currentActivityId
     * @param assigneeListVariableName
     */
    @Deprecated
    private void calcTaskAssignerDefault(DelegateExecution execution, UserTask userTask, String currentActivityId,
                                         String assigneeListVariableName) {

        BpmnTaskCalculateDTO calculateDTO = new BpmnTaskCalculateDTO();
        calculateDTO.setTaskId(userTask.getId());
        calculateDTO.setCategory(userTask.getCategory());
        calculateDTO.setProcessDefinitionId(execution.getProcessDefinitionId());
        calculateDTO.setProcessInstanceId(execution.getProcessInstanceId());
        calculateDTO.setTaskName(userTask.getName());
        calculateDTO.setTenantId(execution.getTenantId());
        calculateDTO.setTaskDefinitionKey(currentActivityId);
        calculateDTO.setVariables(execution.getVariables());

        bpmTaskDelegate.ifAvailable(delegate -> {
            List<BpmnTaskDelegateAssigner> assigners = delegate.calculateAssignerAtExecution(calculateDTO);
            List<String> assigneeIdList = new ArrayList<>();
            if (!CollectionUtils.isEmpty(assigners)) {
                for (BpmnTaskDelegateAssigner user : assigners) {
                    assigneeIdList.add(user.buildAssigneeId());
                }
                // 审批人为空并且当前节点设置了自动跳过条件
            } else if (StringUtils.hasLength(userTask.getSkipExpression())) {
                // 自动通过的默认引擎参数必须设置为 true
                execution.setTransientVariable(BpmnConstants.FLOWABLE_SKIP_EXPRESSION_ENABLE, true);
                // 设置当前 UserTask 使用的 skip 表达式
                execution.setTransientVariable(BPM_ALLOW_SKIP_USER_TASK, true);
            }


            // UserTask 非多实例
            if (!userTask.hasMultiInstanceLoopCharacteristics()) {
                if (!CollectionUtils.isEmpty(assigneeIdList)) {
                    userTask.setAssignee(assigneeIdList.get(0));
                }
            } else {
                // UserTask 多实例, 该变量用于引擎
                execution.setVariable(assigneeListVariableName, assigneeIdList);
            }
            // 将当次审批节点下计算出来的人存储起来,方便后续对 task 保持审批人快照
            execution.setVariableLocal(INTERNAL_ACTIVITY_RELATION_ASSIGNEE_LIST_INFO_SNAPSHOT + currentActivityId,
                    assigners);
        });
    }

    public Long getApiTimeout() {
        return apiTimeout;
    }
}
