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

import cn.axzo.workflow.common.exception.WorkflowEngineException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.flowable.common.engine.api.FlowableException;
import org.flowable.common.engine.impl.identity.Authentication;
import org.flowable.common.engine.impl.interceptor.CommandContext;
import org.flowable.form.api.FormInfo;
import org.flowable.form.api.FormInstance;
import org.flowable.form.engine.FormEngineConfiguration;
import org.flowable.form.engine.impl.cmd.CreateFormInstanceCmd;
import org.flowable.form.engine.impl.persistence.entity.FormInstanceEntity;
import org.flowable.form.engine.impl.persistence.entity.FormInstanceEntityManager;
import org.flowable.form.engine.impl.util.CommandContextUtil;
import org.flowable.form.model.FormField;
import org.flowable.form.model.FormFieldTypes;
import org.flowable.form.model.SimpleFormModel;
import org.joda.time.LocalDate;
import org.springframework.util.CollectionUtils;

import java.util.Collection;
import java.util.Collections;
import java.util.Map;

import static cn.axzo.workflow.common.code.FormInstanceRespCode.FORM_DATA_PARSE_ERROR_BY_AMOUNT;
import static cn.axzo.workflow.common.code.FormInstanceRespCode.FORM_DATA_PARSE_ERROR_BY_UPLOAD;

/**
 * 覆盖表单引擎的创建表单实例的命令
 *
 * @author wangli
 * @since 2025-01-22 16:52
 */
public class CustomCreateFormInstanceCmd extends CreateFormInstanceCmd {
    public CustomCreateFormInstanceCmd(FormInfo formInfo, Map<String, Object> variables, String taskId, String processInstanceId, String processDefinitionId, String tenantId, String outcome) {
        super(formInfo, variables, taskId, processInstanceId, processDefinitionId, tenantId, outcome);
    }

    public CustomCreateFormInstanceCmd(String formModelId, Map<String, Object> variables, String taskId, String processInstanceId, String processDefinitionId, String tenantId, String outcome) {
        super(formModelId, variables, taskId, processInstanceId, processDefinitionId, tenantId, outcome);
    }

    public CustomCreateFormInstanceCmd(FormInfo formInfo, Map<String, Object> variables, String taskId, String scopeId, String scopeType, String scopeDefinitionId, String tenantId, String outcome) {
        super(formInfo, variables, taskId, scopeId, scopeType, scopeDefinitionId, tenantId, outcome);
    }

    public CustomCreateFormInstanceCmd(String formModelId, Map<String, Object> variables, String taskId, String scopeId, String scopeType, String scopeDefinitionId, String tenantId, String outcome) {
        super(formModelId, variables, taskId, scopeId, scopeType, scopeDefinitionId, tenantId, outcome);
    }

    @Override
    public FormInstance execute(CommandContext commandContext) {
        FormEngineConfiguration formEngineConfiguration = CommandContextUtil.getFormEngineConfiguration();
        if (formInfo == null) {
            if (formModelId == null) {
                throw new FlowableException("Invalid form model and no form model Id provided");
            }
            formInfo = CommandContextUtil.getFormEngineConfiguration().getFormRepositoryService().getFormModelById(formModelId);
        }

        if (formInfo == null || formInfo.getId() == null) {
            throw new FlowableException("Invalid form model provided");
        }

        ObjectMapper objectMapper = formEngineConfiguration.getObjectMapper();
        ObjectNode submittedFormValuesJson = objectMapper.createObjectNode();

        ObjectNode valuesNode = submittedFormValuesJson.putObject("values");

        // Loop over all form fields and see if a value was provided

        SimpleFormModel formModel = (SimpleFormModel) formInfo.getFormModel();
        Map<String, FormField> fieldMap = formModel.allFieldsAsMap();
        for (String fieldId : fieldMap.keySet()) {
            FormField formField = fieldMap.get(fieldId);

            if (FormFieldTypes.EXPRESSION.equals(formField.getType()) || FormFieldTypes.CONTAINER.equals(formField.getType())) {
                continue;
            }

            if (variables != null && variables.containsKey(fieldId)) {
                Object variableValue = variables.get(fieldId);
                if (variableValue == null) {
                    valuesNode.putNull(fieldId);
                } else if (variableValue instanceof Long) {
                    valuesNode.put(fieldId, (Long) variables.get(fieldId));

                } else if (variableValue instanceof Double) {
                    valuesNode.put(fieldId, (Double) variables.get(fieldId));

                } else if (variableValue instanceof Boolean) {
                    valuesNode.put(fieldId, (Boolean) variables.get(fieldId));

                } else if (variableValue instanceof LocalDate) {
                    valuesNode.put(fieldId, ((LocalDate) variableValue).toString());
                } else if (variableValue instanceof Collection) {
                    if (CollectionUtils.isEmpty((Collection<?>) variableValue)) {
                        variableValue = Collections.emptyList();
                    }
                    try {
                        valuesNode.put(fieldId, objectMapper.writeValueAsString(variableValue));
                    } catch (JsonProcessingException e) {
                        throw new WorkflowEngineException(FORM_DATA_PARSE_ERROR_BY_UPLOAD);
                    }
                } else if (variableValue instanceof Map) {
                    if (CollectionUtils.isEmpty((Map<?, ?>) variableValue)) {
                        variableValue = Collections.emptyMap();
                    }
                    try {
                        valuesNode.put(fieldId, objectMapper.writeValueAsString(variableValue));
                    } catch (JsonProcessingException e) {
                        throw new WorkflowEngineException(FORM_DATA_PARSE_ERROR_BY_AMOUNT);
                    }
                } else {
                    valuesNode.put(fieldId, variableValue.toString());
                }
            }
        }

        if (outcome != null) {
            submittedFormValuesJson.put("flowable_form_outcome", outcome);
        }

        FormInstanceEntityManager formInstanceEntityManager = CommandContextUtil.getFormInstanceEntityManager(commandContext);
        FormInstanceEntity formInstanceEntity = findExistingFormInstance(formEngineConfiguration);

        if (formInstanceEntity == null) {
            formInstanceEntity = formInstanceEntityManager.create();
        }

        formInstanceEntity.setFormDefinitionId(formInfo.getId());
        formInstanceEntity.setTaskId(taskId);

        if (processInstanceId != null) {
            formInstanceEntity.setProcessInstanceId(processInstanceId);
            formInstanceEntity.setProcessDefinitionId(processDefinitionId);

        } else {
            formInstanceEntity.setScopeId(scopeId);
            formInstanceEntity.setScopeType(scopeType);
            formInstanceEntity.setScopeDefinitionId(scopeDefinitionId);
        }

        formInstanceEntity.setSubmittedDate(formEngineConfiguration.getClock().getCurrentTime());
        formInstanceEntity.setSubmittedBy(Authentication.getAuthenticatedUserId());

        if (tenantId != null) {
            formInstanceEntity.setTenantId(tenantId);
        }

        try {
            formInstanceEntity.setFormValueBytes(objectMapper.writeValueAsBytes(submittedFormValuesJson));
        } catch (Exception e) {
            throw new FlowableException("Error setting form values JSON", e);
        }

        if (formInstanceEntity.getId() == null) {
            formInstanceEntityManager.insert(formInstanceEntity);
        } else {
            formInstanceEntityManager.update(formInstanceEntity);
        }


        return formInstanceEntity;
    }
}
