package com.gci.boot.common.util;

import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.AnnotationUtils;

import java.beans.Introspector;
import java.lang.invoke.SerializedLambda;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.stream.IntStream;

@Slf4j
public final class QueryWrapperUtil {
    enum Operation{
        EQ,
        NE,
        GE,
        LE,
        GT,
        LT,
        LK,
        LLK,
        RLK,
        IN,
        NI,
        NU,
        NN;
    }

    public static <T> Builder<T> builder(){
        return new Builder<>();
    }

    public static class Builder<T>{

        //<列名, 操作类型>
        private Map<String, Operation> opMap = new HashMap<>();
        //<列名, 替换名>
        private Map<String, String> columnReplace = new HashMap<>();
        //<排序列名, 替换名>
        private Map<String, String> orderColumnReplace = new HashMap<>();
        //<[列名,表名]>
        private List<String[]> orderColumns;
        //<表名, 忽略的列>
        private Map<String, Set<String>> ignoreColumns;
        //排序
        private List<String> orders;
        //<表名, 别名>
        private Map<String, String> tableAlias;
        //<字段名, 表名>
        Map<String, String> columnTableRelations;
        //默认操作
        private Operation defaultOp = Operation.EQ;

        public <T> Builder<T> columnReplace(String column, String replace){
            columnReplace.put(StringUtils.camelToUnderline(column), replace);
            return (Builder<T>) this;
        }

        public <T> Builder<T> columnReplace(SFunction<T, ?> func, String replace){
            columnReplace.put(getFieldName(func), replace);
            return (Builder<T>) this;
        }

        public <T> Builder<T> orderColumnReplace(SFunction<T, ?> func, String replace){
            orderColumnReplace.put(getFieldName(func), replace);
            return (Builder<T>) this;
        }

        public <T> Builder<T> orderColumnReplace(String column, String replace){
            orderColumnReplace.put(StringUtils.camelToUnderline(column), replace);
            return (Builder<T>) this;
        }

        private <T> Builder<T> table(String tableName, String tableAlia){
            if(StringUtils.isBlank(tableName) || StringUtils.isBlank(tableAlia)){
                return (Builder<T>) this;
            }
            if(this.tableAlias == null){
                tableAlias = new HashMap<>();
            }
            tableAlias.put(StringUtils.camelToUnderline(tableName), tableAlia);
            return (Builder<T>) this;
        }

        public <T> Builder<T> table(Class<?> clazz, String tableAlia){
            if(clazz == null){
                return (Builder<T>) this;
            }
            if(columnTableRelations == null){
                columnTableRelations = new HashMap<>();
            }
            registerColumnTableRelations(clazz, columnTableRelations);
            return table(getTableName(clazz), tableAlia);
        }

        public <T> Builder<T> table(Class<?> clazz){
            if(clazz == null){
                return (Builder<T>) this;
            }
            if(columnTableRelations == null){
                columnTableRelations = new HashMap<>();
            }
            registerColumnTableRelations(clazz, columnTableRelations);
            return (Builder<T>) this;
        }

        private String getTableName(Class<?> clazz){
            TableName tableAnno = AnnotationUtils.findAnnotation(clazz, TableName.class);
            if(tableAnno != null){
                return tableAnno.value();
            }else{
                return StringUtils.camelToUnderline(clazz.getSimpleName());
            }
        }

        private <T> String getFuncTableName(SFunction<T, ?> func){
            Class<?> funcClass = getFuncClass(func);
            if(funcClass == null){
                return null;
            }
            return getTableName(funcClass);
        }


        public <T> Builder<T> ignoreColumn(Class<?> clazz, String column){
            return ignoreColumn(getTableName(clazz), column);
        }

        public <T> Builder<T> ignoreColumn(String tableName, String column){
            if(ignoreColumns == null){
                ignoreColumns = new HashMap<>();
            }
            Set<String> igCols = ignoreColumns.get(tableName);
            if(igCols == null){
                igCols = new HashSet<>();
                ignoreColumns.put(tableName, igCols);
            }
            igCols.add(StringUtils.camelToUnderline(column));
            return (Builder<T>) this;
        }

        public <T> Builder<T> ignoreColumn(SFunction<T, ?> func){
            return ignoreColumn(getFuncClass(func), getFieldName(func));
        }

        public <T> Builder<T> eq(String column){
            opMap.put(StringUtils.camelToUnderline(column), Operation.EQ);
            return (Builder<T>) this;
        }

        public <T> Builder<T> ne(String column){
            opMap.put(StringUtils.camelToUnderline(column), Operation.NE);
            return (Builder<T>) this;
        }
        public <T> Builder<T> ge(String column){
            opMap.put(StringUtils.camelToUnderline(column), Operation.GE);
            return (Builder<T>) this;
        }
        public <T> Builder<T> le(String column){
            opMap.put(StringUtils.camelToUnderline(column), Operation.LE);
            return (Builder<T>) this;
        }

        public <T> Builder<T> gt(String column){
            opMap.put(StringUtils.camelToUnderline(column), Operation.GT);
            return (Builder<T>) this;
        }
        public <T> Builder<T> lt(String column){
            opMap.put(StringUtils.camelToUnderline(column), Operation.LT);
            return (Builder<T>) this;
        }
        public <T> Builder<T> like(String column){
            opMap.put(StringUtils.camelToUnderline(column), Operation.LK);
            return (Builder<T>) this;
        }
        public <T> Builder<T> likeLeft(String column){
            opMap.put(StringUtils.camelToUnderline(column), Operation.LLK);
            return (Builder<T>) this;
        }
        public <T> Builder<T> likeRight(String column){
            opMap.put(StringUtils.camelToUnderline(column), Operation.RLK);
            return (Builder<T>) this;
        }
        public <T> Builder<T> in(String column){
            opMap.put(StringUtils.camelToUnderline(column), Operation.IN);
            return (Builder<T>) this;
        }
        public <T> Builder<T> notIn(String column){
            opMap.put(StringUtils.camelToUnderline(column), Operation.NI);
            return (Builder<T>) this;
        }
        public <T> Builder<T> isNull(String column){
            opMap.put(StringUtils.camelToUnderline(column), Operation.NU);
            return (Builder<T>) this;
        }

        public <T> Builder<T> isNotNull(String column){
            opMap.put(StringUtils.camelToUnderline(column), Operation.NN);
            return (Builder<T>) this;
        }


        //lambda表达式表示列
        public <T> Builder<T> eq(SFunction<T,?> func){
            return eq(getFieldName(func));
        }

        public <T> Builder<T> ne(SFunction<T,?> func){
            return ne(getFieldName(func));
        }
        public <T> Builder<T> ge(SFunction<T,?> func){
            return ge(getFieldName(func));
        }
        public <T> Builder<T> le(SFunction<T,?> func){
            return le(getFieldName(func));
        }

        public <T> Builder<T> gt(SFunction<T,?> func){
            return gt(getFieldName(func));
        }
        public <T> Builder<T> lt(SFunction<T,?> func){
            return lt(getFieldName(func));
        }
        public <T> Builder<T> like(SFunction<T,?> func){
            return like(getFieldName(func));
        }
        public <T> Builder<T> likeLeft(SFunction<T,?> func){
            return likeLeft(getFieldName(func));
        }
        public <T> Builder<T> likeRight(SFunction<T,?> func){
            return likeRight(getFieldName(func));
        }
        public <T> Builder<T> in(SFunction<T,?> func){
            return in(getFieldName(func));
        }
        public <T> Builder<T> notIn(SFunction<T,?> func){
            return notIn(getFieldName(func));
        }
        public <T> Builder<T> isNull(SFunction<T,?> func){
            return isNull(getFieldName(func));
        }

        public <T> Builder<T> isNotNull(SFunction<T,?> func){
            return isNotNull(getFieldName(func));
        }


        public <T> Builder<T> orderColums(String columns){
            if(StringUtils.isBlank(columns)){
                return (Builder<T>) this;
            }
            return orderColums(columns.split(","));
        }

        public <T> Builder<T> orders(String orders){
            if(StringUtils.isBlank(orders)){
                return (Builder<T>) this;
            }
            return orders(orders.split(","));
        }

        public <T> Builder<T> orderColums(String[] columns){
            if(orderColumns == null){
                orderColumns = new ArrayList<>();
            }
            Arrays.asList(columns).forEach(col->{
                orderColumns.add(new String[]{StringUtils.camelToUnderline(col), null});
            });
            return (Builder<T>) this;
        }

        public <T> Builder<T> orders(String[] orders){
            if(this.orders == null){
                this.orders = new ArrayList<>();
            }
            this.orders.addAll(Arrays.asList(orders));
            return (Builder<T>) this;
        }

        public <T> Builder<T> orderByAsc(String tableName, String column){
            if(orderColumns == null){
                orderColumns = new ArrayList<>();
            }

            orderColumns.add(new String[]{StringUtils.camelToUnderline(column), tableName});

            if(this.orders == null){
                this.orders = new ArrayList<>();
            }
            this.orders.add("ASC");
            return (Builder<T>) this;
        }

        public <T> Builder<T> orderByAsc(String column){
            return orderByAsc(null, column);
        }

        public <T> Builder<T> orderByAsc(SFunction<T,?> func){
            String table = getFuncTableName(func);
            String column = getFieldName(func);
            return orderByAsc(table, column);
        }

        public <T> Builder<T> orderByDesc(String column){
            return orderByDesc(null, column);
        }
        public <T> Builder<T> orderByDesc(SFunction<T,?> func){
            String table = getFuncTableName(func);
            String column = getFieldName(func);
            return orderByDesc(table, column);
        }

        public <T> Builder<T> orderByDesc(String tableName, String column){
            if(orderColumns == null){
                orderColumns = new ArrayList<>();
            }

            orderColumns.add(new String[]{StringUtils.camelToUnderline(column), tableName});

            if(this.orders == null){
                this.orders = new ArrayList<>();
            }
            this.orders.add("DESC");
            return (Builder<T>) this;
        }

        public <T> QueryWrapper<T> buildFromObject(Object... objects){
            return buildFromObject(null, objects);
        }

        public <T> QueryWrapper<T> buildFromObject(QueryWrapper<T> wrapper, Object... objects){
            if(wrapper == null){
                wrapper = new QueryWrapper();
            }
            if(objects == null || objects.length == 0){
                return wrapper;
            }
            Class<?>[] entityClasses = new Class[objects.length];
            try {
                for(int i=0; i < objects.length; i++) {
                    Object entity = objects[i];
                    entityClasses[i] = entity.getClass();
                    List<Field> fields = ClassReflectionUtil.getAllFields(entity.getClass());
                    String tableName = getTableName(entity.getClass());
                    for (Field f : fields) {
                        //跳过常量和静态变量
                        if (Modifier.isFinal(f.getModifiers()) || Modifier.isStatic(f.getModifiers())) {
                            continue;
                        }
                        String k = f.getName();
                        String colName = StringUtils.camelToUnderline(k);
                        //跳过忽略字段
                        if(ignoreColumns != null) {
                            Set<String> igCols = ignoreColumns.get(tableName);
                            if (igCols != null && igCols.contains(colName)) {
                                continue;
                            }
                        }
                        //处理别名
                        String columnName = useAliaIfExists(colName, tableName);
                        //获取值
                        boolean accessible = f.isAccessible();
                        f.setAccessible(true);
                        Object v = f.get(entity);
                        f.setAccessible(accessible);
                        //设置wrapper
                        setWrapper(wrapper, columnName, v, opMap.getOrDefault(colName, defaultOp));
                    }
                }
                setWrapperOrder(wrapper, orderColumns, orders, columnTableRelations);
                return wrapper;
            } catch (Exception e) {
                log.error("", e);
                return wrapper;
            }
        }

        public <T> QueryWrapper<T> buildFromMap( Map<String, Object> map){
            return buildFromMap(null, map);
        }

        public <T> QueryWrapper<T> buildFromMap(QueryWrapper<T> wrapper, Map<String, Object> map){
            if(wrapper == null){
                wrapper = new QueryWrapper();
            }
            if(map == null || map.size() == 0){
                return wrapper;
            }

            try {
                Set<String> keys = map.keySet();
                for (String k : keys) {
                    String colName = StringUtils.camelToUnderline(k);
                    String tableName = columnTableRelations.get(colName);
                    //跳过忽略字段
                    if(ignoreColumns != null) {
                        Set<String> igCols = ignoreColumns.get(tableName);
                        if (igCols != null && igCols.contains(colName)) {
                            continue;
                        }
                    }
                    //处理别名
                    String columnName = useAliaIfExists(colName, tableName);
                    //获取值
                    Object v = map.get(k);
                    //设置wrapper
                    setWrapper(wrapper, columnName, v, opMap.getOrDefault(colName, defaultOp));
                }
                setWrapperOrder(wrapper, orderColumns, orders, columnTableRelations);
                return wrapper;
            } catch (Exception e) {
                log.error("", e);
                return wrapper;
            }
        }

        private Map<String, String> registerColumnTableRelations( Class<?> cls, Map<String, String> columnTableRelations){
            if(columnTableRelations == null){
                columnTableRelations = new HashMap<>();
            }
            List<Field> fields = ClassReflectionUtil.getAllFields(cls);
            String tableName = getTableName(cls);
            for(Field f : fields){
                String col = StringUtils.camelToUnderline(f.getName());
                if(!columnTableRelations.containsKey(col)){
                    columnTableRelations.put(col, tableName);
                }
            }
            return columnTableRelations;
        }

        /**
         * 如果设置了列名替换，直接使用替换的列
         * 如果没有设置列名替换，添加表别名
         */
        private String useAliaIfExists(String colName, String tableName){
            String columnName = columnReplace.get(colName);
            if (columnName == null) {
                String tableAlia = null;
                if(tableAlias != null) {
                    tableAlia = tableAlias.get(tableName);
                }
                if (tableAlia != null) {
                    columnName = tableAlia + "." + colName;
                } else {
                    columnName = colName;
                }
            }
            return columnName;
        }

        //设置查询条件
        private void setWrapper(QueryWrapper wrapper, String columnName, Object v, Operation op){
            columnName = StringUtils.camelToUnderline(columnName);
            if(v == null){
                if(Operation.NU.equals(op)){
                    wrapper.isNull(columnName);
                }else if(Operation.NN.equals(op)){
                    wrapper.isNotNull(columnName);
                }
                return;
            }

            if(Operation.EQ.equals(op)){
                wrapper.eq(columnName, v);
            }else if(Operation.NE.equals(op)){
                wrapper.ne(columnName, v);
            }else if(Operation.GE.equals(op)){
                wrapper.ge(columnName, v);
            }else if(Operation.LE.equals(op)){
                wrapper.le(columnName, v);
            }else if(Operation.GT.equals(op)){
                wrapper.gt(columnName, v);
            }else if(Operation.LT.equals(op)){
                wrapper.lt(columnName, v);
            }else if(Operation.LK.equals(op)){
                wrapper.like(columnName, v);
            }else if(Operation.LLK.equals(op)){
                wrapper.likeLeft(columnName, v);
            }else if(Operation.RLK.equals(op)){
                wrapper.likeRight(columnName, v);
            }else if(Operation.IN.equals(op)){
                wrapper.in(columnName, v);
            }else if(Operation.NI.equals(op)){
                wrapper.notIn(columnName, v);
            }else if(Operation.NU.equals(op)){
                wrapper.isNull(columnName);
            }else if(Operation.NN.equals(op)){
                wrapper.isNotNull(columnName);
            }
        }

        //设置排序
        private void setWrapperOrder(QueryWrapper wrapper, List<String[]> columns, List<String> orders, Map<String, String> columnTableRelations){
            if(columns == null || columns.size() == 0){
                return;
            }
            IntStream.range(0, columns.size()).forEach(i->{
                String[] colAndTable  = columns.get(i);
                if(colAndTable[1] == null && columnTableRelations != null){
                    colAndTable[1] = columnTableRelations.get(colAndTable[0]);
                }
                String c = useAliaIfExists(colAndTable[0], colAndTable[1]);
                if(orders == null||orders.size() <= i){
                    wrapper.orderByAsc(c);
                }else{
                    if("ASC".equalsIgnoreCase(orders.get(i))){
                        wrapper.orderByAsc(c);
                    }else{
                        wrapper.orderByDesc(c);
                    }
                }
            });
        }
    }

    //根据lambda表达式获取字段名
    public static <T>String getFieldName(SFunction<T, ?> func){
        try {
            Method method = func.getClass().getDeclaredMethod("writeReplace");
            method.setAccessible(true);
            SerializedLambda serializedLambda = (SerializedLambda) method.invoke(func);
            String getterMethodName = serializedLambda.getImplMethodName();
            String fieldName = Introspector.decapitalize(getterMethodName.replace("get", ""));
            return fieldName;
        } catch (Exception e) {
            return null;
        }
    }

    //根据lambda表达式获取列名
    public static <T>String getColumnName(SFunction<T, ?> func){
        String fieldName = getFieldName(func);
        if(fieldName != null){
            return StringUtils.camelToUnderline(fieldName);
        }
        return null;
    }

    /**
     * 根据lambda表达式获取类
     * @param func
     * @return
     * @param <T>
     */
    public static <T> Class<?> getFuncClass(SFunction<T, ?> func){
        try {
            Method method = func.getClass().getDeclaredMethod("writeReplace");
            boolean accessible = method.isAccessible();
            method.setAccessible(true);
            SerializedLambda serializedLambda = (SerializedLambda) method.invoke(func);
            method.setAccessible(accessible);
            String className = serializedLambda.getImplClass().replace("/", ".");
            return Class.forName(className);
        } catch (Exception e) {
            return null;
        }
    }
}
