package com.yeejoin.amos.knowledgebase.face.util.sql;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import lombok.Setter;
import lombok.experimental.Accessors;
import org.typroject.tyboot.core.foundation.utils.ValidationUtil;
import org.typroject.tyboot.core.restful.exception.instance.BadRequest;

import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Pattern;

/**
 * 查询条件语句
 *
 * @author tiantao
 */
@Setter
@Accessors(chain = true)
class SqlStatement extends BaseSqlCondition {

    static {
        Map<String, String[]> map = new HashMap<>();
        map.put(ValueType.date.name(), str2array("dateH"));
        map.put(ValueType.text.name(), str2array("text"));
        map.put(ValueType.single.name(), str2array("singleValue"));
        map.put(ValueType.range.name(), str2array("rangeMin", "rangeMax"));
        valueTypeFieldMap = Collections.unmodifiableMap(map);
        map = new HashMap<>();
        map.put(ValueType.date.name(), str2array(", MAX(CASE `tv`.`FIELD_NAME` WHEN 'dateH' THEN `tv`.`TAG_VALUE` ELSE NULL END) AS `dateH`"));
        map.put(ValueType.text.name(), str2array(", MAX(CASE `tv`.`FIELD_NAME` WHEN 'text' THEN `tv`.`TAG_VALUE` ELSE NULL END) AS `text`"));
        map.put(ValueType.single.name(), str2array(", MAX(CASE `tv`.`FIELD_NAME` WHEN 'singleValue' THEN `tv`.`TAG_VALUE` ELSE NULL END) AS `singleValue`"));
        map.put(ValueType.range.name(), str2array(", MAX(CASE `tv`.`FIELD_NAME` WHEN 'rangeMin' THEN `tv`.`TAG_VALUE` ELSE NULL END) AS `rangeMin`",
                ", MAX(CASE `tv`.`FIELD_NAME` WHEN 'rangeMax' THEN `tv`.`TAG_VALUE` ELSE NULL END) AS `rangeMax`"));
        valueTagFieldSqlMap = Collections.unmodifiableMap(map);
    }

    private static final Map<String, String[]> valueTypeFieldMap;
    private static final Map<String, String[]> valueTagFieldSqlMap;
    private static final Pattern numberPattern = Pattern.compile("^[-\\+]?[\\d]+[.]?[\\d]*$");
    private static final Pattern datePattern = Pattern.compile("^[\\d]{4}-[\\d]{2}-[\\d]{2} [\\d]{2}:[\\d]{2}:[\\d]{2}$");
    private static final Pattern datePattern16 = Pattern.compile("^[\\d]{4}/[\\d]{2}/[\\d]{2} [\\d]{2}:[\\d]{2}$");

    /**
     * 字段类型
     */
    private FieldType fieldType;

    /**
     * 字段名/标签id
     */
    private String expandField;

    /**
     * 值类型
     */
    private ValueType valueType;

    /**
     * 值
     */
    private List<String> values;

    /**
     * 模糊匹配
     */
    private boolean fuzzy;

    private static String[] str2array(String... str) {
        return str;
    }

    SqlStatement(Map<String, Object> resourceMap) {
        super(resourceMap);
        this.setFieldType(FieldType.valueOf((String) resourceMap.get("fieldType")))
                .setExpandField((String) resourceMap.get("expandFiled"))
                .setValues((List<String>) resourceMap.get("values"))
                .setFuzzy(Boolean.parseBoolean(String.valueOf(resourceMap.get("fuzzy"))));
        if (this.fieldType != FieldType.textTag) {
            this.setValueType(ValueType.valueOf((String) resourceMap.get("valueType")));
        }
    }

    @Override
    <T> void composeWrapper(QueryWrapper<T> queryWrapper, String logic) {
        switch (logic) {
            case "or":
                queryWrapper.or();
            case "and":
            default:
                composeThis(queryWrapper);
                break;
        }
    }

    @Override
    boolean validate() {
        if (fieldType == null || expandField == null || values == null) {
            return false;
        }
        if (fieldType != FieldType.textTag && valueType == null) {
            return false;
        }
        boolean emptyVal = ValidationUtil.isEmpty(values);
        if (fieldType != FieldType.textTag && values.size() > 0) {
            String val1 = values.get(0);
            String val2 = "";
            switch (valueType) {
                case text:
                    //格式化
                    if (!emptyVal && !ValidationUtil.isEmpty(val1)) {
                        values.add(0, stringParamFormat(val1));
                        values.remove(1);
                    }
                    break;
                case date:
                    // 校验并格式化
                    if (!emptyVal && !ValidationUtil.isEmpty(val1)) {
                        if (val1.contains("/") && val1.length() == 16) {
                            paramValidate(datePattern16, val1);
                        } else {
                            paramValidate(datePattern, val1);
                        }
                        values.add(0, stringParamFormat(val1));
                        values.remove(1);
                    }
                    val2 = values.get(1);
                    if (!emptyVal && !ValidationUtil.isEmpty(val2)) {
                        if (val2.contains("/") && val1.length() == 16) {
                            paramValidate(datePattern16, val2);
                        } else {
                            paramValidate(datePattern, val2);
                        }
                        values.add(1, stringParamFormat(val2));
                        values.remove(2);
                    }
                    break;
                case range:
                case single:
                    // 校验
                    if (!emptyVal && !ValidationUtil.isEmpty(val1)) {
                        paramValidate(numberPattern, val1);
                    }
                    val2 = values.get(1);
                    if (!emptyVal && !ValidationUtil.isEmpty(val2)) {
                        paramValidate(numberPattern, val2);
                    }
                    break;
                default:
            }
        }
        if (valuesIsEmpty(true)) {
            switch (this.fieldType) {
                case valueTag:
                    this.fieldType = FieldType.textTag;
                    break;
                case textTag:
                    break;
                case content:
                case baseInfo:
                    throw new BadRequest("条件搜索值为空");
                default:
            }
        }
        return true;
    }

    @Override
    public Set<String> getTagSeqs() {
        Set<String> set = new HashSet<>();
        if (fieldType == FieldType.textTag || fieldType == FieldType.valueTag) {
            set.add(expandField);
        }
        return set;
    }

    @Override
    public int getTotalStatement() {
        return 1;
    }

    /**
     * value判空
     *
     * @param all 全空
     * @return
     */
    private boolean valuesIsEmpty(boolean all) {
        if (all) {
            if (ValidationUtil.isEmpty(values)) {
                return true;
            } else {
                boolean empty0 = ValidationUtil.isEmpty(values.get(0));
                if (values.size() > 1) {
                    return empty0 && ValidationUtil.isEmpty(values.get(1));
                }
                return empty0;
            }
        } else {
            if (ValidationUtil.isEmpty(values)) {
                return true;
            } else {
                boolean empty0 = ValidationUtil.isEmpty(values.get(0));
                if (values.size() > 1) {
                    return !(empty0 || ValidationUtil.isEmpty(values.get(1)));
                }
                return empty0;
            }
        }

    }

    private <T> void composeThis(QueryWrapper<T> queryWrapper) {
        switch (fieldType) {
            case content:
                composeContentCondition(queryWrapper);
                break;
            case baseInfo:
                queryWrapper.inSql("SEQUENCE_NBR", buildBaseInfoSql());
                break;
            case textTag:
                queryWrapper.inSql("SEQUENCE_NBR", buildTagExistSql());
                break;
            case valueTag:
                queryWrapper.inSql("SEQUENCE_NBR", buildTagValueSql());
                break;
            default:
        }
    }

    private <T> void composeContentCondition(QueryWrapper<T> wrapper) {
        switch (valueType) {
            case text: {
                String text = "";
                if (values != null && values.get(0) != null) {
                    text = values.get(0);
                }
                if (fuzzy) {
                    wrapper.like(expandField, text);
                } else {
                    wrapper.eq(expandField, text);
                }
                break;
            }
            case date: {
                String min = values.get(0);
                String max = values.get(1);
                paramValidate(datePattern, min, max);
                if (min != null) {
                    wrapper.ge(expandField, min);
                }
                if (max != null) {
                    wrapper.le(expandField, max);
                }
                break;
            }
            default:

        }

    }

    private String buildBaseInfoSql() {
        StringBuilder builder = new StringBuilder("SELECT INSTANCE_ID FROM `knowledge_dynamics_value` WHERE FIELD_NAME = '")
                .append(expandField).append("'");
        getChildCondition("FIELD_VALUE").forEach(child -> builder.append(" AND ").append(child));
        return builder.toString();
    }

    private String buildTagValueSql() {
        StringBuilder builder = new StringBuilder("SELECT TARGET_SEQ FROM (SELECT `ti`.`SEQUENCE_NBR` AS `SEQUENCE_NBR`, `ti`.`TAG_SEQ` AS `TAG_SEQ`, `ti`.`TARGET_SEQ` AS `TARGET_SEQ`");
        Arrays.asList(valueTagFieldSqlMap.get(valueType.name())).forEach(fieldSql -> builder.append(fieldSql));
        builder.append(" FROM `knowledge_tag_instance` `ti` INNER JOIN `knowledge_tag_value` `tv` ON `ti`.`SEQUENCE_NBR` = `tv`.`INSTANCE_SEQ` WHERE `ti`.`MARKING_TYPE` = 'DOC' AND `ti`.`TAG_SEQ` = ")
                .append(expandField).append(" AND (");
        AtomicBoolean needOr = new AtomicBoolean(false);
        Arrays.asList(valueTypeFieldMap.get(valueType.name())).forEach(fieldName -> {
            if (needOr.get()) {
                builder.append(" OR");
            } else {
                needOr.set(true);
            }
            builder.append(" `tv`.FIELD_NAME = '").append(fieldName).append("'");
        });
        builder.append(")").append(" GROUP BY `ti`.`SEQUENCE_NBR`) AS tmp").append(" WHERE");
        AtomicBoolean needAnd = new AtomicBoolean(false);
        getChildCondition(valueTypeFieldMap.get(valueType.name())).forEach(child -> {
            if (needAnd.get()) {
                builder.append(" AND");
            } else {
                needAnd.set(true);
            }
            builder.append(" ").append(child);
        });
        return builder.toString();
    }

    private String buildTagExistSql() {
        return "SELECT TARGET_SEQ FROM `knowledge_tag_instance` WHERE TAG_SEQ = " + expandField;
    }

    private List<String> getChildCondition(String... fields) {
        List<String> res = new ArrayList<>();
        switch (valueType) {
            case text: {
                if (fuzzy) {
                    res.add(fields[0] + " like '%" + stringParamFormat(values.get(0)) + "%'");
                } else {
                    res.add(fields[0] + " = '" + stringParamFormat(values.get(0)) + "'");
                }
                break;
            }
            case single: {
                String min = values.get(0);
                String max = values.get(1);
                if (min != null && min.length() > 0) {
                    res.add("CONVERT(" + fields[0] + ",DECIMAL)" + " >= " + min);
                }
                if (max != null && max.length() > 0) {
                    res.add("CONVERT(" + fields[0] + ",DECIMAL)" + " <= " + max);
                }
                break;
            }
            case date: {
                String min = values.get(0);
                if (fuzzy) {
                    if (min != null && min.length() > 0) {
                        res.add(fields[0] + " >= '" + min + "'");
                    }
                    if (values.size() > 1) {
                        String max = values.get(1);
                        if (max != null && max.length() > 0) {
                            res.add(fields[0] + " <= '" + max + "'");
                        }
                    }
                } else {
                    if (min != null && min.length() > 0) {
                        res.add(fields[0] + " = '" + min + "'");
                    }
                    if (values.size() > 1) {
                        String max = values.get(1);
                        if (max != null && max.length() > 0) {
                            res.add(fields[0] + " = '" + max + "'");
                        }
                    }
                }
                break;
            }
            case range: {
                String min = values.get(0);
                String max = values.get(1);
                if (fuzzy) {
                    if (min != null && min.length() > 0) {
                        res.add(fields[1] + " >= " + min);
                    }
                    if (max != null && max.length() > 0) {
                        res.add(fields[0] + " <= " + max);
                    }
                } else {
                    if (min != null && min.length() > 0) {
                        res.add(fields[0] + " <= " + min);
                    }
                    if (max != null && max.length() > 0) {
                        res.add(fields[1] + " >= " + max);
                    }
                }
                break;
            }
            default:
        }
        return res;
    }

    /**
     * sql参数防注入格式化
     */
    private static String stringParamFormat(String str) {
        if (null != str) {
            return str.replace("'", "''");
        } else {
            return "null";
        }
    }


    // 参数格式校验
    private static void paramValidate(Pattern pattern, String... numbers) {
        for (String number : numbers) {
            if (number == null || number.length() == 0) {
                continue;
            }
            if (!pattern.matcher(number).matches()) {
                throw new RuntimeException("数值格式有误");
            }
        }
    }

    enum FieldType {
        /**
         * 文档表字段
         */
        content,
        /**
         * 动态字段
         */
        baseInfo,
        /**
         * 文本标签
         */
        textTag,
        /**
         * 标签值
         */
        valueTag

    }

    enum ValueType {
        text, date, single, range
    }
}
