package com.yanzuoguang.dao.Impl;

import com.yanzuoguang.dao.TableAnnotation;
import com.yanzuoguang.db.DbExecute;
import com.yanzuoguang.db.Impl.DbRow;
import com.yanzuoguang.util.base.ObjectHelper;
import com.yanzuoguang.util.cache.MemoryCache;
import com.yanzuoguang.util.exception.CodeException;
import com.yanzuoguang.util.helper.StringHelper;
import com.yanzuoguang.util.vo.MapRow;
import com.yanzuoguang.util.vo.PageSizeData;
import com.yanzuoguang.util.vo.PageSizeReq;
import com.yanzuoguang.util.vo.PageSizeReqVo;

import javax.annotation.Resource;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * SQL语句基本操作类
 */
public abstract class BaseDaoSql {

    /**
     * 定义列表记录缓存对象
     */
    protected MemoryCache cacheList = new MemoryCache(0);

    /**
     * 数据库执行类
     */
    @Resource
    protected DbExecute db;

    /**
     * 当前Dao的表结构SQL语句信息
     */
    protected TableSqlCache Table = null;

    /**
     * 缓存的表结构和SQL语句
     */
    protected static MemoryCache<TableSqlCache> Cache = new MemoryCache<>();

    /**
     * 获取数据库执行类
     *
     * @return
     */
    protected DbExecute getDb() {
        return db;
    }

    /**
     * 构造函数
     */
    public BaseDaoSql() {
        this.initTable();
    }

    /**
     * 初始化当前类对应的SQL语句对象
     */
    private void initTable() {
        String cls = this.getClass().getName();
        // 外检测,防止锁影响性能
        this.Table = Cache.get(cls);
        // 判断是否已经取到对象
        if (this.Table == null) {
            this.Table = new TableSqlCache();
            Cache.put(cls, this.Table);
            this.init();
        }
    }

    /**
     * 注册SQL语句
     */
    protected abstract void init();

    /**
     * 注册表结构,会根据实体以及表名自动创建Create、update、remove、Load等SQL语句
     *
     * @param clsModel 操作的实体,主键放在第一位,其他字段放到后面;需要注意的是必需和表结构对应起来,会有隐性BUG,比如说在实体中增加了字段,会导致增加修改失败
     * @return 当前的表结构
     */
    protected TableSqlCache register(Class<?> clsModel) {
        TableAnnotation annotation = clsModel.getAnnotation(TableAnnotation.class);
        if (annotation == null) {
            throw new CodeException("该页面未绑定表");
        }
        return this.register(annotation.value(), clsModel);
    }

    /**
     * 注册表结构,会根据实体以及表名自动创建Create、update、remove、Load等SQL语句
     *
     * @param tableName 表名,数据库中的表名
     * @param clsModel  操作的实体,主键放在第一位,其他字段放到后面;需要注意的是必需和表结构对应起来,会有隐性BUG,比如说在实体中增加了字段,会导致增加修改失败
     * @return 当前的表结构
     */
    protected TableSqlCache register(String tableName, Class<?> clsModel) {
        // 生成表结构
        TableStruct table = new TableStruct(tableName, clsModel);
        // 根据表结构生成基本的SQL语句
        table.init(this.Table);
        return this.Table;
    }

    /**
     * 获取SQL语句
     *
     * @param name 需要获取的SQL语句的名称
     * @return 对应名称的SQL语句
     */
    protected SqlData getSql(String name) {
        return getSql(name, true);
    }

    /**
     * 获取SQL语句
     *
     * @param name 需要获取的SQL语句的名称
     * @return 对应名称的SQL语句
     */
    protected SqlData getSql(String name, boolean isThrow) {
        if (this.Table == null) {
            throw new CodeException("类" + this.getClass().getName() + "未发现表结构");
        }
        SqlData sql = this.Table.Sqls.get(name);
        if (isThrow && sql == null) {
            throw new CodeException("类" + this.getClass().getName() + "未发现SQL语句" + name);
        }
        return sql;
    }

    /**
     * 当更新数据时
     *
     * @param model 更新时的数据
     */
    protected void onUpdateSql(Object model) {
        // 删除所有查询的缓存
        cacheList.clear();
    }

    /**
     * 执行更新语句
     *
     * @param sqlData 需要执行的语句
     * @param model   执行的实体
     * @return 影响的行数
     */
    protected int updateSql(SqlData sqlData, Object model) {
        List<Object> paras = new ArrayList<Object>();
        String sql = this.getQueryPara(paras, sqlData, model);
        int ret = db.update(this.getClass(), sqlData.Name, sql, paras.toArray());
        this.onUpdateSql(model);
        return ret;
    }

    /**
     * 更新SQL语句
     *
     * @param sqlName 参数对应的名称
     * @param model   参数对应的实体
     * @return 修改成功,返回修改条数,否则为0
     */
    public int updateSql(String sqlName, Object model) {
        SqlData sqlData = this.getSql(sqlName);
        int ret = updateSql(sqlData, model);
        return ret;
    }

    /**
     * 根据SQL语句信息查询第一个单元格
     *
     * @param sqlData SQL语句信息
     * @param model   前台参数
     * @return 查询的结果
     */
    protected Object queryCell(SqlData sqlData, Object model) {
        List<Object> paras = new ArrayList<Object>();
        String sql = this.getQueryPara(paras, sqlData, model);
        Object cell = this.queryCellWithCache(sql, sqlData.Name, paras.toArray());
        return cell;
    }

    /**
     * 根据SQL名称获取第一个单元格
     *
     * @param sqlName 需要执行的SQL语句的名称
     * @param model   需要转化的实体
     * @return 处理参数
     */
    protected Object queryCell(String sqlName, Object model) {
        return this.queryCell(this.getSql(sqlName), model);
    }


    /**
     * 根据SQL语句信息查询数据
     *
     * @param cls     数据结果类型
     * @param sqlData SQL语句信息
     * @param model   前台参数
     * @param <T>     返回数据类型
     * @return 查询的结果
     */
    protected <T extends Object> List<T> queryData(Class<T> cls, SqlData sqlData, Object model) {
        List<Object> paras = new ArrayList<Object>();
        String sql = this.getQueryPara(paras, sqlData, model);
        List<T> list = this.queryWithCache(cls, sqlData.Name, sql, paras.toArray());
        return list;
    }

    /**
     * 查询不分页数据
     *
     * @param cls     数据结果类型
     * @param sqlData SQL数据
     * @param model   前台参数
     * @param <T>     返回数据类型
     */
    protected <T extends Object> void queryData(Class<T> cls, DbRow<T> handle, SqlData sqlData, Object model) {
        List<Object> paras = new ArrayList<Object>();
        String sql = this.getQueryPara(paras, sqlData, model);
        // 查询数据
        db.query(this.getClass(), cls, handle, sqlData.Name, sql, paras);
    }

    /**
     * 查询不分页数据
     *
     * @param cls     数据结果类型
     * @param sqlName SQL语句名称
     * @param model   前台参数
     * @param <T>     返回数据类型
     * @return 查询的结果
     */
    protected <T extends Object> List<T> query(Class<T> cls, String sqlName, Object model) {
        SqlData sqlData = this.getSql(sqlName);
        return queryData(cls, sqlData, model);
    }

    /**
     * 查询不分页数据
     *
     * @param cls     数据结果类型
     * @param handle  需要处理的数据
     * @param sqlName SQL语句名称
     * @param model   前台参数
     * @return 查询的结果
     */
    protected <T extends Object> void query(Class<T> cls, DbRow<T> handle, String sqlName, Object model) {
        SqlData sqlData = this.getSql(sqlName);
        queryData(cls, handle, sqlData, model);
    }

    /**
     * 查询第一条数据
     *
     * @param cls     数据结果类型
     * @param sqlName SQL语句名称
     * @param model   前台参数
     * @param <T>     返回数据类型
     * @return 查询的结果
     */
    protected <T extends Object> T queryFirst(Class<T> cls, String sqlName, Object model) {
        PageSizeReq pageSize = new PageSizeReqVo();
        pageSize.setPageIndex(1);
        pageSize.setPageSize(1);
        List<T> list = this.queryPageData(cls, pageSize, sqlName, model);
        T retVal = list.size() > 0 ? list.get(0) : null;
        return retVal;
    }

    /**
     * 查询分页数据,仅仅只是查询分页中的数据,不查询分页信息。如:包含的总数据数量
     *
     * @param cls      数据结果类型
     * @param pageSize 分页参数
     * @param sqlName  SQL语句名称
     * @param model    前台参数
     * @param <T>      返回数据类型
     * @return 查询的结果
     */
    protected <T extends Object> List<T> queryPageData(Class<T> cls, PageSizeReq pageSize, String sqlName, Object model) {
        SqlData from = this.getSql(sqlName);
        // 对SQL语句进行分页处理
        SqlData to = from.copy();
        to.Sql = from.Sql;
        to.addCode("{LIMIT}", " LIMIT " + pageSize.getPageStart() + "," + pageSize.getPageSize());
        return queryData(cls, to, model);
    }

    /**
     * 查询分页数据,仅仅只是查询分页中的数据。如:包含的总数据数量
     *
     * @param cls      数据结果类型
     * @param pageSize 分页参数
     * @param sqlName  SQL语句名称
     * @param model    前台参数
     * @param <T>      返回数据类型
     * @return 查询的结果
     */
    protected <T extends Object> PageSizeData<T> queryPage(Class<T> cls, PageSizeReqVo pageSize, String sqlName, Object model) {
        // 获取需要执行的SQL语句
        SqlData from = this.getSql(sqlName);

        // 设置基本参数值
        PageSizeData<T> data = new PageSizeData<T>();
        data.setPageIndex(pageSize.getPageIndex());
        data.setPageSize(pageSize.getPageSize());

        // 对SQL语句进行分页处理
        SqlData to = from.copy();
        to.addCode("{LIMIT}", " LIMIT " + pageSize.getPageStart() + "," + pageSize.getPageSize());

        // 按照分页查询数据
        List<Object> paras = new ArrayList<Object>();
        String sql = this.getQueryPara(paras, to, model);
        // 查询实体数据
        List<T> list = this.queryWithCache(cls, sqlName, sql, paras.toArray());
        data.setList(list);

        // 查询分页总条数的SQL语句
        String sqlTo = sql;
        // 获取分页查询的SQL语句
        SqlData fromPageSize = getSql(sqlName + TableSqlCache.PAGE_SIZE_TAG, false);
        if (fromPageSize != null) {
            sqlTo = getQueryPara(new ArrayList<>(), fromPageSize, model);
        }

        // 查询总数据量
        String sqlSize = "SELECT COUNT(1) FROM (" +
                sqlTo.replaceAll("(?i)(ORDER\\s+BY\\s+[^)]+){0,1}(limit\\s+\\d+,\\d+\\s*){0,1}$", "")
                + ") t";
        data.setPageTotal(StringHelper.toInt(queryCellWithCache(String.format("%s.Size", sqlName), sqlSize, paras.toArray())));

        return data;
    }

    /**
     * 根据SQL语句信息查询数据
     *
     * @param sqlData SQL语句信息
     * @param model   前台参数
     * @return 查询的结果
     */
    protected List<MapRow> queryData(SqlData sqlData, Object model) {
        return this.queryData(MapRow.class, sqlData, model);
    }

    /**
     * 查询不分页数据
     *
     * @param sqlName SQL语句名称
     * @param model   前台参数
     * @return 查询的结果
     */
    protected List<MapRow> query(String sqlName, Object model) {
        return this.query(MapRow.class, sqlName, model);
    }

    /**
     * 查询第一条数据
     *
     * @param sqlName SQL语句名称
     * @param model   前台参数
     * @return 查询的结果
     */
    protected MapRow queryFirst(String sqlName, Object model) {
        return this.queryFirst(MapRow.class, sqlName, model);
    }

    /**
     * 查询分页数据,仅仅只是查询分页中的数据,不查询分页信息。如:包含的总数据数量
     *
     * @param pageSize 分页参数
     * @param sqlName  SQL语句名称
     * @param model    前台参数
     * @return 查询的结果
     */
    protected List<MapRow> queryPageData(PageSizeReqVo pageSize, String sqlName, Object model) {
        return this.queryPageData(MapRow.class, pageSize, sqlName, model);
    }

    /**
     * 查询分页数据,仅仅只是查询分页中的数据,不查询分页信息。如:包含的总数据数量
     *
     * @param pageSize 分页参数
     * @param sqlName  SQL语句名称
     * @param model    前台参数
     * @return 查询的结果
     */
    protected PageSizeData<MapRow> queryPage(PageSizeReqVo pageSize, String sqlName, Object model) {
        return this.queryPage(MapRow.class, pageSize, sqlName, model);
    }


    /**
     * 获取SQL语句的参数
     *
     * @param paras   SQL语句
     * @param sqlData SQL语句的参数
     * @param model   参数的实体
     * @return SQL条件
     */
    protected String getQueryPara(List<Object> paras, SqlData sqlData, Object model) {

        // 定义可替换片段
//        String fromNext = "{FIELD_Front}";
//        String fromPrev = "{FIELD}";
//        String wherePrev = "{INNER}";
        String[] lastCode = new String[]{"{WHERE}", "{GROUP}", "{HAVING}", "{ORDER}", "{LIMIT}"};

        // 将SQL语句进行代码片段追加
        String sql = sqlData.Sql;
        for (String code : lastCode) {
            if (sql.indexOf(code) == -1) {
                sql += code;
            }
        }

        // 处理字段以及代码片段
        Map<String, List<String>> codeMap = new HashMap<String, List<String>>();        // 代码片段缓存
        // 循环处理字段
        for (SqlDataField field : sqlData.sqlDataFields) {
            // 获取值
            Object val = ObjectHelper.get(model, field.paraName);

            // 不需要输入参数,仅仅只是代码片段
            boolean isCode = StringHelper.isEmpty(field.paraName);
            // 判断是否属于
            if (!isCode) {
                // 判断是否属于条件
                boolean isCond = field.codes.size() > 0 && field.auto;
                //  处理参数
                if (!isCond) {
                    // 进行SQL语句参数替换,后面增加一个空格,方便后续用正则表达式进行替换处理
                    sql = sql.replaceFirst("\\?", "@" + field.paraName + " ");
                }
                if (isCond && StringHelper.isEmpty(val)) {
                    continue;
                }
            }

            // 判断代码片段是否合法
            if (field.codes.size() % 2 == 1) {
                throw new CodeException("代码片段" + this.getClass().getSimpleName() + ":" + sqlData.Name + ":" + field.paraName + "为单数");
            }
            // 处理代码片段
            for (int i = 0; i < field.codes.size(); i = i + 2) {
                String codeName = field.codes.get(i);
                String codeValue = field.codes.get(i + 1);
                codeValue = codeValue.replaceAll("\\?", "@" + field.paraName + " ");
                addCodeMap(codeMap, codeName, codeValue);
            }
        }

        // 正则表达式匹配代码片段,并根据代码片段获取数组进行增加
        {
            String regex = "\\{.*?\\}";
            Pattern p = Pattern.compile(regex);
            Matcher m = p.matcher(sql);
            // 寻找到的代码片段 不包含分括号
            while (m.find()) {
                String name = m.group();
                // m.group(1);
                List<String> codes = codeMap.containsKey(name) ? codeMap.get(name) : new ArrayList<String>();
                String code = String.join("", codes);
                sql = sql.replace(name, code);
            }
        }

        // 通过正则表达式处理参数 @Name  并替换SQL语句成 ?
        {
            String regex = "@([a-zA-Z0-9_]+)";
            Pattern p = Pattern.compile(regex);
            Matcher m = p.matcher(sql);
            // 寻找到的代码片段 不包含分括号
            while (m.find()) {
                // SQL参数名称 @Name\s
                String name = m.group();
                // 对应的前台输入字段 Name
                String field = m.group(1);
                // 根据输入字段从参数中取值
                Object val = ObjectHelper.get(model, field);
                // 判断是否为数组
                if (val != null && (val instanceof List || val.getClass().isArray())) {
                    sql = getListSql(paras, sql, name, val);
                } else {
                    // 当参数不为数组时,则直接增加
                    sql = sql.replaceFirst(name, "?");
                    val = this.getParaValue(val);
                    paras.add(val);
                }
            }
        }

        return sql;
    }

    /**
     * 获取处理后的参数值
     *
     * @param val 获取参数值
     * @return 处理后的参数值
     */
    private Object getParaValue(Object val) {
        if (val instanceof Boolean) {
            val = 0;
        }
        val = StringHelper.toString(val);
        return val;
    }

    /**
     * 获取列表的SQL语句
     *
     * @param paras 参数
     * @param sql   SQL语句
     * @param name  名称
     * @param val   值
     * @return 获取包含列表的SQL语句的值
     */
    private String getListSql(List<Object> paras, String sql, String name, Object val) {
        List list;

        // 判断处理
        boolean isList = val instanceof List;
        if (isList) {
            list = (List) val;
        } else {
            Object[] arr = (Object[]) val;
            list = Arrays.asList(arr);
        }
        int length = list.size();

        //  进行循环
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < length; i++) {
            Object item = list.get(i);
            // 判断列表是否为常规对象
            if (item == null || item instanceof Number || item.getClass().isEnum() || item instanceof String || item instanceof Date) {
                addArrayIn(paras, sb, i, item);
            } else if (item instanceof CaseSqlModel) {
                addArrayCase(paras, sb, i, (CaseSqlModel) item);
            } else {
                addArrayTable(paras, sb, i, item);
            }
        }

        // 生成最终的SQL语句
        sql = sql.replaceFirst(name, sb.toString());
        return sql;
    }


    /**
     * 添加数组型参数
     *
     * @param paras 参数列表
     * @param sb    字符串数组
     * @param i     序号
     * @param from  参数值
     */
    private void addArrayIn(List<Object> paras, StringBuilder sb, int i, Object from) {
        // 添加连接字符串
        if (i > 0) {
            sb.append(",");
        }

        // 添加参数SQL语句
        sb.append("?");
        Object to = this.getParaValue(from);
        paras.add(to);
    }

    /**
     * 添加 CASE型字段
     *
     * @param paras 参数列表
     * @param sb    字符串数组
     * @param i     序号
     * @param from  参数值
     */
    private void addArrayCase(List<Object> paras, StringBuilder sb, int i, CaseSqlModel from) {
        // 添加连接字符串
        if (i > 0) {
            sb.append(",");
        }
        switch (from.getGroupType()) {
            case CaseSqlModel.GroupTypeSUM:
                sb.append("SUM");
                break;
            case CaseSqlModel.GroupTypeCOUNT:
                sb.append("COUNT");
                break;
            case CaseSqlModel.GroupTypeMAX:
                sb.append("MAX");
                break;
            case CaseSqlModel.GroupTypeMIN:
                sb.append("MIN");
                break;
            case CaseSqlModel.GroupTypeAVG:
                sb.append("AVG");
                break;
            default:
                throw new CodeException("统计类型[" + from.getGroupType() + "]不支持");
        }
        sb.append("( CASE WHEN ");
        sb.append(from.getCaseField());
        sb.append(" ");
        if (from.getCaseValue() != null) {
            sb.append("=?");
            paras.add(from.getCaseValue());
        }
        sb.append(from.getAndWhere());
        sb.append(" THEN ");
        if (!StringHelper.isEmpty(from.getValueField())) {
            sb.append(from.getValueField());
        } else if (!StringHelper.isEmpty(from.getValue())) {
            sb.append("?");
            paras.add(from.getValue());
        }
        sb.append(" ELSE 0 END ) AS ");
        sb.append(from.getToName());
    }

    /**
     * 添加表格型参数,将列表转换成表格
     *
     * @param paras 参数列表
     * @param sb    字符串数组
     * @param i     序号
     * @param from  参数值
     */
    private void addArrayTable(List<Object> paras, StringBuilder sb, int i, Object from) {
        // 添加连接字符串
        if (i > 0) {
            sb.append(" UNION ALL ");
        }

        // 将对象转换为 Map 对象
        Map to = new HashMap<String, Object>();
        if (from instanceof Map) {
            to = (Map) from;
        } else {
            ObjectHelper.writeWithFromClass(to, from);
        }

        // 生成SQL语句
        int column = 0;
        sb.append(" SELECT ");

        // 处理列
        for (Object oItemKey : to.keySet()) {
            if (column > 0) {
                sb.append(",");
            }

            String itemKey = StringHelper.toString(oItemKey);
            sb.append("? AS " + itemKey);

            // 处理参数
            Object itemValue = to.get(oItemKey);
            itemValue = this.getParaValue(itemValue);
            paras.add(itemValue);
            column++;
        }
    }

    /**
     * 将代码片段添加到SQL语句中
     *
     * @param codeMap 映射关系
     * @param name    执行的代码片段
     * @param code    代码片段
     */
    private void addCodeMap(Map<String, List<String>> codeMap, String name, String code) {
        if (!codeMap.containsKey(name)) {
            codeMap.put(name, new ArrayList<String>());
        }
        List<String> arr = codeMap.get(name);
        arr.add(code);
    }

    /**
     * 根据缓存查询第一个单元格
     *
     * @param sqlName 需要执行的SQL语句名称
     * @param sql     需要执行的SQL语句
     * @param paras   SQL语句的参数
     * @return 查询的结果
     */
    protected Object queryCellWithCache(String sqlName, String sql, Object... paras) {

        // 生成缓存的主键
        String cacheKey = getCacheKey(Object.class, sql, paras);

        // 获取缓存
        Object cache = this.cacheList.get(cacheKey);

        // 返回缓存
        if (cache != null) {
            return cache;
        }

        // 查询数据
        Object ret = db.queryCell(this.getClass(), sqlName, sql, paras);
        // 写入缓存
        this.cacheList.put(cacheKey, ret);

        return ret;
    }

    /**
     * 执行查询SQL语句,并将数据缓存起来
     *
     * @param cls     返回的类型
     * @param sqlName 需要执行的SQL语句名称
     * @param sql     需要执行的SQL语句
     * @param paras   执行的SQL语句的参数
     * @param <T>     返回的泛型
     * @return 需要返回的数据
     */
    protected <T> List<T> queryWithCache(Class<T> cls, String sqlName, String sql, Object... paras) {
        // 生成缓存的主键
        String cacheKey = getCacheKey(cls, sql, paras);

        // 获取缓存
        Object cache = this.cacheList.get(cacheKey);

        // 返回缓存
        if (cache != null) {
            return (List<T>) cache;
        }

        // 查询数据
        List<T> ret = db.query(this.getClass(), cls, sqlName, sql, paras);

        // 写入缓存
        this.cacheList.put(cacheKey, ret);
        return ret;
    }

    /**
     * 获取缓存的键值对
     *
     * @param cls   结果类型
     * @param sql   SQL语句
     * @param paras SQL语句参数
     * @return 返回的结果
     */
    protected String getCacheKey(Class<?> cls, String sql, Object... paras) {
        StringBuilder sb = new StringBuilder();
        sb.append(cls.getName());
        sb.append(":");
        sb.append(sql);
        for (Object para : paras) {
            sb.append(":");
            sb.append(para);
        }
        return StringHelper.md5(sb.toString());
    }
}