package cn.axzo.workflow.core.common.utils;

import cn.axzo.workflow.common.enums.ApprovalMethodEnum;
import cn.axzo.workflow.common.enums.ApproverEmptyHandleTypeEnum;
import cn.axzo.workflow.common.enums.ApproverScopeEnum;
import cn.axzo.workflow.common.enums.ApproverSpecifyEnum;
import cn.axzo.workflow.common.enums.BpmnFlowNodeType;
import cn.axzo.workflow.common.model.request.bpmn.BpmnButtonConf;
import cn.axzo.workflow.common.model.request.bpmn.BpmnButtonMetaInfo;
import cn.axzo.workflow.common.model.request.bpmn.BpmnFieldConf;
import cn.axzo.workflow.common.model.request.bpmn.BpmnFieldOptionConf;
import cn.axzo.workflow.common.model.request.bpmn.BpmnNoticeConf;
import com.alibaba.fastjson.JSON;
import com.google.common.collect.Lists;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.ExtensionElement;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.ReceiveTask;
import org.flowable.bpmn.model.ServiceTask;
import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.impl.util.ProcessDefinitionUtil;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_APPROVAL_METHOD;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_APPROVER_EMPTY_HANDLE_TYPE;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_APPROVER_SCOPE;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_APPROVER_SPECIFY;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_BUTTON;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_BUTTON_META;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_BUTTON_TYPE_CARBON_COPY;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_BUTTON_TYPE_CURRENT;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_BUTTON_TYPE_HISTORY;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_BUTTON_TYPE_INITIATOR;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_FIELD;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_FIELD_META;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_FIELD_OPTION;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_NODE_TYPE;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_NOTICE;
import static cn.axzo.workflow.common.constant.BpmnConstants.ELEMENT_ATTRIBUTE_CODE;
import static cn.axzo.workflow.common.constant.BpmnConstants.ELEMENT_ATTRIBUTE_DISABLED;
import static cn.axzo.workflow.common.constant.BpmnConstants.ELEMENT_ATTRIBUTE_KEY;
import static cn.axzo.workflow.common.constant.BpmnConstants.ELEMENT_ATTRIBUTE_NAME;
import static cn.axzo.workflow.common.constant.BpmnConstants.ELEMENT_ATTRIBUTE_ORDER;
import static cn.axzo.workflow.common.constant.BpmnConstants.ELEMENT_ATTRIBUTE_TYPE;
import static cn.axzo.workflow.common.constant.BpmnConstants.ELEMENT_ATTRIBUTE_VALUE;
import static cn.axzo.workflow.common.constant.BpmnConstants.FLOW_SERVER_VERSION;
import static cn.axzo.workflow.common.constant.BpmnConstants.TEMPLATE_NOTICE_MESSAGE_ID;
import static cn.axzo.workflow.common.constant.BpmnConstants.TEMPLATE_PENDING_MESSAGE_ID;
import static cn.axzo.workflow.common.constant.BpmnConstants.TEMPLATE_SMS_MESSAGE_ID;

/**
 * 协助解析 BPMN 文件中的自定义扩展字段和属性
 *
 * @author wangli
 * @since 2023/11/15 22:51
 */
public final class BpmnMetaParserHelper {
    private BpmnMetaParserHelper() {}

    public static Optional<String> getProcessServerVersion(Process process) {
        return Optional.ofNullable(process.getAttributeValue(null, FLOW_SERVER_VERSION));
    }

    public static Optional<BpmnNoticeConf> getNoticeConfig(Process process) {
        List<ExtensionElement> elements = process.getExtensionElements().getOrDefault(CONFIG_NOTICE,
                Collections.emptyList());
        if (CollectionUtils.isEmpty(elements)) {
            return Optional.empty();
        }
        BpmnNoticeConf conf = new BpmnNoticeConf();
        elements.get(0).getChildElements().forEach((k, v) -> {
            if (TEMPLATE_NOTICE_MESSAGE_ID.equals(k)) {
                conf.setNoticeMessageId(v.get(0).getAttributeValue(null, ELEMENT_ATTRIBUTE_VALUE));
            } else if (TEMPLATE_PENDING_MESSAGE_ID.equals(k)) {
                conf.setPendingMessageId(v.get(0).getAttributeValue(null, ELEMENT_ATTRIBUTE_VALUE));
            } else if (TEMPLATE_SMS_MESSAGE_ID.equals(k)) {
                conf.setSmsId(v.get(0).getAttributeValue(null, ELEMENT_ATTRIBUTE_VALUE));
            }
        });
        return Optional.of(conf);
    }

    public static Optional<BpmnButtonConf> getButtonConfig(Process process) {
        List<ExtensionElement> elements = process.getExtensionElements().getOrDefault(CONFIG_BUTTON,
                Collections.emptyList());
        if (CollectionUtils.isEmpty(elements)) {
            return Optional.empty();
        }
        BpmnButtonConf conf = new BpmnButtonConf();
        elements.get(0).getChildElements().forEach((k, v) -> {
            List<BpmnButtonMetaInfo> buttonMetaInfos = new ArrayList<>();
            buildButtonMetaInfo(v, buttonMetaInfos);
            if (CONFIG_BUTTON_TYPE_INITIATOR.equals(k)) {
                conf.setInitiator(buttonMetaInfos);
            } else if (CONFIG_BUTTON_TYPE_CURRENT.equals(k)) {
                conf.setCurrent(buttonMetaInfos);
            } else if (CONFIG_BUTTON_TYPE_HISTORY.equals(k)) {
                conf.setHistory(buttonMetaInfos);
            } else if (CONFIG_BUTTON_TYPE_CARBON_COPY.equals(k)) {
                conf.setCarbonCopy(buttonMetaInfos);
            }
        });
        return Optional.of(conf);
    }

    private static void buildButtonMetaInfo(List<ExtensionElement> v, List<BpmnButtonMetaInfo> buttonMetaInfos) {
        v.get(0).getChildElements().get(CONFIG_BUTTON_META).forEach(i -> {
            BpmnButtonMetaInfo buttonMetaInfo = new BpmnButtonMetaInfo();
            buttonMetaInfo.setBtnKey(i.getAttributeValue(null, ELEMENT_ATTRIBUTE_KEY));
            buttonMetaInfo.setBtnName(i.getAttributeValue(null, ELEMENT_ATTRIBUTE_NAME));
            buttonMetaInfo.setChecked(Boolean.valueOf(i.getAttributeValue(null, ELEMENT_ATTRIBUTE_VALUE)));
            buttonMetaInfo.setOrder(Integer.valueOf(i.getAttributeValue(null, ELEMENT_ATTRIBUTE_ORDER)));
            buttonMetaInfo.setDisabled(Boolean.valueOf(i.getAttributeValue(null, ELEMENT_ATTRIBUTE_DISABLED)));
            buttonMetaInfos.add(buttonMetaInfo);
        });
    }

    public static Optional<List<BpmnFieldConf>> getFieldConfig(Process process) {
        List<ExtensionElement> elements = process.getExtensionElements().getOrDefault(CONFIG_FIELD,
                Collections.emptyList());
        if (CollectionUtils.isEmpty(elements)) {
            return Optional.empty();
        }
        List<BpmnFieldConf> fields = new ArrayList<>();
        elements.get(0).getChildElements().getOrDefault(CONFIG_FIELD_META, Collections.emptyList()).forEach(i -> {
            BpmnFieldConf conf = new BpmnFieldConf();
            conf.setType(i.getAttributeValue(null, ELEMENT_ATTRIBUTE_TYPE));
            conf.setName(i.getAttributeValue(null, ELEMENT_ATTRIBUTE_NAME));
            conf.setCode(i.getAttributeValue(null, ELEMENT_ATTRIBUTE_CODE));

            List<BpmnFieldOptionConf> options = new ArrayList<>();
            if (!CollectionUtils.isEmpty(i.getChildElements())) {
                i.getChildElements().get(CONFIG_FIELD_OPTION).forEach(j -> {
                    BpmnFieldOptionConf option = new BpmnFieldOptionConf();
                    option.setName(j.getAttributeValue(null, ELEMENT_ATTRIBUTE_NAME));
                    option.setValue(j.getAttributeValue(null, ELEMENT_ATTRIBUTE_VALUE));
                    options.add(option);
                });
                conf.setOptions(options);
            }

            fields.add(conf);
        });
        return Optional.of(fields);
    }

    public static Optional<BpmnFlowNodeType> getNodeType(FlowElement flowElement) {
        if (flowElement instanceof UserTask
                || flowElement instanceof ServiceTask
                || flowElement instanceof ReceiveTask) {
            return defaultValid(flowElement, CONFIG_NODE_TYPE)
                    .map(element -> BpmnFlowNodeType.valueOf(element.getElementText()));
        }
        return Optional.empty();
    }

    public static Optional<ApprovalMethodEnum> getApprovalMethod(UserTask userTask) {
        return defaultValid(userTask, CONFIG_APPROVAL_METHOD)
                .map(element -> ApprovalMethodEnum.valueOf(element.getAttributeValue(null, ELEMENT_ATTRIBUTE_VALUE)));
    }

    public static Optional<ApproverScopeEnum> getApproverScope(UserTask userTask) {
        return defaultValid(userTask, CONFIG_APPROVER_SCOPE)
                .map(element -> ApproverScopeEnum.valueOf(element.getAttributeValue(null, ELEMENT_ATTRIBUTE_VALUE)));
    }

    public static Optional<ApproverSpecifyEnum> getApproverSpecify(UserTask userTask) {
        return defaultValid(userTask, CONFIG_APPROVER_SPECIFY)
                .map(element -> ApproverSpecifyEnum.valueOf(element.getAttributeValue(null, ELEMENT_ATTRIBUTE_VALUE)));
    }

    public static Optional<List<String>> getApproverSpecifyValue(UserTask userTask) {
        return defaultValid(userTask, CONFIG_APPROVER_SPECIFY)
                .map(element -> StringUtils.hasLength(element.getElementText())
                        ? Lists.newArrayList(JSON.parseArray(element.getElementText(), String.class))
                        : Collections.emptyList());
    }

    public static Optional<ApproverEmptyHandleTypeEnum> getApproverEmptyHandleType(UserTask userTask) {
        return defaultValid(userTask, CONFIG_APPROVER_EMPTY_HANDLE_TYPE)
                .map(element -> ApproverEmptyHandleTypeEnum.valueOf(element.getAttributeValue(null,
                        ELEMENT_ATTRIBUTE_VALUE)));
    }


    private static Optional<ExtensionElement> defaultValid(FlowElement flowElement, String elementName) {
        if (CollectionUtils.isEmpty(flowElement.getExtensionElements())
                || !flowElement.getExtensionElements().containsKey(elementName)) {
            return Optional.empty();
        }
        List<ExtensionElement> elements = flowElement.getExtensionElements().getOrDefault(elementName,
                Collections.emptyList());
        if (CollectionUtils.isEmpty(elements)) {
            return Optional.empty();
        }
        return Optional.ofNullable(elements.get(0));
    }

    public static Optional<UserTask> getPreNode(FlowElement userTask, DelegateExecution execution,
                                                BpmnModel bpmnModel) {
        if (Objects.isNull(bpmnModel)) {
            bpmnModel = ProcessDefinitionUtil.getBpmnModel(execution.getProcessDefinitionId());
        }
        if (userTask instanceof UserTask) {
            UserTask task = (UserTask) userTask;
            if (CollectionUtils.isEmpty(task.getIncomingFlows())) {
                return Optional.empty();
            }
            //preNode
            FlowElement flowElement = bpmnModel.getFlowElement(task.getIncomingFlows().get(0).getSourceRef());

            if (Objects.isNull(flowElement)) {
                return Optional.empty();
            } else if (!(flowElement instanceof UserTask)) {
                return getPreNode(flowElement, execution, bpmnModel);
            } else {
                return Optional.of((UserTask) flowElement);
            }
        }
        return Optional.empty();
    }
}
