package com.persagy.dmp.rwd.digital.utils;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.persagy.dmp.basic.model.QueryCriteria;
import com.persagy.dmp.basic.model.QueryOperator;
import com.persagy.dmp.basic.utils.JsonNodeUtils;
import com.persagy.dmp.common.constant.CommonConstant;
import com.persagy.dmp.common.constant.ValidEnum;
import com.persagy.dmp.common.model.entity.BaseEntity;
import com.persagy.dmp.digital.entity.ObjectDigital;
import com.persagy.dmp.digital.entity.ObjectRelation;
import com.persagy.dmp.rwd.digital.domain.CalculatingDTO;
import lombok.SneakyThrows;

import java.util.*;

/**
 * 对象 查询条件 助手
 * 增加对对象专属条件的处理
 * @author Charlie Yu
 * @date 2021-08-16
 */
public class ObjectDigitalCriteriaHelper {

    /** 对象类字段集 */
    public static final Set<String> DIGITAL_FIELDS = JsonNodeUtils.getFieldMap(ObjectDigital.class).keySet();
    /** 未绑定关系标识 */
    private static final String BIND_FLAG = "$bindFlag";

    /**
     * 对象查询条件预处理：主要处理infos扩展内容
     * @param criteria
     */
    public static void presetDigitalCriteria(QueryCriteria criteria) {
        // 处理查询条件
        ensureExtraCond(criteria.getCriteria());
        // 处理排序条件
        ensureOrders(criteria.getOrders());
    }

    /**
     * 处理排序条件
     * @param orders
     */
    private static void ensureOrders(List<OrderItem> orders) {
        if(CollUtil.isEmpty(orders)) {
            return;
        }
        for(OrderItem order:orders) {
            String column = order.getColumn();
            // 基础属性不用处理
            if(StrUtil.isBlank(column) || DIGITAL_FIELDS.contains(column)) {
                continue;
            }
            // 非基础属性转json
            column = StrUtil.format("#JSON#json_extract({}, '$.{}')", ObjectDigital.EXTRA_COLUMN, column);
            order.setColumn(column);
        }
    }

    /**
     * 拼接扩展条件
     * 暂用#JSON#表示是json条件，不处理驼峰
     * @param criteria
     */
    private static void ensureExtraCond(ObjectNode criteria) {
        if(criteria == null || criteria.isNull()) {
            return;
        }
        ensureSearchCond(criteria);
        ensureRelationCond(criteria);
        ensureJsonCond(criteria);
    }

    /**
     * 拼接json条件
     * @param criteria
     */
    private static void ensureJsonCond(ObjectNode criteria) {
        Iterator<String> fieldNames = criteria.fieldNames();
        Map<String, JsonNode> valueMap = new HashMap<>(16);
        while (fieldNames.hasNext()) {
            String infoCode = fieldNames.next();
            // 20210903 grouping字段特殊处理，解决mysql关键字问题
            if("grouping".equalsIgnoreCase(infoCode)) {
                valueMap.put("`grouping`", criteria.get(infoCode));
                fieldNames.remove();
                continue;
            }
            // 基础属性不用处理
            if(DIGITAL_FIELDS.contains(infoCode)) {
                continue;
            }
            if (QueryOperator.AND_OR.getIndex().equalsIgnoreCase(infoCode)){
                // $andOr的特殊处理
                JsonNode andOrNode = criteria.get(QueryOperator.AND_OR.getIndex());
                processAndOrCond(andOrNode);
                continue;
            }
            JsonNode value = criteria.get(infoCode);
            if(value == null || value.isNull()) {
                continue;
            }
            // 如果是or条件
            if(QueryOperator.OR.getIndex().equalsIgnoreCase(infoCode)) {
                processOrCond(value);
                continue;
            }
            commonProcessInfosCond(value,infoCode,criteria, valueMap,fieldNames);
        }
        // 将valueMap写入criteria中
        if(MapUtil.isEmpty(valueMap)) {
            return;
        }
        for(String key:valueMap.keySet()) {
            criteria.set(key, valueMap.get(key));
        }
    }
    /***
     * Description: 处理andOr的查询条件
     * @param node : andOr查询对象
     * @return : void
     * @author : lijie
     * @date :2021/10/20 19:16
     * Update By lijie 2021/10/20 19:16
     */
    private static void processAndOrCond(JsonNode node) {
        if(node == null || node.isNull() || !node.isObject()) {
            return;
        }
        ObjectNode objectNode = (ObjectNode) node;
        Iterator<String> fieldNames = node.fieldNames();
        Map<String, JsonNode> valueMap = new HashMap<>(16);
        while (fieldNames.hasNext()){
            String infoCode = fieldNames.next();
            // 基础属性不用处理
            if(DIGITAL_FIELDS.contains(infoCode)) {
                continue;
            }
            JsonNode value = node.get(infoCode);
            commonProcessInfosCond(value,infoCode,objectNode, valueMap,fieldNames);
        }
        // 将valueMap写入criteria中
        if(MapUtil.isEmpty(valueMap)) {
            return;
        }
        for(String key:valueMap.keySet()) {
            objectNode.set(key, valueMap.get(key));
        }
    }
    /***
     * Description: 通用处理infos的查询条件
     * @param value : key对应的值
     * @param infoCode : 信息点code
     * @param node : 查询条件对象
     * @param valueMap : 信息点code与值的映射
     * @param fieldNames : 字段列表
     * @return : void
     * @author : lijie
     * @date :2021/10/20 19:16
     * Update By lijie 2021/10/20 19:16
     */
    private static void commonProcessInfosCond(JsonNode value,String infoCode, ObjectNode node,
                                               Map<String, JsonNode> valueMap,
                                               Iterator<String> fieldNames) {
        // 如果值是对象 先处理contains并移除
        if(value.isObject()) {
            // 包含
            if (value.has(QueryOperator.CONTAINS.getIndex())) {
                JsonNode valueNode = value.get(QueryOperator.CONTAINS.getIndex());
                String valueStr = valueNode.isTextual() ? value.asText():value.toString();
                String key = StrUtil.format("#JSON#json_contains({}, {}, '$.{}')", ObjectDigital.EXTRA_COLUMN, valueStr, infoCode);
                valueMap.put(key, node.numberNode(1));
                ((ObjectNode) value).remove(QueryOperator.CONTAINS.getIndex());
            } else if (value.has(QueryOperator.NOT_CONTAINS.getIndex())) {
                // 不包含
                JsonNode valueNode = value.get(QueryOperator.NOT_CONTAINS.getIndex());
                String valueStr = valueNode.isTextual() ? value.asText():value.toString();
                String key = StrUtil.format("#JSON#json_contains({}, {}, '$.{}')", ObjectDigital.EXTRA_COLUMN, valueStr, infoCode);
                valueMap.put(key, node.numberNode(0));
                ((ObjectNode) value).remove(QueryOperator.NOT_CONTAINS.getIndex());
            }
        }
        // 获取格式化的Key
        String key = ensureFormatKey(infoCode, value);
        valueMap.put(key,value);
        // 处理完成后删除
        fieldNames.remove();
    }

    /**
     * 格式化Key
     * @param infoCode
     * @param value
     */
    private static String ensureFormatKey(String infoCode, JsonNode value) {
        if(StrUtil.isBlank(infoCode) || value == null) {
            return null;
        }
        String format = null;
        // 如果值是对象
        if(value.isObject()) {
            ObjectNode obj = (ObjectNode) value;
            if (obj.has(QueryOperator.LIKE.getIndex()) || obj.has(QueryOperator.LIKE_LEFT.getIndex())
                    || obj.has(QueryOperator.LIKE_RIGHT.getIndex())) {
                format = "#JSON#json_unquote(json_extract({}, '$.{}'))";
            } else if (obj.has(QueryOperator.NULL.getIndex())) {
                format = "#JSON#json_extract({}, '$.{}')";
            }else {
                for (String key : Arrays.asList("$gt", "$lt", "$gte", "$lte", "$ne")) {
                    if (obj.has(key)) {
                        // 递归处理
                        return ensureFormatKey(infoCode, obj.get(key));
                    }
                }
            }
        } else if (value.isArray()) {
            ArrayNode array = (ArrayNode) value;
            if (ArrayUtil.isEmpty(array)) {
                return null;
            }
            // 递归处理
            return ensureFormatKey(infoCode, array.get(0));
        } else if (value.isNull()) {
            return null;
        } else if (value.isTextual()) {
            format = "#JSON#json_unquote(json_extract({}, '$.{}'))";
        } else if (value.isDouble() || value.isFloat() || value.isBigDecimal()) {
            format = "#JSON#ifnull(json_extract({}, '$.{}'), -9999.99)";
        } else if (value.isInt() || value.isLong() || value.isShort() || value.isBigInteger()) {
            format = "#JSON#ifnull(json_extract({}, '$.{}'), -9999)";
        } else if (value.isBoolean()) {
            format = "#JSON#ifnull(json_extract({}, '$.{}'), 0)";
        } else if (value.isPojo()) {
            format = "#JSON#json_unquote(json_extract({}, '$.{}'))";
        }
        return StrUtil.format(format, ObjectDigital.EXTRA_COLUMN, infoCode);
    }

    /**
     * 处理or条件
     * @param node
     */
    private static void processOrCond(JsonNode node) {
        if(node == null || node.isNull()) {
            return;
        }
        // 如果是数组 - 递归继续处理即可
        if(node.isArray()) {
            Iterator<JsonNode> elements = node.elements();
            while (elements.hasNext()) {
                // 递归继续处理
                JsonNode objNode = elements.next();
                processOrCond(objNode);
            }
            return;
        }
        // 如果是对象，则转换条件
        if(node.isObject()) {
            // 对象 递归从头开始处理
            ObjectNode obj = (ObjectNode) node;
            ensureExtraCond(obj);
            return;
        }
    }

    /**
     * 拼接$search条件
     * @param criteria
     */
    private static void ensureSearchCond(ObjectNode criteria) {
        if(criteria == null || criteria.isNull()) {
            return;
        }
        // 处理search
        JsonNode search = criteria.get(QueryOperator.SEARCH.getIndex());
        if(search != null && search.isTextual()) {
            String searchValue = search.asText();
            String key = StrUtil.format("#JSON#json_search({0}, 'one', {1})", ObjectDigital.EXTRA_COLUMN, searchValue);
            ObjectNode searchObj = criteria.putObject(key);
            searchObj.put(QueryOperator.NULL.getIndex(), false);
            // 处理完成后删除
            criteria.remove(QueryOperator.SEARCH.getIndex());
        }
    }

    /**
     * 处理关系条件
     * @param criteria
     */
    private static void ensureRelationCond(ObjectNode criteria) {
        if(criteria == null || criteria.isNull()) {
            return;
        }
        // 处理relationFrom
        ensureRelationIds(criteria, true);
        // 处理relationTo
        ensureRelationIds(criteria, false);
    }

    /**
     * 处理关系条件
     * @param criteria
     * @param isFrom
     */
    private static void ensureRelationIds(ObjectNode criteria, boolean isFrom) {
        //增加关系默认查询有效数据条件
        /*if(criteria != null && criteria.get("valid") == null){
            criteria.put("valid", ValidEnum.TRUE.getType());
        }*/
        String flagKey = isFrom ? QueryOperator.RELATION_FROM.getIndex() : QueryOperator.RELATION_TO.getIndex();
        String selectKey = isFrom ? ObjectRelation.OBJ_FROM : ObjectRelation.OBJ_TO;
        String whereKey = isFrom ? "objTo" : "objFrom";
        JsonNode relationCond = criteria.get(flagKey);
        // 没有条件不需要处理
        if(relationCond == null || relationCond.size() <= 0 || !relationCond.isObject()) {
            return;
        }
        //增加默认有效标示
        ObjectNode jsonNode = (ObjectNode) relationCond;
        if(jsonNode.get(BaseEntity.PROP_VALID) == null){
            jsonNode.put(BaseEntity.PROP_VALID, ValidEnum.TRUE.getType());
        }
        /** 20210827 relation条件支持复杂条件 - 使用Wrapper方式只能先查询出来，可以改为拼接sql提升效率，但需要大量扩展，不便于维护。
        // 查询关系的条件
        QueryWrapper<ObjectRelation> wrapper = new QueryWrapper<>();
        wrapper.select(QueryCriteriaHelper.toUnderlineCase(selectKey));
        // 添加所属项目条件
        ConditionUtil.ensureProjectCriteria(wrapper);
        QueryCriteriaHelper.toWrapper(wrapper, (ObjectNode) relationCond);
        List<ObjectRelation> relations = SpringHelper.getBean(IObjectRelationService.class).queryByCondition(wrapper);
        List<String> idList = CollUtil.getFieldValues(relations, selectKey, String.class);
        // 回写到criteria中
        ObjectNode inObj = criteria.putObject(BaseEntity.PROP_ID);
        ArrayNode inArray = inObj.putArray(QueryOperator.IN.getIndex());
        if(CollUtil.isEmpty(idList)) {
            inArray.add("0");
        } else {
            idList.forEach(id -> inArray.add(id));
        }
        // 处理完成后删除
        criteria.remove(flagKey);
         **/
        StringBuilder inSql = new StringBuilder("(select "+selectKey+" from dt_relation where 1=1 ");
        addWhereCond(jsonNode, inSql, "graphId");
        addWhereCond(jsonNode, inSql, "graphCode");
        addWhereCond(jsonNode, inSql, "relCode");
        addWhereCond(jsonNode, inSql, "relValue");
        addWhereCond(jsonNode, inSql, "valid");
        addWhereCond(jsonNode, inSql, whereKey);
        inSql.append(")");
        // 查询已绑定or未绑定关系的对象 - $bindFlag设置为false 才为查未绑定对象
        Object bindFlag = JsonNodeUtils.getNodeValue(jsonNode.get(BIND_FLAG));
        boolean notBind = bindFlag != null && bindFlag instanceof Boolean && BooleanUtil.isFalse((Boolean) bindFlag);
        // 重置查询条件
        ObjectNode inObj = criteria.putObject(BaseEntity.PROP_ID);
        inObj.put(notBind?QueryOperator.NOT_IN_SQL.getIndex():QueryOperator.IN_SQL.getIndex(), inSql.toString());
        // 处理完成后删除
        criteria.remove(flagKey);
    }

    /**
     * 添加where条件
     * @param node json对象
     * @param inSql in条件
     * @param field 属性名
     */
    @SneakyThrows
    private static void addWhereCond(JsonNode node, StringBuilder inSql, String field) {
        Object value = JsonNodeUtils.getNodeValue(node.get(field));
        // 转下划线
        String column = StrUtil.toUnderlineCase(field);
        if(value == null) {
            // 如果是数组形式,则加上in查询
            JsonNode specialNode = node.get(field);
            if (specialNode == null || specialNode.isNull()
                    || !specialNode.isArray() || specialNode.size()<1 || !specialNode.get(0).isTextual()){
               return;
            }
            ObjectMapper objectMapper = SpringUtil.getBean(ObjectMapper.class);
            List<String> codes = objectMapper
                    .readValue(objectMapper.writeValueAsString(specialNode),new TypeReference<List<String>>(){});
            inSql.append("and ")
                    .append(column)
                    .append(" in('")
                    .append(String.join("','",codes))
                    .append("') ");
            return;
        }
        // 字符串的加单引号，其他的不加
        if(value instanceof String) {
            String stringValue = (String) value;
            if(StrUtil.isNotEmpty(stringValue)) {
                inSql.append("and " + column + " = '" + stringValue + "' ");
            }
        } else {
            inSql.append("and " + column + " = " + StrUtil.toString(value) + " ");
        }
    }

    /***
     * Description: 创建通用查询条件对象
     * @param ids : 对象id数组
     * @param calculatingDTO : 请求参数
     * @return : com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<com.persagy.dmp.digital.entity.ObjectDigital>
     * @author : lijie
     * @date :2021/9/11 15:41
     * Update By lijie 2021/9/11 15:41
     */
    public static <T> QueryWrapper<T> createCommonQueryWrapper(Set<String> ids, CalculatingDTO calculatingDTO) {
        QueryWrapper<T> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq(ObjectDigital.PROP_VALID,Boolean.TRUE);
        if (CollUtil.isNotEmpty(ids)){
            queryWrapper.in(ObjectDigital.PROP_ID,ids);
        }
        if (StrUtil.isNotBlank(calculatingDTO.getGroupCode())){
            queryWrapper.eq(StrUtil.toUnderlineCase(CommonConstant.QUERY_GROUPCODE),calculatingDTO.getGroupCode());
        }
        if (StrUtil.isNotBlank(calculatingDTO.getProjectId())){
            queryWrapper.eq(StrUtil.toUnderlineCase(CommonConstant.QUERY_PROJECTID),calculatingDTO.getProjectId());
        }
        return queryWrapper;
    }
}
