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

import cn.axzo.workflow.common.exception.WorkflowEngineException;
import cn.axzo.workflow.common.model.dto.AmountFieldDTO;
import cn.axzo.workflow.common.model.dto.UploadFieldDTO;
import cn.axzo.workflow.core.engine.cmd.helper.FormFieldClone;
import cn.axzo.workflow.form.engine.model.CustomSimpleFormModel;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.flowable.common.engine.api.FlowableException;
import org.flowable.common.engine.api.delegate.Expression;
import org.flowable.common.engine.impl.el.VariableContainerWrapper;
import org.flowable.common.engine.impl.interceptor.CommandContext;
import org.flowable.form.api.FormInstance;
import org.flowable.form.api.FormInstanceInfo;
import org.flowable.form.api.FormInstanceQuery;
import org.flowable.form.engine.FormEngineConfiguration;
import org.flowable.form.engine.impl.cmd.GetFormInstanceModelCmd;
import org.flowable.form.engine.impl.util.CommandContextUtil;
import org.flowable.form.model.ExpressionFormField;
import org.flowable.form.model.FormContainer;
import org.flowable.form.model.FormField;
import org.flowable.form.model.FormFieldTypes;
import org.flowable.form.model.Option;
import org.flowable.form.model.OptionFormField;
import org.flowable.form.model.SimpleFormModel;
import org.joda.time.LocalDate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import static cn.axzo.workflow.common.code.FormInstanceRespCode.FORM_DATA_PARSE_ERROR_BY_CUSTOM_COMPONENT;
import static cn.axzo.workflow.common.constant.FormConstants.FORM_FIELD_TYPE_CHANGE_SIGNATURE_ORDER;
import static cn.axzo.workflow.common.constant.FormConstants.FORM_FIELD_TYPE_CUSTOM_COMPONENT;
import static cn.axzo.workflow.common.constant.FormConstants.FORM_FIELD_TYPE_IMAGE;
import static cn.axzo.workflow.common.constant.FormConstants.FORM_FIELD_TYPE_INPUT;
import static cn.axzo.workflow.common.constant.FormConstants.FORM_FIELD_TYPE_RECTIFY_ORDER;
import static cn.axzo.workflow.common.constant.FormConstants.FORM_FIELD_TYPE_TASK_ORDER;
import static cn.axzo.workflow.common.constant.FormConstants.FORM_FIELD_TYPE_TEXTAREA;
import static cn.axzo.workflow.form.engine.cmd.CustomGetVariablesFromFormSubmissionCmd.fmtConvert;
import static cn.axzo.workflow.form.engine.cmd.CustomGetVariablesFromFormSubmissionCmd.parseToLocalDateTime;

/**
 * 自定义的获取表单模型和最新表单内容的命令实现
 *
 * @author wangli
 * @since 2024-11-25 19:52
 */
public class CustomGetFormInstanceModelCmd extends GetFormInstanceModelCmd {
    private static final Logger log = LoggerFactory.getLogger(CustomGetFormInstanceModelCmd.class);
    private static final long serialVersionUID = 1L;

    public CustomGetFormInstanceModelCmd(String formInstanceId, Map<String, Object> variables) {
        super(formInstanceId, variables);
    }

    public CustomGetFormInstanceModelCmd(String formDefinitionKey, String formDefinitionId, String taskId, String processInstanceId, Map<String, Object> variables) {
        super(formDefinitionKey, formDefinitionId, taskId, processInstanceId, variables);
    }

    public CustomGetFormInstanceModelCmd(String formDefinitionKey, String parentDeploymentId, String formDefinitionId, String taskId, String processInstanceId, Map<String, Object> variables) {
        super(formDefinitionKey, parentDeploymentId, formDefinitionId, taskId, processInstanceId, variables);
    }

    public CustomGetFormInstanceModelCmd(String formDefinitionKey, String parentDeploymentId, String formDefinitionId, String taskId, String processInstanceId, String tenantId, Map<String, Object> variables, boolean fallbackToDefaultTenant) {
        super(formDefinitionKey, parentDeploymentId, formDefinitionId, taskId, processInstanceId, tenantId, variables, fallbackToDefaultTenant);
    }

    @Override
    protected FormInstance resolveFormInstance(CommandContext commandContext) {
        FormEngineConfiguration formEngineConfiguration = CommandContextUtil.getFormEngineConfiguration();
        FormInstanceQuery formInstanceQuery = formEngineConfiguration.getFormService().createFormInstanceQuery().formDefinitionId(formDefinitionId);
        if (formInstanceId != null) {
            formInstanceQuery.id(formInstanceId);

        } else if (processInstanceId != null) {
            formInstanceQuery.processInstanceId(processInstanceId);

            if (StringUtils.hasText(taskId)) {
                formInstanceQuery.taskId(taskId);
            } else if (Objects.equals("", taskId)) {
                formInstanceQuery.withoutTaskId();
            }

        } else if (scopeId != null) {
            formInstanceQuery.scopeId(scopeId);
            formInstanceQuery.scopeType(scopeType);

            if (taskId == null) {
                formInstanceQuery.withoutTaskId();
            }

        } else {
            return null;
        }

        List<FormInstance> formInstances = formInstanceQuery.orderBySubmittedDate().desc().list();
        // 这里约束前端每次编辑表单提交时，都是提交全量的字段，所以这里只取最后的数据即可。
        // 如果以后开放给业务传入，那么这里的逻辑可能就需要调整
        if (!formInstances.isEmpty()) {
            return formInstances.get(0);
        }

        log.info("未查询到流程实例关联的表单实例数据");
        return null;
    }

    @Override
    public void fillVariablesWithFormValues(Map<String, JsonNode> submittedFormFieldMap, List<FormField> allFields) {
        for (FormField field : allFields) {

            JsonNode fieldValueNode = submittedFormFieldMap.get(field.getId());

            if (fieldValueNode == null || fieldValueNode.isNull()) {
                continue;
            }

            String fieldType = field.getType();
            String fieldValue = fieldValueNode.asText();

            if (FormFieldTypes.DATE.equals(fieldType)) {
                try {
                    if (org.apache.commons.lang3.StringUtils.isNotEmpty(fieldValue)) {
                        LocalDateTime dateValue = parseToLocalDateTime(fieldValue, fmtConvert(field.getParam("fmt").toString()));
                        if (Objects.nonNull(dateValue)) {
                            variables.put(field.getId(), dateValue.format(DateTimeFormatter.ofPattern(fmtConvert(field.getParam("fmt").toString()))));
                        } else {
                            variables.put(field.getId(), fieldValue);
                        }
                    }
                } catch (Exception e) {
                    log.error("Error parsing form date value for process instance {} and task {} with value {}", processInstanceId, taskId, fieldValue, e);
                }
//            } else if (FormFieldTypes.UPLOAD.equals(fieldType) || FormConstants.FORM_FIELD_TYPE_IMAGE.equals(fieldType)) {
//                FormEngineConfiguration formEngineConfiguration = CommandContextUtil.getFormEngineConfiguration();
//                ObjectMapper objectMapper = formEngineConfiguration.getObjectMapper();
//                try {
//                    List<UploadFieldDTO> uploadFiles = objectMapper.readValue(fieldValue, new TypeReference<List<UploadFieldDTO>>() {
//                    });
//                    variables.put(field.getId(), uploadFiles);
//                } catch (JsonProcessingException e) {
//                    throw new WorkflowEngineException(FORM_DATA_PARSE_ERROR_BY_UPLOAD);
//                }
            } else if (fieldValueNode.isBoolean()) {
                variables.put(field.getId(), fieldValueNode.asBoolean());

            } else if (fieldValueNode.isLong()) {
                variables.put(field.getId(), fieldValueNode.asLong());

            } else if (fieldValueNode.isDouble()) {
                variables.put(field.getId(), fieldValueNode.asDouble());

            } else {
                variables.put(field.getId(), fieldValue);
            }
        }
    }

    @Override
    protected void fillFormInstanceValues(FormInstanceInfo formInstanceModel, FormInstance formInstance, Map<String, JsonNode> formInstanceFieldMap, ObjectMapper objectMapper) {
        try {
            JsonNode submittedNode = objectMapper.readTree(formInstance.getFormValueBytes());
            if (submittedNode == null) {
                return;
            }

            if (submittedNode.get("values") != null) {
                JsonNode valuesNode = submittedNode.get("values");
                Iterator<String> fieldIdIterator = valuesNode.fieldNames();
                while (fieldIdIterator.hasNext()) {
                    String fieldId = fieldIdIterator.next();
                    JsonNode valueNode = valuesNode.get(fieldId);
                    formInstanceFieldMap.put(fieldId, valueNode);
                }
            }

            if (submittedNode.get("flowable_form_outcome") != null) {
                JsonNode outcomeNode = submittedNode.get("flowable_form_outcome");
                if (!outcomeNode.isNull() && org.apache.commons.lang3.StringUtils.isNotEmpty(outcomeNode.asText())) {
                    formInstanceModel.setSelectedOutcome(outcomeNode.asText());
                }
            }

        } catch (Exception e) {
            throw new FlowableException("Error parsing form instance " + formInstance.getId(), e);
        }
    }

    @Override
    protected void fillFormFieldValues(FormInstance formInstance, FormInstanceInfo formInstanceModel, CommandContext commandContext) {
        FormEngineConfiguration formEngineConfiguration = CommandContextUtil.getFormEngineConfiguration();
        CustomSimpleFormModel formModel = new CustomSimpleFormModel((SimpleFormModel) formInstanceModel.getFormModel());
        List<FormField> allFields = formModel.listAllFields();
        if (allFields != null) {

            Map<String, JsonNode> formInstanceFieldMap = new HashMap<>();
            if (formInstance != null) {
                fillFormInstanceValues(formInstanceModel, formInstance, formInstanceFieldMap, formEngineConfiguration.getObjectMapper());
                fillVariablesWithFormValues(formInstanceFieldMap, allFields);
            }

            for (FormField field : allFields) {
                if (field instanceof OptionFormField) {
                    OptionFormField optionFormField = (OptionFormField) field;
                    if (optionFormField.getOptionsExpression() != null) {
                        // Drop down options to be populated from an expression
                        Expression optionsExpression = formEngineConfiguration.getExpressionManager().createExpression(optionFormField.getOptionsExpression());
                        Object value = null;
                        try {
                            value = optionsExpression.getValue(new VariableContainerWrapper(variables));
                        } catch (Exception e) {
                            throw new FlowableException("Error getting value for optionsExpression: " + optionFormField.getOptionsExpression(), e);
                        }
                        if (value instanceof List) {
                            @SuppressWarnings("unchecked")
                            List<Option> options = (List<Option>) value;
                            optionFormField.setOptions(options);
                        } else if (value instanceof String) {
                            String json = (String) value;
                            try {
                                List<Option> options = formEngineConfiguration.getObjectMapper().readValue(json, new TypeReference<List<Option>>() {
                                });
                                optionFormField.setOptions(options);
                            } catch (Exception e) {
                                throw new FlowableException("Error parsing optionsExpression json value: " + json, e);
                            }
                        } else {
                            throw new FlowableException("Invalid type from evaluated expression for optionsExpression: " + optionFormField.getOptionsExpression() + ", resulting type:" + value.getClass().getName());
                        }
                    }
                    Object variableValue = variables.get(field.getId());
                    optionFormField.setValue(variableValue);

                } else if (FormFieldTypes.HYPERLINK.equals(field.getType())) {
                    Object variableValue = variables.get(field.getId());
                    // process expression if there is no value, otherwise keep it
                    if (variableValue != null) {
                        field.setValue(variableValue);
                    } else {
                        // No value set, process as expression
                        if (field.getParam("hyperlinkUrl") != null) {
                            String hyperlinkUrl = field.getParam("hyperlinkUrl").toString();
                            Expression formExpression = formEngineConfiguration.getExpressionManager().createExpression(hyperlinkUrl);
                            try {
                                field.setValue(formExpression.getValue(new VariableContainerWrapper(variables)));
                            } catch (Exception e) {
                                log.error("Error getting value for hyperlink expression {} {}", hyperlinkUrl, e.getMessage(), e);
                            }
                        }
                    }

                } else if (field instanceof ExpressionFormField) {
                    ExpressionFormField expressionField = (ExpressionFormField) field;
                    Expression formExpression = formEngineConfiguration.getExpressionManager().createExpression(expressionField.getExpression());
                    try {
                        field.setValue(formExpression.getValue(new VariableContainerWrapper(variables)));
                    } catch (Exception e) {
                        log.error("Error getting value for expression {} {}", expressionField.getExpression(), e.getMessage());
                    }

                } else if (FormFieldTypes.UPLOAD.equals(field.getType())
                        || FORM_FIELD_TYPE_IMAGE.equals(field.getType())) {

                    // Multiple docs are stored as comma-separated string ids,
                    // explicitly storing them as an array so they're serialized properly
                    if (variables.containsKey(field.getId())) {
                        String uploadValue = (String) variables.get(field.getId());
                        if (StringUtils.hasText(uploadValue)) {
                            try {
                                List<UploadFieldDTO> uploadFiles = formEngineConfiguration.getObjectMapper()
                                        .readValue(uploadValue, new TypeReference<List<UploadFieldDTO>>() {
                                        });
                                field.setValue(uploadFiles);
                            } catch (JsonProcessingException e) {
                                throw new FlowableException("Error parsing upload files json value: " + uploadValue, e);
                            }
                        }
                    }
                } else if (FORM_FIELD_TYPE_TASK_ORDER.equals(field.getType())
                        || FORM_FIELD_TYPE_RECTIFY_ORDER.equals(field.getType())
                        || FORM_FIELD_TYPE_CHANGE_SIGNATURE_ORDER.equals(field.getType())) {
                    if (variables.containsKey(field.getId())) {
                        String listValue = (String) variables.get(field.getId());
                        if (StringUtils.hasText(listValue)) {
                            try {
                                List<Map<String, String>> idMap = formEngineConfiguration.getObjectMapper()
                                        .readValue(listValue, new TypeReference<List<Map<String, String>>>() {
                                        });
                                field.setValue(idMap);
                            } catch (JsonProcessingException e) {
                                throw new FlowableException("Error parsing business component ids json value: " + listValue, e);
                            }
                        }
                    }
                } else if (FORM_FIELD_TYPE_CUSTOM_COMPONENT.equals(field.getType())) {
                    String listJson = (String) variables.get(field.getId());
                    if (StringUtils.hasText(listJson)) {
                        ObjectMapper objectMapper = formEngineConfiguration.getObjectMapper();
                        try {
                            List<Map> rows = objectMapper.readValue(listJson, new TypeReference<List<Map>>() {
                            });
                            if (!CollectionUtils.isEmpty(rows)) {
                                List<List<FormField>> fields = ((FormContainer) field).getFields();
                                if (rows.size() == 1) {
                                    fields.get(0).forEach(sf -> {
                                        if (FORM_FIELD_TYPE_INPUT.equals(sf.getType())
                                                || FORM_FIELD_TYPE_TEXTAREA.equals(sf.getType())) {
                                            if (rows.get(0).containsKey(sf.getId())) {
                                                sf.setValue(rows.get(0).get(sf.getId()));
                                            }
                                        } else if (FormFieldTypes.UPLOAD.equals(sf.getType())
                                                || FORM_FIELD_TYPE_IMAGE.equals(sf.getType())) {
                                            if (StringUtils.hasText(String.valueOf(rows.get(0).get(sf.getId())))) {
                                                if (rows.get(0).containsKey(sf.getId())) {

                                                    try {
                                                        String updateValueJson = objectMapper.writeValueAsString(rows.get(0).get(sf.getId()));
                                                        if (StringUtils.hasText(updateValueJson)) {
                                                            List<UploadFieldDTO> uploadFiles = formEngineConfiguration.getObjectMapper()
                                                                    .readValue(updateValueJson, new TypeReference<List<UploadFieldDTO>>() {
                                                                    });
                                                            sf.setValue(uploadFiles);
                                                        }
                                                    } catch (JsonProcessingException e) {
                                                        throw new FlowableException("Error parsing upload files json ", e);
                                                    }

                                                }
                                            }
                                        }
                                    });
                                } else {
                                    // 根据传入的表单自定义组件数据，构建表单模型，Field 是二维数组，一维用于行，二维用于列
                                    for (int i = 1; i < rows.size(); i++) {
                                        List<FormField> newRow = new ArrayList<>();
                                        fields.get(0).forEach(e -> {
                                            newRow.add(FormFieldClone.clone(e));
                                        });
                                        fields.add(newRow);
                                    }
                                    // 为每行赋值
                                    for (int i = 0; i < rows.size(); i++) {
                                        Map<String, Object> rowMap = rows.get(i);
                                        fields.get(i).forEach(sf -> {
                                            if (FORM_FIELD_TYPE_INPUT.equals(sf.getType())
                                                    || FORM_FIELD_TYPE_TEXTAREA.equals(sf.getType())) {
                                                if (rowMap.containsKey(sf.getId())) {
                                                    sf.setValue(rowMap.get(sf.getId()));
                                                }
                                            } else if (FormFieldTypes.UPLOAD.equals(sf.getType())
                                                    || FORM_FIELD_TYPE_IMAGE.equals(sf.getType())) {
                                                if (rowMap.containsKey(sf.getId())) {
                                                    try {
                                                        String updateValueJson = objectMapper.writeValueAsString(rowMap.get(sf.getId()));
                                                        if (StringUtils.hasText(updateValueJson)) {
                                                            List<UploadFieldDTO> uploadFiles = formEngineConfiguration.getObjectMapper()
                                                                    .readValue(updateValueJson, new TypeReference<List<UploadFieldDTO>>() {
                                                                    });
                                                            sf.setValue(uploadFiles);
                                                        }
                                                    } catch (JsonProcessingException e) {
                                                        throw new FlowableException("Error parsing upload files json ", e);
                                                    }
                                                }
                                            }
                                        });
                                    }
                                }
                            }
                        } catch (JsonProcessingException e) {
                            throw new WorkflowEngineException(FORM_DATA_PARSE_ERROR_BY_CUSTOM_COMPONENT);
                        }
                    }
                } else if (FormFieldTypes.AMOUNT.equals(field.getType())) {
                    if (variables.containsKey(field.getId())) {
                        String amountValue = (String) variables.get(field.getId());
                        if (StringUtils.hasText(amountValue)) {
                            try {
                                AmountFieldDTO amount = formEngineConfiguration.getObjectMapper().readValue(amountValue, new TypeReference<AmountFieldDTO>() {
                                });
                                field.setValue(amount);
                            } catch (JsonProcessingException e) {
                                throw new FlowableException("Error parsing amount json value: " + amountValue, e);
                            }
                        }
                    }
                } else {
                    Object variableValue = variables.get(field.getId());
                    if (variableValue != null) {

                        if (variableValue instanceof LocalDateTime) {
                            field.setValue(((LocalDateTime) variableValue).format(DateTimeFormatter.ofPattern(fmtConvert(field.getParam("fmt").toString()))));
                        } else if (variableValue instanceof LocalDate) {
                            field.setValue(((LocalDate) variableValue).toString(fmtConvert(field.getParam("fmt").toString())));
                        } else if (variableValue instanceof Date) {
                            field.setValue(DATE_FORMAT.format(((Date) variableValue).toInstant()));
                        } else {
                            field.setValue(variableValue);
                        }
                    }
                }

                field.setReadOnly(true);
            }
        }
    }

}
