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

import cn.axzo.workflow.common.enums.VarTypeEnum;
import cn.axzo.workflow.common.exception.WorkflowEngineException;
import cn.axzo.workflow.common.model.dto.ContactsPersonDTO;
import cn.axzo.workflow.common.model.dto.SignatureDTO;
import cn.axzo.workflow.common.model.dto.VariableObjectDTO;
import cn.axzo.workflow.common.model.request.category.CategoryGroupVarSearchDto;
import cn.axzo.workflow.common.model.response.category.CategoryGroupVarItemVo;
import cn.axzo.workflow.common.model.response.category.CategoryItemVO;
import cn.axzo.workflow.core.common.utils.SpringContextUtils;
import cn.axzo.workflow.core.service.CategoryGroupService;
import cn.axzo.workflow.core.service.CategoryService;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.flowable.common.engine.api.FlowableObjectNotFoundException;
import org.flowable.common.engine.impl.interceptor.CommandContext;
import org.flowable.engine.HistoryService;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl;
import org.flowable.engine.impl.util.CommandContextUtil;
import org.flowable.form.api.FormInfo;
import org.flowable.form.api.FormRepositoryService;
import org.flowable.form.api.FormService;
import org.flowable.form.model.FormContainer;
import org.flowable.form.model.FormField;
import org.flowable.form.model.FormFieldTypes;
import org.flowable.form.model.SimpleFormModel;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import java.io.Serializable;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

import static cn.axzo.workflow.common.code.FormModelRespCode.FORM_MODEL_NOT_EXISTS;
import static cn.axzo.workflow.common.constant.BpmnConstants.BPM_MODEL_CATEGORY;
import static cn.axzo.workflow.common.constant.BpmnConstants.INTERNAL_INITIATOR;
import static cn.axzo.workflow.common.constant.BpmnConstants.SIGNATURE_COLLECTION;
import static cn.axzo.workflow.common.constant.BpmnConstants.SIGN_VARIABLE;
import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_DEFINITION_KEY;
import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_DEFINITION_KEY_DESC;
import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_END_TIME;
import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_END_TIME_DESC;
import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_INITIATOR;
import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_INITIATOR_DESC;
import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_INSTANCE_ID;
import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_INSTANCE_ID_DESC;
import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_START_TIME;
import static cn.axzo.workflow.common.constant.VariableConstants.PRINT_VAR_PROCESS_START_TIME_DESC;
import static cn.axzo.workflow.common.enums.BpmnFlowNodeType.NODE_STARTER;

/**
 * 自定义获取流程实例中的变量命令实现
 * <p>
 * 返回的变量包括如下：
 * 1、业务名称、审批编号、发起事件、发起人对象信息、节点签名和意见
 * 2、业务管理中的变量
 * 3、表单中变量
 *
 * @author wangli
 * @since 2025-01-21 11:47
 */
@Slf4j
public class CustomGetProcessInstanceVariablesToObjectCmd extends AbstractCommand<List<VariableObjectDTO>> implements Serializable {

    private final String processInstanceId;
    private final SimpleDateFormat sdf = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss");
    private static final List<String> SUPPORTED_FORM_TYPES = Lists.newArrayList("input", "date", "textarea", "image", "contacts", "amount", "decimal");

    public CustomGetProcessInstanceVariablesToObjectCmd(String processInstanceId) {
        this.processInstanceId = processInstanceId;
    }

    @Override
    public String paramToJsonString() {
        Map<String, Object> params = new HashMap<>();
        params.put("processInstanceId", processInstanceId);
        return JSON.toJSONString(params);
    }

    @Override
    public List<VariableObjectDTO> executeInternal(CommandContext commandContext) {
        List<VariableObjectDTO> returnVariables = new ArrayList<>();
        if (!StringUtils.hasText(processInstanceId)) {
            return returnVariables;
        }
        returnVariables.add(VariableObjectDTO.builder()
                .key(PRINT_VAR_PROCESS_INSTANCE_ID)
                .desc(PRINT_VAR_PROCESS_INSTANCE_ID_DESC)
                .value(processInstanceId)
                .build());


        ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration();
        HistoryService historyService = processEngineConfiguration.getHistoryService();
        HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery()
                .processInstanceId(processInstanceId).includeProcessVariables().singleResult();
        // 添加流程开始时间
        returnVariables.add(VariableObjectDTO.builder()
                .key(PRINT_VAR_PROCESS_START_TIME)
                .desc(PRINT_VAR_PROCESS_START_TIME_DESC)
                .value(sdf.format(historicProcessInstance.getStartTime()))
                .build());
        returnVariables.add(VariableObjectDTO.builder()
                .key(PRINT_VAR_PROCESS_END_TIME)
                .desc(PRINT_VAR_PROCESS_END_TIME_DESC)
                .value(Objects.nonNull(historicProcessInstance.getEndTime()) ? sdf.format(historicProcessInstance.getEndTime()) : null)
                .build());

        Map<String, Object> processVariables = historicProcessInstance.getProcessVariables();
        // 添加流程业务 ID 和业务下分组变量信息
        addProcessDefinitionKeyAndVariables(returnVariables, (Map<String, Object>) processVariables.getOrDefault(SIGN_VARIABLE, new HashMap<String, Object>()), historicProcessInstance);

        // 发起人流程引擎内部模型，需要外部调用该命令时，再解析； 当前命令所在 module 不支持调用二方接口
        returnVariables.add(VariableObjectDTO.builder()
                .key(PRINT_VAR_PROCESS_INITIATOR)
                .desc(PRINT_VAR_PROCESS_INITIATOR_DESC)
                .value(processVariables.get(INTERNAL_INITIATOR))
                .type(VariableObjectDTO.Type.obj)
                .build());

        // 电子签名
        addSignature(returnVariables, (List<SignatureDTO>) processVariables.get(SIGNATURE_COLLECTION));

        // 表单中的变量
        addFormFieldValue(commandContext, historicProcessInstance, returnVariables, processVariables);

        return returnVariables;
    }

    private void addFormFieldValue(CommandContext commandContext, HistoricProcessInstance instance, List<VariableObjectDTO> variables, Map<String, Object> processVariables) {
        Map<String, Object> formFieldValues = new HashMap<>();
        byte[] formInstanceValue = commandContext.getCommandExecutor().execute(new CustomGetFormInstanceLatestValuesCmd(processInstanceId, false));
        if (!ObjectUtils.isEmpty(formInstanceValue)) {
            JSONObject treeNode = JSON.parseObject(new String(formInstanceValue));
            formFieldValues.putAll(treeNode.getJSONObject("values").getInnerMap());
        }

        FormRepositoryService formRepositoryService = CommandContextUtil.getFormRepositoryService(commandContext);
        FormInfo formModel;
        try {
            formModel = formRepositoryService.getFormModelByKey(instance.getProcessDefinitionKey(), instance.getTenantId(), true);
        } catch (FlowableObjectNotFoundException e) {
            log.warn("can't found form model");
            throw new WorkflowEngineException(FORM_MODEL_NOT_EXISTS);
        }
        if (CollectionUtils.isEmpty(formFieldValues)) {

            FormService formService = CommandContextUtil.getFormService(commandContext);
            formFieldValues.putAll(formService.getVariablesFromFormSubmission(formModel, processVariables, null));
        }

        if (CollectionUtils.isEmpty(formFieldValues)) {
            return;
        }


        List<FormField> formFields = ((SimpleFormModel) formModel.getFormModel()).getFields();

        formFields.forEach(formField -> {
            FormContainer formContainer = (FormContainer) formField;
            formContainer.getFields().get(0).forEach(field -> {
                if (!(field instanceof FormContainer) && SUPPORTED_FORM_TYPES.contains(field.getType())) {
                    Object fieldValue = formFieldValues.getOrDefault(field.getId(), null);
                    if (Objects.nonNull(fieldValue)) {
                        if (Objects.equals(field.getType(), FormFieldTypes.AMOUNT)) {
                            if (StringUtils.hasText(fieldValue.toString())) {
                                JSONObject jsonObject = JSON.parseObject(fieldValue.toString());
                                BigDecimal standardNumerals = jsonObject.getBigDecimal("standardNumerals");
                                if (Objects.nonNull(standardNumerals)) {
                                    variables.add(VariableObjectDTO.builder()
                                            .key(field.getId() + "_standardNumerals")
                                            .desc(field.getName() + "小写")
                                            .value(standardNumerals)
                                            .type(convert(field.getType()))
                                            .build());
                                }
                                if (Boolean.parseBoolean(field.getParam("toUpper").toString())) {
                                    String uppercaseNumerals = jsonObject.getString("uppercaseNumerals");
                                    if (StringUtils.hasText(uppercaseNumerals)) {
                                        variables.add(VariableObjectDTO.builder()
                                                .key(field.getId() + "_uppercaseNumerals")
                                                .desc(field.getName() + "大写")
                                                .value(uppercaseNumerals)
                                                .type(convert(field.getType()))
                                                .build());
                                    }
                                }
                            }
                        } else if (Objects.equals(field.getType(), "contacts")) {
                            if (StringUtils.hasText(fieldValue.toString())
                                    && fieldValue.toString().startsWith("[")
                                    && fieldValue.toString().endsWith("]")) {
                                List<String> names = JSON.parseArray((String) fieldValue, ContactsPersonDTO.class)
                                        .stream().map(ContactsPersonDTO::getRealName).collect(Collectors.toList());
                                variables.add(VariableObjectDTO.builder()
                                        .key(field.getId())
                                        .desc(field.getName())
                                        .value(StringUtils.collectionToCommaDelimitedString(names))
                                        .type(convert(field.getType()))
                                        .build());
                            }
                        } else if (Objects.equals(field.getType(), "decimal")) {
                            if (StringUtils.hasText(fieldValue.toString())) {
                                variables.add(VariableObjectDTO.builder()
                                        .key(field.getId())
                                        .desc(field.getName())
                                        .value(fieldValue + String.valueOf(field.getParam("unit")))
                                        .type(convert(field.getType()))
                                        .build());
                            }
                        } else {
                            variables.add(VariableObjectDTO.builder()
                                    .key(field.getId())
                                    .desc(field.getName())
                                    .value(fieldValue)
                                    .type(convert(field.getType()))
                                    .build());
                        }
                    }
                }
            });
        });
    }

    private void addSignature(List<VariableObjectDTO> variables, List<SignatureDTO> signatures) {
        if (CollectionUtils.isEmpty(signatures)) {
            return;
        }
        signatures.forEach(sign -> {
            if (!Objects.equals(NODE_STARTER.getType(), sign.getActivityId())) {
                variables.add(VariableObjectDTO.builder()
                        .key(sign.getActivityId())
                        .desc(sign.getActivityName())
                        .value(sign.getSignatures())
                        .type(VariableObjectDTO.Type.signatureAndAdvice)
                        .build());
            }
        });
    }

    private void addProcessDefinitionKeyAndVariables(List<VariableObjectDTO> variables, Map<String, Object> bizVariables, HistoricProcessInstance historicProcessInstance) {
        CategoryService categoryService = SpringContextUtils.getBean(CategoryService.class);
        categoryService.get(BPM_MODEL_CATEGORY, historicProcessInstance.getProcessDefinitionKey()).ifPresent(category -> {
            variables.add(VariableObjectDTO.builder()
                    .key(PRINT_VAR_PROCESS_DEFINITION_KEY)
                    .desc(PRINT_VAR_PROCESS_DEFINITION_KEY_DESC)
                    .value(category.getLabel() + "(" + category.getValue() + ")")
                    .build());
            // 添加业务下的变量
            addVariables(variables, category, bizVariables);
        });
    }

    private void addVariables(List<VariableObjectDTO> variables, CategoryItemVO category, Map<String, Object> bizVariables) {
        CategoryGroupService categoryServiceGroup = SpringContextUtils.getBean(CategoryGroupService.class);
        List<CategoryGroupVarItemVo> groupVariables = categoryServiceGroup.searchGroupAndVarList(CategoryGroupVarSearchDto.builder()
                .dictId(category.getId())
                .category(category.getValue())
                .build());

        groupVariables.forEach(group -> {
            if (!CollectionUtils.isEmpty(group.getVars())) {
                group.getVars().forEach(variable -> {
                    Object value = bizVariables.getOrDefault(variable.getCode(), null);
                    if (Objects.isNull(value)) {
                        // 目前为了减少 wps 替换耗时，所以将没有值的变量，直接踢出
                        return;
                    }
                    variables.add(VariableObjectDTO.builder()
                            .key(variable.getCode())
                            .desc(group.getGroupName() + variable.getName())
                            .value(value)
                            .type(convert(variable.getType()))
                            .build());
                });
            }
        });


    }

    private VariableObjectDTO.Type convert(VarTypeEnum varType) {
        switch (varType) {
            case TEXT:
                return VariableObjectDTO.Type.text;
            case PICTURE:
                return VariableObjectDTO.Type.img;
            default:
                return VariableObjectDTO.Type.obj;
        }
    }

    private VariableObjectDTO.Type convert(String type) {
        switch (type) {
            case "input":
            case "text":
            case "date":
            case "textarea":
            case "amount":
            case "contacts":
            case "decimal":
                return VariableObjectDTO.Type.text;
            case "image":
                return VariableObjectDTO.Type.img;
            default:
                return VariableObjectDTO.Type.obj;
        }
    }

}
