package com.cwhelp.common.data;

import com.cwhelp.common.enums.ResultEnum;
import com.cwhelp.common.enums.StatusEnum;
import com.cwhelp.common.exception.ResultException;
import org.springframework.data.jpa.domain.Specification;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.util.*;

/**
 * @author yan.y
 */
public class QuerySpec {
    // 查询规则
    public static final Long EQUAL = 0L;              //精确查询(=)
    public static final Long LIKE = 1L;               //模糊查询(*XX*)
    public static final Long LEFT_LIKE = 2L;          //左模糊查询(*XX)
    public static final Long RIGHT_LIKE = 3L;         //右模糊查询(XX*)
    public static final Long NOT_EQUAL = 4L;          //不等于(!=)
    public static final Long GT = 5L;                 //大于(>)
    public static final Long GE = 6L;                 //大于等于(>=)
    public static final Long LT = 7L;                 //小于(<)
    public static final Long LE = 8L;                 //小于等于(<=)
    public static final Long IN = 9L;                 //多值(in)
    public static final Long BETWEEN = 10L;           //区间查询(between)

    // 字段规则列表
    private Map<String, Long> fieldRules;
    // 忽视字段
    private String[] ignoredPaths;
    // 多值in查询方式值列表
    private Map<String, List<Object>> inValues;
    // 区间between查询方式值列表
    private Map<String, Long[]> betweenValues;
    // 状态字段名称
    private String status = "status";

    // 无参构造方法，初始化数据
    private QuerySpec(){
        fieldRules = new HashMap<>();
        inValues = new HashMap<>();
        betweenValues = new HashMap<>();
    }

    /**
     * 创建查询匹配器
     */
    public static QuerySpec matching(){
        return new QuerySpec();
    }

    /**
     * 添加一条匹配规则
     * @param propertyPath 需要验证的字段名称
     * @param regulation 查询规则
     */
    public QuerySpec withMatcher(String propertyPath, Long regulation){
        fieldRules.put(propertyPath, regulation);
        return this;
    }

    /**
     * 添加一条IN匹配规则
     * @param propertyPath 需要验证的字段名称
     * @param inValueList IN数据列表
     */
    public QuerySpec withMatcherIn(String propertyPath, List<Object> inValueList){
        fieldRules.put(propertyPath, IN);
        inValues.put(propertyPath, inValueList);
        return this;
    }

    /**
     * 添加一条BETWEEN匹配规则
     * @param propertyPath 需要验证的字段名称
     * @param x 第一个值
     * @param y 第二个值
     */
    public QuerySpec withMatcherBetween(String propertyPath, Long x, Long y){
        fieldRules.put(propertyPath, BETWEEN);
        betweenValues.put(propertyPath, new Long[]{x, y});
        return this;
    }

    /**
     * 设置忽略的字段
     * @param ignoredPaths 忽略字段
     */
    public QuerySpec withIgnorePaths(String... ignoredPaths){
        this.ignoredPaths = ignoredPaths;
        return this;
    }

    /**
     * 获取规则列表
     */
    private Map<String, Long> getFieldRules(){
        return fieldRules;
    }

    /**
     * 获取忽略字段列表
     */
    private List<String> getIgnoredPaths(){
        return Arrays.asList(ignoredPaths);
    }

    /**
     * 获取in查询方式值列表
     */
    private Map<String, List<Object>> getInValues(){
        return inValues;
    }

    /**
     * 获取区间between查询方式值列表
     */
    private Map<String, Long[]> getBetweenValues(){
        return betweenValues;
    }

    /**
     * 创建Specification<T>复杂查询实例
     * @param probe 实体对象
     * @param querySpec 匹配器
     */
    public static <T> Specification<T> of(T probe, QuerySpec querySpec){

        // 获取用户规则
        Map<String, Long> fieldRules = querySpec.getFieldRules();
        // 获取忽略字段
        List<String> ignoredPaths = querySpec.getIgnoredPaths();
        // 获取in查询方式值列表
        Map<String, List<Object>> inValues = querySpec.getInValues();
        // 获取in查询方式值列表
        Map<String, Long[]> betweenValues = querySpec.getBetweenValues();

        // 创建Specification<T>对象
        Specification<T> specification = new Specification<T>(){

            @Override
            public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb) {

                List<Predicate> preList = new ArrayList<>();
                // 通过反射遍历实体对象
                try {
                    final BeanInfo bi = Introspector.getBeanInfo(probe.getClass());
                    for (final PropertyDescriptor pd : bi.getPropertyDescriptors()) {
                        final Object value = pd.getReadMethod().invoke(probe, (Object[]) null);
                        if(!(value instanceof Class) && value != null && !ignoredPaths.contains(pd.getName())){
                            // 判断是否对数据状态进行非法请求
                            if(querySpec.status.equals(pd.getName()) && StatusEnum.DELETE.getCode().equals(Byte.valueOf(String.valueOf(value)))){
                                throw new ResultException(ResultEnum.STATUS_ERROR);
                            }

                            // 进行查询匹配器配对
                            if(fieldRules.containsKey(pd.getName())){
                                Long regulation = fieldRules.get(pd.getName());
                                // 精确查询
                                if(regulation.equals(QuerySpec.EQUAL)){
                                    preList.add(cb.equal(root.get(pd.getName()).as(value.getClass()), value));
                                }else
                                // 模糊查询
                                if(regulation.equals(QuerySpec.LIKE)){
                                    preList.add(cb.like(root.get(pd.getName()).as(String.class), "%"+ String.valueOf(value) +"%"));
                                }else
                                // 左模糊查询
                                if(regulation.equals(QuerySpec.LEFT_LIKE)){
                                    preList.add(cb.like(root.get(pd.getName()).as(String.class), "%"+ String.valueOf(value)));
                                }else
                                // 右模糊查询
                                if(regulation.equals(QuerySpec.RIGHT_LIKE)){
                                    preList.add(cb.like(root.get(pd.getName()).as(String.class), String.valueOf(value) +"%"));
                                }else
                                // 不等于查询
                                if(regulation.equals(QuerySpec.NOT_EQUAL)){
                                    preList.add(cb.notEqual(root.get(pd.getName()).as(value.getClass()), value));
                                }else
                                // 大于查询
                                if(regulation.equals(QuerySpec.GT)){
                                    preList.add(cb.gt(root.get(pd.getName()).as(Long.class), Long.valueOf(String.valueOf(value))));
                                }else
                                // 大于等于查询
                                if(regulation.equals(QuerySpec.GE)){
                                    preList.add(cb.ge(root.get(pd.getName()).as(Long.class), Long.valueOf(String.valueOf(value))));
                                }else
                                // 小于查询
                                if(regulation.equals(QuerySpec.LT)){
                                    preList.add(cb.lt(root.get(pd.getName()).as(Long.class), Long.valueOf(String.valueOf(value))));
                                }else
                                // 小于等于查询
                                if(regulation.equals(QuerySpec.LE)){
                                    preList.add(cb.le(root.get(pd.getName()).as(Long.class), Long.valueOf(String.valueOf(value))));
                                }else
                                // 多值查询
                                if(regulation.equals(QuerySpec.IN)){
                                    CriteriaBuilder.In<Object> in = cb.in(root.get(pd.getName()));
                                    List<Object> inList = inValues.get(pd.getName());
                                    if(inList != null){
                                        inList.forEach(in::value);
                                        preList.add(in);
                                    }
                                }else
                                // 区间查询
                                if(regulation.equals(QuerySpec.BETWEEN)){
                                    Long[] between = betweenValues.get(pd.getName());
                                    preList.add(cb.between(root.get(pd.getName()), between[0], between[1]));
                                }
                            }else{
                                preList.add(cb.equal(root.get(pd.getName()).as(value.getClass()), value));
                            }
                        }
                    }
                } catch (InvocationTargetException | IntrospectionException | IllegalAccessException e) {
                    throw new IllegalArgumentException("获取实体类数据时出错，请检查实体类Bean格式是否规范！", e);
                }

                Predicate[] pres = new Predicate[preList.size()];
                return query.where(preList.toArray(pres)).getRestriction();
            }
        };

        return specification;
    }
}
