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.enums.CarbonCopyObjectType;
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.BpmnCarbonCopyConf;
import cn.axzo.workflow.common.model.request.bpmn.BpmnCarbonCopyProperty;
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 cn.axzo.workflow.common.model.request.bpmn.BpmnNoticeProperty;
import cn.axzo.workflow.common.model.request.bpmn.BpmnPendingProperty;
import cn.axzo.workflow.common.model.request.bpmn.BpmnSmsProperty;
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.Task;
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_CARBON_COPIES;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_CARBON_COPY;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_CARBON_COPY_OBJECT;
import static cn.axzo.workflow.common.constant.BpmnConstants.CONFIG_CARBON_COPY_SPECIFY;
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_CHECKED;
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_CARBON_COPY_MESSAGE_ID;
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));
    }

    /**
     * 获取流程模型全局兜底的消息模板相关配置
     *
     * @param process
     * @return
     */
    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)) {
                BpmnNoticeProperty notice = new BpmnNoticeProperty();
                notice.setNoticeMessageId(v.get(0).getAttributeValue(null, ELEMENT_ATTRIBUTE_VALUE));
                notice.setViewJson(v.get(0).getElementText());
                conf.setNotice(notice);
            } else if (TEMPLATE_CARBON_COPY_MESSAGE_ID.equals(k)) {
                BpmnCarbonCopyProperty carbonCopy = new BpmnCarbonCopyProperty();
                carbonCopy.setCarbonCopyMessageId(v.get(0).getAttributeValue(null, ELEMENT_ATTRIBUTE_VALUE));
                carbonCopy.setViewJson(v.get(0).getElementText());
                conf.setCarbonCopy(carbonCopy);
            } else if (TEMPLATE_PENDING_MESSAGE_ID.equals(k)) {
                BpmnPendingProperty pending = new BpmnPendingProperty();
                pending.setPendingMessageId(v.get(0).getAttributeValue(null, ELEMENT_ATTRIBUTE_VALUE));
                pending.setViewJson(v.get(0).getElementText());
                conf.setPending(pending);
            } else if (TEMPLATE_SMS_MESSAGE_ID.equals(k)) {
                BpmnSmsProperty sms = new BpmnSmsProperty();
                sms.setSmsId(v.get(0).getAttributeValue(null, ELEMENT_ATTRIBUTE_VALUE));
                sms.setViewJson(v.get(0).getElementText());
                conf.setSms(sms);
            }
        });
        return Optional.of(conf);
    }

    /**
     * 获取指定审批节点的待办消息模板配置内容
     *
     * @param userTask
     * @return
     */
    public static Optional<BpmnPendingProperty> getNodePendingConfig(UserTask userTask) {
        return defaultValid(userTask, TEMPLATE_PENDING_MESSAGE_ID).map(element -> {
            BpmnPendingProperty pendingProperty = new BpmnPendingProperty();
            pendingProperty.setPendingMessageId(element.getAttributeValue(null, ELEMENT_ATTRIBUTE_VALUE));
            pendingProperty.setViewJson(element.getElementText());
            return pendingProperty;
        });
    }

    /**
     * 获取抄送节点(ServiceTask) 下的抄送配置元数据
     * <p>
     * 目前抄送统一使用 ServiceTask 来实现
     *
     * @param serviceTask
     * @return
     */
    public static Optional<List<BpmnCarbonCopyConf>> getCarbonCopyConfigs(ServiceTask serviceTask) {
        return parseCarbonCopyConfig(serviceTask.getExtensionElements().getOrDefault(CONFIG_CARBON_COPIES,
                Collections.emptyList()));
    }

    private static Optional<List<BpmnCarbonCopyConf>> parseCarbonCopyConfig(List<ExtensionElement> elements) {
        if (CollectionUtils.isEmpty(elements) || CollectionUtils.isEmpty(elements.get(0).getChildElements())) {
            return Optional.empty();
        }
        List<BpmnCarbonCopyConf> confs = new ArrayList<>();
        elements.get(0).getChildElements().getOrDefault(CONFIG_CARBON_COPY, Collections.emptyList()).forEach(e -> {
            BpmnCarbonCopyConf bpmnCarbonCopyConf = new BpmnCarbonCopyConf();
            buildCarbonCopyConfig(e, bpmnCarbonCopyConf);
            confs.add(bpmnCarbonCopyConf);
        });
        return Optional.of(confs);
    }

    private static void buildCarbonCopyConfig(ExtensionElement element, BpmnCarbonCopyConf bpmnCarbonCopyConf) {
        String carbonCopyObject = element.getChildElements().get(CONFIG_CARBON_COPY_OBJECT)
                .get(0).getAttributeValue(null, ELEMENT_ATTRIBUTE_VALUE);
        bpmnCarbonCopyConf.setCarbonCopyObjectType(CarbonCopyObjectType.valueOfType(carbonCopyObject));

        String carbonCopySpecify = element.getChildElements().get(CONFIG_CARBON_COPY_SPECIFY).get(0).getElementText();
        bpmnCarbonCopyConf.setSpecifyValue(carbonCopySpecify);
    }

    public static Optional<BpmnButtonConf> getButtonConfig(Process process) {
        return parseButtonConfig(process.getExtensionElements().getOrDefault(CONFIG_BUTTON, Collections.emptyList()));
    }

    private static Optional<BpmnButtonConf> parseButtonConfig(List<ExtensionElement> elements) {
        if (CollectionUtils.isEmpty(elements) || CollectionUtils.isEmpty(elements.get(0).getChildElements())) {
            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);
    }

    /**
     * 获取按钮配置信息
     *
     * @param process           流程定义元数据
     * @param taskDefinitionKey 可为空,获取默认的流程兜底的按钮配置
     * @return
     */
    public static Optional<BpmnButtonConf> getButtonConfig(Process process, String taskDefinitionKey) {
        if (StringUtils.hasLength(taskDefinitionKey)) {
            Task task = (Task) process.getFlowElement(taskDefinitionKey);
            if (Objects.isNull(task)) {
                return getButtonConfig(process);
            } else {
                Optional<BpmnButtonConf> optConfig =
                        parseButtonConfig(task.getExtensionElements().getOrDefault(CONFIG_BUTTON,
                                Collections.emptyList()));
                if (optConfig.isPresent()) {
                    return optConfig;
                } else {
                    return getButtonConfig(process);
                }
            }
        } else {
            return getButtonConfig(process);
        }
    }

    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_CHECKED)));
            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(FlowElement flowElement) {
        return defaultValid(flowElement, 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<String> getApproverSpecifyValue(UserTask userTask) {
        return defaultValid(userTask, CONFIG_APPROVER_SPECIFY).map(element -> StringUtils.hasLength(element.getElementText()) ? element.getElementText() : "[]");
    }

    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)));
    }

    public static Optional<String> getEmptyApproverSpecify(UserTask userTask) {
        return defaultValid(userTask, CONFIG_APPROVER_EMPTY_HANDLE_TYPE).map(element -> StringUtils.hasLength(element.getElementText()) ? element.getElementText() : "[]");
    }


    private static Optional<ExtensionElement> defaultValid(FlowElement flowElement, String elementName) {
        if (Objects.isNull(flowElement)) {
            return Optional.empty();
        }
        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 flowElement, DelegateExecution execution,
                                                BpmnModel bpmnModel) {
        if (Objects.isNull(bpmnModel)) {
            bpmnModel = ProcessDefinitionUtil.getBpmnModel(execution.getProcessDefinitionId());
        }
        if (flowElement instanceof UserTask) {
            UserTask task = (UserTask) flowElement;
            if (CollectionUtils.isEmpty(task.getIncomingFlows())) {
                return Optional.empty();
            }
            //preNode
            FlowElement preFlowElement = bpmnModel.getFlowElement(task.getIncomingFlows().get(0).getSourceRef());

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