package com.elab.es.client.spring;

import com.alibaba.druid.sql.SQLUtils;
import com.elab.core.aop.annotations.XmlGroupName;
import com.elab.core.bean.PageModel;
import com.elab.core.dao.IExampleDaoSupport;
import com.elab.core.dao.model.JdbcParamsModel;
import com.elab.core.dao.model.ListDynamicSearch;
import com.elab.core.dao.params.NamedParameterUtils2;
import com.elab.core.dao.params.ParsedSql2;
import com.elab.core.spring.base.EntityOperation;
import com.elab.core.spring.common.SqlInfoModel;
import com.elab.core.spring.common.TypeGroupModel;
import com.elab.core.spring.common.utils.BeanUtils;
import com.elab.core.spring.common.utils.DataUtils;
import com.elab.core.spring.method.DefaultSQLBuilderSupport;
import com.elab.core.spring.method.ISQLBuliderSupport;
import com.elab.core.sql.ConfigurableFactory;
import com.elab.core.sql.config.SqlCommandType;
import com.elab.core.utils.StringUtils;
import com.elab.es.client.DefaultElasticSearchTemplate;
import com.elab.es.client.db.IElasticSearchSupport;
import io.github.iamazy.elasticsearch.dsl.sql.ElasticSql2DslParser;
import io.github.iamazy.elasticsearch.dsl.sql.model.ElasticSqlParseResult;
import org.apache.commons.lang3.ArrayUtils;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.SqlParameterSource;

import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * dao层方法拦截根据指定的类型调用方法
 * <p>
 * 具体dao类执行的方法,根据方法名进行判断类型
 * </p>
 *
 * @author Liukx
 * @create 2018-01-03 11:36
 * @email liukx@elab-plus.com
 **/
public class ProxyMethodInvoke {

    /**
     * 具体的执行接口
     */
    private Class<?> mapperInterface;

    /**
     * 执行的方法
     */
    private Method method;

    /**
     * DB层的SQL执行器
     */
    private DefaultElasticSearchTemplate beanInvoke;

    /**
     * 父类的构造参数
     */
    private Class<?> supperGenricType;

    /**
     * 父类的接口
     */
    private String supperInterfaceName;

    /**
     * entity的操作对象
     */
    private EntityOperation entityOperation;

    /**
     * 配置文件对象,将sql.xml描述成对象
     */
    private ConfigurableFactory configurableFactory;

    /**
     * example类型 例如UserExample
     */
    private Class<?> exampleType;

    /**
     * 自动生成的SQL对象接口
     */
    private List<ISQLBuliderSupport> sqlBuilderSupportList = new ArrayList<ISQLBuliderSupport>();

    /**
     * 缓存一些反射的信息
     */
    private Map<String, Class<?>> methodReturnCacheMap = new ConcurrentHashMap<String, Class<?>>();

    private ElasticSql2DslParser parser = new ElasticSql2DslParser();

    /**
     * 默认的API接口描述
     */
    private final String defaultSupperInterface = IElasticSearchSupport.class.getName();

    /**
     * 默认的example的API接口对象
     */
    private final String defaultExampleInterface = IExampleDaoSupport.class.getName();

    /**
     * 当前类的sql文件中的groupName对象
     */
    private String xmlGroupName;

    private Logger logger = LoggerFactory.getLogger(ProxyMethodInvoke.class);

    /**
     * 构建一个DaoMethod的方法对象
     *
     * @param <T>
     * @param mapperInterface     dao的接口類
     * @param method              方法信息
     * @param invokeBean          jdbc操作类
     * @param configurableFactory 配置工厂
     */
    public <T> ProxyMethodInvoke(Class<?> mapperInterface, Method method, Object invokeBean, ConfigurableFactory configurableFactory) {
        // 接口信息
        this.mapperInterface = mapperInterface;
        // 方法信息
        this.method = method;
        // 具体操作类
        this.beanInvoke = (DefaultElasticSearchTemplate) invokeBean;
        // 配置工厂
        this.configurableFactory = configurableFactory;
        //默认先将初始化的sql构建加入进来
        this.sqlBuilderSupportList.add(new DefaultSQLBuilderSupport());
        // 类的信息提取
        // 父类的接口类
        this.supperInterfaceName = mapperInterface.getGenericInterfaces()[0].getTypeName();
        // 泛型对象
        List<String> classGenricTypeString = BeanUtils.getClassGenricTypeString(this.supperInterfaceName);
        // 第一个db对象
        this.supperGenricType = BeanUtils.getClassByString(classGenricTypeString.get(0));
        // 第二个,案例对象
        this.exampleType = classGenricTypeString.size() > 1 ? BeanUtils.getClassByString(classGenricTypeString.get(1)) : null;
        // 实体操作类
        this.entityOperation = new EntityOperation(this.supperGenricType, "id");
        if (this.mapperInterface.isAnnotationPresent(XmlGroupName.class)) {
            this.xmlGroupName = this.mapperInterface.getAnnotation(XmlGroupName.class).value();
        }
    }

    /**
     * 具体执行sql的方法
     * 1. 判断sql的类型
     * 2. 根据类型判断调用的方法
     * 3.
     *
     * @param args
     * @return
     */
    public Object execute(Object[] args) throws Exception {
        int argsLength = args.length;
        String sqlSource = method.getName();
        String statementName = getSqlId();
        String tableName = this.entityOperation.tableName;
        Object arg = args[0];
        SqlCommandType sqlType = this.configurableFactory.getSqlConfigurableFactory().getSqlType(statementName);

        // 如果是没有找到对应的SQL情况下
        if (sqlType == null) {
            // 表示配置文件中没有找到,则会从父类接口去获取
            if (!StringUtils.isEmpty(this.supperInterfaceName)) {
                if (this.supperInterfaceName.startsWith(defaultSupperInterface)) {
                    // 如果是其他增删改类型的方法
                    if (createSql(statementName)) {
                        return execute(args);
                    }
                }

                if (this.defaultExampleInterface.startsWith(defaultExampleInterface)) {
                    // 如果是查找类型的方法
                    if ("find".equals(method.getName())) {
                        return findMethodInvoke(arg);
                    }
                }
            }
            // 抛出异常
            throw new Exception(" 找不到[" + statementName + "]对应的处理sql");
        } else {
            // 这里是找到SQL的情况下
            String sql = this.configurableFactory.getSqlConfigurableFactory().getSql(statementName);
            logger.debug(" SQL ID : " + statementName);
            if (sqlType == SqlCommandType.INSERT) {
                if (arg instanceof List) {
                    return this.beanInvoke.saveBatch((List) arg);
                } else {
                    boolean save = this.beanInvoke.save(arg);
                    return save ? 1 : 0;
                }
            } else if (sqlType == SqlCommandType.UPDATE || sqlType == SqlCommandType.DELETE) {
                if (arg instanceof List) {
                    return this.beanInvoke.bulkUpdateBatch((List) arg);
                }
                boolean update = this.beanInvoke.update(arg);
                return update ? 1 : 0;
            } else {
                if (sqlType != SqlCommandType.SELECT) {
                    System.out.println("语句类型有误!");
                    return null;
                }
                return selectInvokeMethod(statementName, sql, args);
            }
        }
    }

    /**
     * 获取sql文件中的对应sql编号
     *
     * @return
     */
    private String getSqlId() {
        String statementName;
        if (StringUtils.isEmpty(this.xmlGroupName)) {
            statementName = mapperInterface.getName() + "." + method.getName();
        } else {
            statementName = this.xmlGroupName + "." + method.getName();
        }
        return statementName;
    }

    /**
     * 执行特殊的find的方法处理过程
     *
     * @param arg
     * @return
     */
    private Object findMethodInvoke(Object arg) {
        List oredCriteriaList = (List) BeanUtils.invokegetGetterMethod(arg, "oredCriteria");
        List<Object> paramsList = new LinkedList<Object>();
        StringBuffer sb = new StringBuffer();
        sb.append("select " + entityOperation.allColumn + " from " + entityOperation.tableName + " ");

        if (oredCriteriaList.size() > 0) {
            for (int i = 0; i < oredCriteriaList.size(); i++) {
                Object o = oredCriteriaList.get(i);
                List criteriaList = (List) BeanUtils.invokegetGetterMethod(o, "criteria");
                boolean flag = true;
                for (int j = 0; j < criteriaList.size(); j++) {
                    if (flag) {
                        sb.append(" where ");
                        flag = false;
                    } else {
                        sb.append(" and ");
                    }
                    Object o2 = criteriaList.get(j);
                    // key 条件
                    String condition = (String) BeanUtils.invokegetGetterMethod(o2, "condition");
                    // 对应的值
                    Object valueObject = BeanUtils.invokegetGetterMethod(o2, "value");
                    Object secondValue = BeanUtils.invokegetGetterMethod(o2, "secondValue");
                    // 如果是在集合的话需要单独处理  比如 in
                    if (valueObject instanceof List) {
                        List valueObject1 = (List) valueObject;
                        sb.append(condition + " ( ");
                        flag = false;
                        for (int k = 0; k < valueObject1.size(); k++) {
                            if (flag) {
                                sb.append(" , ");
                            }
                            Object o1 = valueObject1.get(i);
                            paramsList.add(o1);
                            sb.append(" ? ");
                            flag = true;
                        }
                        sb.append(" )");
                    } else {

                        sb.append(condition);

                        // is null , is not null
                        if (!com.elab.core.utils.ObjectUtils.isEmpty(valueObject)) {
                            paramsList.add(valueObject);
                            sb.append(" ? ");
                        }

                        // between 的情况
                        if (!com.elab.core.utils.ObjectUtils.isEmpty(secondValue)) {
                            sb.append(" and ? ");
                            paramsList.add(secondValue);
                        }
                    }
                }
            }
        }
        Object[] objects = paramsList.toArray();
        logger.debug(" SQL - " + sb.toString() + " \n " + Arrays.toString(objects));
        return null;
    }


    /**
     * 删除第一个and
     *
     * @param sql
     * @return
     */
    private String removeFirstAnd(String sql) {
        if (StringUtils.isEmpty(sql)) {
            return sql;
        }
        return sql.trim().toLowerCase().replaceAll("^\\s*and", "") + " ";
    }


    /**
     * 查询执行方法
     *
     * @param statementName SQL的唯一编号
     * @param sql           SQL执行的内容
     * @param args          参数列表
     * @return
     */
    private Object selectInvokeMethod(String statementName, String sql, Object[] args) throws Exception {
        Class<?> returnType = this.method.getReturnType();
        TypeGroupModel paramsByType = DataUtils.getParamsByType(this.method, args);
        Object arg = args[0];
        // 确定最终要执行的入参对象
        // 这里的处理方式就是要么就是Map、要么就是Object、要么就是普通数组
        if (paramsByType.getMap() != null && paramsByType.getMap().size() > 0) {
            arg = paramsByType.getMap();
        } else if (paramsByType.getObj() != null && paramsByType.getObj().length > 0) {
            arg = paramsByType.getObj()[0];
        }
        // 构建SQL相关的对象信息
        SqlInfoModel sqlInfo = getSqlInfo(statementName, sql, returnType, arg, paramsByType);
        return processSelectSQL(sql, returnType, paramsByType, arg);
    }

    /**
     * 执行查询SQL
     *
     * @param sql          SQL内容
     * @param returnType   SQL的返回结果
     * @param paramsByType SQL的特殊参数对象
     * @param arg          SQL的正常参数对象
     * @return
     * @throws Exception
     */
    private Object processSelectSQL(String sql, Class<?> returnType, TypeGroupModel paramsByType, Object arg) throws Exception {
        boolean returnsMany = (returnType == List.class);
        boolean returnsMap = (returnType == Map.class);
        boolean returnPage = (returnType == PageModel.class);
        // 如果需要追加动态参数对象处理
        ListDynamicSearch listDynamicSearch = paramsByType.getListDynamicSearch();
        Class typeClass = getCacheMethodReturnClass();
        // 是否是返回多个对象,例如List
        if (returnsMany) {
            if (typeClass == Map.class) {
                return executorSqlByList(sql, arg, listDynamicSearch, typeClass);
            } else {
                RowMapper rowMapper = paramsByType.getRowMapper();
                if (rowMapper == null) {
                    return executorSqlByList(sql, arg, listDynamicSearch, typeClass);
                } else {
                    return executorSqlByList(sql, arg, listDynamicSearch, typeClass);
                }
            }
        }
        // 返回是Map的
        else if (returnsMap) {
            return executorSqlByList(sql, arg, listDynamicSearch, typeClass);
        }
        // 返回是分页对象的
        else if (returnPage) {
            arg = paramsByType.getObj().length == 0 ? paramsByType.getMap() : paramsByType.getObj()[0];
            PageModel pageModel = paramsByType.getPageModel();

            if (pageModel == null) {
                throw new Exception(" PageModel is not null !!!");
            }

            String sqlCount = "select count(1) from ( " + sql + " ) a";
            String sqlLimit = " " + sql + " " + pageModel.getOrderby() + "  limit " + (pageModel.getCount() - 1) * pageModel.getPageSize() + "," + pageModel.getPageSize();
            List list = null;
            if (typeClass == Map.class) {
                list = (List) executorSqlByList(sql, pageModel, listDynamicSearch, typeClass);
            } else {
                list = (List) executorSqlByList(sql, pageModel, listDynamicSearch, typeClass);
            }
            pageModel.setResultSet(list);
            return pageModel;
        } else if (returnType == Integer.class || returnType == Long.class) {

        } else {
            if (returnType == Object.class) {
                returnType = supperGenricType;
            }
            return executorSqlByObject(sql, arg, listDynamicSearch, returnType);
        }
        return null;
    }

    private <T> List<T> executorSqlByList(String sql, Object arg, ListDynamicSearch listDynamicSearch, Class<T> typeClass) throws Exception {
        JdbcParamsModel jdbcParamsModel = commonParseSql(sql, arg, listDynamicSearch);
        Object[] objects = jdbcParamsModel.getObjects();
        //  select  postDate,id,message,user from posts where 1=1   and  user =?
        String newSQL = jdbcParamsModel.getSql().replace("1=1   and", " ");
        String formatMysql = SQLUtils.format(newSQL, "MYSQL", Arrays.asList(objects.clone())).replace("\n", " ");
        ElasticSqlParseResult sqlParseResult = parser.parse(formatMysql);
        SearchRequest searchRequest = sqlParseResult.getSearchRequest();
        return this.beanInvoke.search(searchRequest, typeClass);
    }

    private <T> T executorSqlByObject(String sql, Object arg, ListDynamicSearch listDynamicSearch, Class<T> typeClass) throws Exception {
        List<T> result = executorSqlByList(sql, arg, listDynamicSearch, typeClass);
        return result.size() == 0 ? null : result.get(0);
    }

    private SearchResponse executorSQL(String sql, Object arg, ListDynamicSearch listDynamicSearch) throws Exception {
        JdbcParamsModel jdbcParamsModel = commonParseSql(sql, arg, listDynamicSearch);
        Object[] objects = jdbcParamsModel.getObjects();
        //  select  postDate,id,message,user from posts where 1=1   and  user =?
        String newSQL = jdbcParamsModel.getSql().replace("1=1   and", " ");
        String formatMysql = SQLUtils.format(newSQL, "MYSQL", Arrays.asList(objects.clone())).replace("\n", " ");
        ElasticSqlParseResult sqlParseResult = parser.parse(formatMysql);
        SearchRequest searchRequest = sqlParseResult.getSearchRequest();
        SearchResponse search = this.beanInvoke.search(searchRequest);
        return search;
    }

    /**
     * 构建SQL相关的信息对象
     *
     * @param statementName SQL的id
     * @param sql           SQL的原始内容
     * @param returnType    SQL的返回类型
     * @param arg           SQL的参数
     * @param paramsByType  SQL的特殊参数组对象
     * @return
     */
    private SqlInfoModel getSqlInfo(String statementName, String sql, Class<?> returnType, Object arg,
                                    TypeGroupModel paramsByType) {
        SqlInfoModel sqlInfoModel = new SqlInfoModel();
        sqlInfoModel.setTableName(this.entityOperation.tableName);
        sqlInfoModel.setArgs(arg);
        sqlInfoModel.setSql(sql);
        sqlInfoModel.setReturnType(returnType);
        sqlInfoModel.setSqlId(statementName);
        sqlInfoModel.setReturnClass(getCacheMethodReturnClass());
        sqlInfoModel.setParamGroup(paramsByType);
        sqlInfoModel.setMapType(this.method.getGenericReturnType());
        return sqlInfoModel;
    }


    /**
     * 获取方法返回值
     *
     * @return
     */
    private Class getCacheMethodReturnClass() {
        String methodName = this.method.getName();
        Class typeClass = methodReturnCacheMap.get(methodName);
        if (typeClass == null) {
            Type type = this.method.getGenericReturnType();
            typeClass = BeanUtils.getClassGenricType(type.getTypeName());
            // 可能是父类interface的方法的情况
            if (typeClass == null) {
                typeClass = this.supperGenricType == null ? Map.class : this.supperGenricType;
            } else {
                methodReturnCacheMap.put(methodName, typeClass);
            }
        }
        return typeClass;
    }

    /**
     * 创建sql的执行方法
     *
     * @param statementName
     * @return
     * @throws Exception
     */
    private boolean createSql(String statementName) throws Exception {
        List<ISQLBuliderSupport> sqlBuliderSupportList = getSqlBuilderSupportList();
        String sql = "";
        boolean isSQLNotNull = false;
        for (int i = 0; i < sqlBuliderSupportList.size(); i++) {
            // 执行每个sql构建器来匹配
            ISQLBuliderSupport sqlBuilderSupport = sqlBuliderSupportList.get(i);
            sql = sqlBuilderSupport.doCreateSQL(entityOperation, method);
            if (!StringUtils.isEmpty(sql)) {
                isSQLNotNull = true;
                break;
            }
        }

        // 注册到sql容器中
        this.configurableFactory.getSqlConfigurableFactory().registerSqlMap(statementName, sql);
        return isSQLNotNull;
    }

    public JdbcParamsModel commonParseSql(String sql, Object param, ListDynamicSearch search) throws Exception {
        long start = System.currentTimeMillis();
        JdbcParamsModel model = new JdbcParamsModel();
        if (param instanceof String) {
            model.setSql(sql);
            model.setObjects(ArrayUtils.toArray(param));
            logger.debug(" 转换后的sql - " + sql);
            logger.debug(" 对应参数    -  " + param);
            return model;
        }
        // 将sql和参数进行绑定
        ParsedSql2 parsedSql = NamedParameterUtils2.parseSqlStatement(sql);
        // 解析参数类型 , 这里会根据Map和Object对象做装换
        SqlParameterSource sqlParameterSource = getSqlParameterSource(param);
        // 获取有效的参数,将为null和""的参数给替换掉
        Object[] data = NamedParameterUtils2.buildValueArray(parsedSql, sqlParameterSource, null);
        // 正常解析sql为jdbc与参数对应
        String s = NamedParameterUtils2.substituteNamedParameters(parsedSql, sqlParameterSource);
        // 校验第一个参数为空的情况下
        s = NamedParameterUtils2.validFirstCheckSql(sql, s, parsedSql, sqlParameterSource);
        // 替换动态表名存在的情况下
        s = NamedParameterUtils2.replaceDynamic(s, sqlParameterSource);

        if (search != null) {
            model = NamedParameterUtils2.appendSQLContent(s, data, search);
        } else {
            model.setSql(s);
            model.setObjects(data);
        }

        logger.debug(" 转换后的sql - " + model.getSql());
        logger.debug(" 对应参数    -  " + Arrays.toString(model.getObjects()));

        long time = System.currentTimeMillis() - start;
        logger.debug(" sql 语句处理耗时 : " + time);
        return model;
    }

    /**
     * 参数转换 , 参数类型分别为 Map,Model
     *
     * @param params
     * @return
     */
    private SqlParameterSource getSqlParameterSource(Object params) {
        SqlParameterSource sqlParameterSource = null;
        if (params instanceof Map) {
            sqlParameterSource = new MapSqlParameterSource((Map) params);
        } else {
            sqlParameterSource = new BeanPropertySqlParameterSource(params);
        }
        return sqlParameterSource;
    }


    public List<ISQLBuliderSupport> getSqlBuilderSupportList() {
        return sqlBuilderSupportList;
    }

    public void setSqlBuilderSupportList(List<ISQLBuliderSupport> sqlBuilderSupportList) {
        this.sqlBuilderSupportList = sqlBuilderSupportList;
    }
}
