package com.yanzuoguang.dao.impl;

import com.yanzuoguang.dao.DaoConst;
import com.yanzuoguang.util.YzgError;
import com.yanzuoguang.util.base.ObjectHelper;
import com.yanzuoguang.util.cache.MemoryCache;
import com.yanzuoguang.util.helper.StringHelper;

import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 缓存的SQL语句信息
 *
 * @author 颜佐光
 */
public class TableSqlCache {

    /**
     * 分页标记
     */
    public static final String PAGE_SIZE_TAG = "_PageSize";

    /**
     * 表结构信息
     */
    private TableStruct table;

    /**
     * 缓存的SQL信息,按照名称进行缓存
     */
    private MemoryCache<SqlData> nameCache = new MemoryCache<SqlData>();

    /**
     * 根据Sql语句类型进行缓存,按照类型进行缓存
     */
    private final MemoryCache<List<SqlData>> typeCache = new MemoryCache<>();

    /**
     * 构造函数
     */
    public TableSqlCache() {
        this.table = new TableStruct();
    }


    /**
     * 获取所有的Sql语句执行类型
     *
     * @param sqlType Sql语句执行类型
     * @return Sql语句列表
     */
    public List<SqlData> getSqlType(int sqlType) {
        String keyType = String.valueOf(sqlType);
        return typeCache.get(keyType);
    }

    /**
     * 添加SQL语句组
     *
     * @param sqls
     */
    public void add(SqlData... sqls) {
        for (SqlData sql : sqls) {
            this.add(sql);
        }
    }

    /**
     * 添加SQL
     *
     * @param sqlName SQL名称
     * @param sql     SQL语句
     * @param fields  字段
     */
    public SqlData add(String sqlName, String sql, String... fields) {
        SqlData model = new SqlData(sqlName, sql, fields);
        return this.add(model);
    }

    /**
     * 根据现有SQL语句生成分页查询
     *
     * @param from
     * @param sql
     * @return
     */
    public SqlData addPageSize(SqlData from, String sql) {
        SqlData to = from.copy();
        to.setSql(sql);
        to.setName(to.getName() + TableSqlCache.PAGE_SIZE_TAG);
        return this.add(to);
    }

    /**
     * 添加可执行的SQL语句
     *
     * @param sql SQL语句
     */
    public SqlData add(SqlData sql) {
        if (sql != null) {
            this.nameCache.put(sql.getName(), sql);
            String keyType = String.valueOf(sql.getSqlType());
            this.typeCache.get(keyType, new ArrayList<>()).add(sql);
        }
        return sql;
    }

    /**
     * 生成GroupSQL语句
     *
     * @param tableWhereField
     * @param addField
     */
    public void addGroup(TableFieldString tableWhereField, TableFieldString addField) {
        this.table.addGroupSql(this, tableWhereField, addField);
    }

    /**
     * 生成GroupSQL语句
     *
     * @param tableWhereField
     * @param addField
     */
    public void addGroup(TableFieldString tableWhereField, TableFieldString replaceField, TableFieldString addField) {
        this.table.addGroupSql(this, tableWhereField, replaceField, addField);
    }

    /**
     * 生成GroupSQL语句
     *
     * @param sqlName
     * @param whereFields
     */
    public void addSaveWith(String sqlName, TableFieldString whereFields) {
        this.table.addSaveWithSql(this, sqlName, whereFields);
    }

    /**
     * 生成Group执行的SQL语句
     *
     * @param sqlName
     * @param tableWhereField
     * @param addField
     * @param sql
     * @param
     */
    public List<SqlData> addGroupList(String sqlName, TableFieldString tableWhereField, TableFieldString addField,
                                      String sql, String... field) {
        Map<String, Boolean> sqlFieldHas = new HashMap<>(DaoConst.COLLECTION_INIT_SIZE);
        Map<String, Boolean> sqlFieldWhere = new HashMap<>(DaoConst.COLLECTION_INIT_SIZE);
        Map<String, Boolean> sqlFieldAdd = new HashMap<>(DaoConst.COLLECTION_INIT_SIZE);

        SqlData sqlInit = new SqlData(sqlName + "_GroupInit", "", field);
        SqlData sqlAdd = new SqlData(sqlName + "_GroupAdd", "", field);

        // 1. 获取SQL语句中已包含的字段
        String sqlField = sql.substring(0, sql.replace("\n", " ")
                .replace("\t", " ")
                .toLowerCase().indexOf(" from ")) + ",";
        // 去掉字段中的函数
        sqlField = sqlField.replaceAll("\\(.+?\\)", "");
        String regex = "[\\s,]*(\\S+?)\\s*?,";
        Pattern p = Pattern.compile(regex);
        Matcher m = p.matcher(sqlField);
        // 寻找到的代码片段 不包含分括号
        while (m.find()) {
            String name = m.group(1);
            if (name.indexOf(".") >= -1) {
                name = name.substring(name.indexOf(".") + 1);
            }
            sqlFieldHas.put(name.toLowerCase(), true);
        }

        // 2. 判断所有纬度字段是否已经存在
        for (String item : tableWhereField.getFields()) {
            String name = item.toLowerCase();
            if (!sqlFieldHas.containsKey(name)) {
                throw YzgError.getRuntimeException("032", sqlName, item);
            }
            sqlFieldWhere.put(name, true);
        }
        for (String item : addField.getFields()) {
            String name = item.toLowerCase();
            sqlFieldAdd.put(name, true);
        }

        // 3. 生成SQL语句
        String selectModel = "SELECT {FromField} FROM ( {SQL} )t";
        String initModel = "INSERT INTO {TABLE}( {CreateField} ) " +
                "SELECT {FromInitField} FROM( {SelectSQL} )a " +
                "LEFT JOIN {TABLE} AS b ON a.{Key} = b.{Key} WHERE b.{Key} IS NULL";
        String addModel = "UPDATE {TABLE} AS a INNER JOIN ( {SelectSQL} ) AS b ON a.{Key} = b.{Key} SET {addConst} ";

        // 定义需要处理的SQL对象
        Map<String, StringBuilder> map = new HashMap<>(DaoConst.COLLECTION_INIT_SIZE);
        addString(map, "{SQL}", sql);
        addString(map, "{TABLE}", this.table.getName());

        if (this.table.getKeyType() == String.class) {
            addString(map, "{Key}", this.table.getKeyName());
        } else if (!StringHelper.isEmpty(this.table.getMD5KeyName())) {
            addString(map, "{Key}", this.table.getMD5KeyName());
        } else {
            throw YzgError.getRuntimeException("033");
        }

        String lowerKey = this.table.getKeyName().toLowerCase();
        String lowerMD5Key = this.table.getMD5KeyName().toLowerCase();

        List<TableFieldVo> allField = new ArrayList<TableFieldVo>();
        allField.add(this.table.getKey());
        allField.addAll(this.table.getFields());
        for (TableFieldVo fieldVo : allField) {
            String fieldName = fieldVo.name;
            String fieldLName = fieldVo.lName;
            if (fieldLName.equals(lowerKey) && this.table.getKeyType() != String.class) {
                continue;
            }

            // 处理逗号
            addFlag(map, "{CreateField}", ",");
            addFlag(map, "{FromInitField}", ",");

            addString(map, "{CreateField}", fieldName);

            if (fieldLName.equals(lowerKey) || fieldLName.equals(lowerMD5Key)) {
                addFlag(map, "{FromField}", ",");
                // 处理需要MD5的字段
                addString(map, "{FromField}", "MD5(CONCAT(");
                int i = 0;
                for (String whereField : tableWhereField.getFields()) {
                    if (i > 0) {
                        addString(map, "{FromField}", ",':',");
                    }
                    addString(map, "{FromField}", whereField);
                    i++;
                }
                addString(map, "{FromField}", ")) AS ");
                addString(map, "{FromField}", fieldName);
                addString(map, "{FromInitField}", "a." + fieldName);
            } else {

                // 处理常规字段
                if (sqlFieldAdd.containsKey(fieldLName) && sqlFieldHas.containsKey(fieldLName)) {
                    addFlag(map, "{addConst}", ",");
                    addString(map, "{addConst}", "a." + fieldName + "=a." + fieldName + "+b." + fieldName);
                }

                if (sqlFieldHas.containsKey(fieldLName)) {
                    addFlag(map, "{FromField}", ",");
                    addString(map, "{FromField}", fieldName);
                }

                if (sqlFieldWhere.containsKey(fieldLName)) {
                    addString(map, "{FromInitField}", "a." + fieldName);
                } else if (sqlFieldAdd.containsKey(fieldLName)) {
                    addString(map, "{FromInitField}", "0 AS " + fieldName);
                } else {
                    addString(map, "{FromInitField}", "a." + fieldName);
                    System.err.println("纬度" + this.table.getName() + "->" + sqlName + "->" + fieldName + "存在,但是没有统计");
                }
            }
        }

        // 最终SQL语句处理
        selectModel = replaceString(map, selectModel);
        addString(map, "{SelectSQL}", selectModel);
        sqlInit.setSql(replaceString(map, initModel));
        sqlAdd.setSql(replaceString(map, addModel));

        this.add(sqlInit);
        this.add(sqlAdd);

        List<SqlData> ret = new ArrayList<>();
        ret.add(sqlInit);
        ret.add(sqlAdd);
        return ret;
    }

    /**
     * 生成该字段值是否存在的SQL语句
     *
     * @param sqlName        SQL语句名称
     * @param allowNullField 允许为空字段
     */
    public void addExist(String sqlName, String... allowNullField) {
        this.addExist(sqlName, null, new TableFieldString(allowNullField));
    }

    /**
     * 生成该字段值是否存在的SQL语句
     *
     * @param sqlName        SQL语句名称
     * @param mustField      必须输入字段
     * @param allowNullField 允许为空字段
     */
    public void addExist(String sqlName, TableFieldString mustField, String... allowNullField) {
        this.table.addExist(this, sqlName, mustField, new TableFieldString(allowNullField));
    }

    /**
     * 生成该字段值是否存在的SQL语句
     *
     * @param sqlName        SQL语句名称
     * @param mustField      必须输入字段
     * @param allowNullField 允许为空字段
     */
    public void addExist(String sqlName, TableFieldString mustField, TableFieldString allowNullField) {
        this.table.addExist(this, sqlName, mustField, allowNullField);
    }

    private void addString(Map<String, StringBuilder> map, String name, String value) {
        if (!map.containsKey(name)) {
            map.put(name, new StringBuilder());
        }
        map.get(name).append(value);
    }

    private void addFlag(Map<String, StringBuilder> map, String name, String flag) {
        if (!map.containsKey(name)) {
            addString(map, name, "");
        } else if (map.get(name).length() > 0) {
            addString(map, name, flag);
        }
    }

    private String replaceString(Map<String, StringBuilder> map, String sql) {
        for (Map.Entry<String, StringBuilder> key : map.entrySet()) {
            sql = sql.replace(key.getKey(), key.getValue().toString());
        }
        return sql;
    }

    public TableStruct getTable() {
        return table;
    }

    public void setTable(TableStruct table) {
        this.table = table;
    }

    public MemoryCache<SqlData> getNameCache() {
        return nameCache;
    }

    public void setNameCache(MemoryCache<SqlData> nameCache) {
        this.nameCache = nameCache;
    }

    /**
     * 获取主键名称
     *
     * @return 获取主键名称
     */
    public String getKeyField() {
        if (this.table == null) {
            throw YzgError.getRuntimeException("002");
        }
        return this.table.getKeyName();
    }

    /**
     * 设置主键值
     *
     * @param model 需要设置的实体
     * @param key   需要设置的主键值
     */
    public void setKeyValue(Object model, String key) {
        String keyField = this.getKeyField();
        ObjectHelper.set(model, keyField, key);
    }

    /**
     * 获取主键值
     *
     * @param model 需要获取主键的实体
     * @return
     */
    public String getKeyValue(Object model) {
        String keyField = this.getKeyField();
        Object key = ObjectHelper.get(model, keyField);
        if (StringHelper.isEmpty(key)) {
            return "";
        }
        String keyString = key.toString();
        if (DaoConst.ZERO.equals(keyString)) {
            keyString = "";
        }
        return keyString;
    }

    /**
     * 初始化key
     *
     * @param model
     * @return
     */
    public String initKeyValue(Object model) {
        // 判断主键是字符串和需要生成主键
        boolean isKeyString = this.table.getKeyType() == String.class;
        String keyString = this.getKeyValue(model);
        //  生成主键
        if (StringHelper.isEmpty(keyString) && isKeyString) {
            keyString = StringHelper.getNewID();
            this.setKeyValue(model, keyString);
        }
        return keyString;
    }

    /**
     * 根据输入参数来获取主键
     *
     * @param from 可以为实体或字符串。为实体时必须包含 主键 字段 或者 id 字段。
     * @return
     */
    public String getInputKey(Object from) {
        String key;
        if (from != null && from.getClass() == String.class) {
            key = StringHelper.toString(from);
        } else {
            key = this.getKeyValue(from);
        }
        if (StringHelper.isEmpty(key)) {
            key = StringHelper.toString(ObjectHelper.get(from, "id"));
        }
        if (StringHelper.isEmpty(key)) {
            key = "";
        }
        return key;
    }

    /**
     * 获取请求对象的主键对象
     *
     * @param from
     * @return
     */
    public Object getKeyObject(Object from) {
        String keyValue = this.getInputKey(from);
        return getKeyObject(from, keyValue);
    }

    /**
     * 根据输入参数来获取主键
     *
     * @param keyValue 根据主键获取对象
     * @return
     */
    public Object getKeyObject(Object from, String keyValue) {
        if (!StringHelper.isEmpty(keyValue)) {
            // 去掉其他非主键的属性
            from = new HashMap<String, Object>(DaoConst.COLLECTION_INIT_SIZE);
            this.setKeyValue(from, keyValue);
        }
        return from;
    }

    /**
     * 按照主键对集合进行排序
     *
     * @param collection
     * @return
     */
    public <T extends Object> List<T> getKeySort(Collection<T> collection) {
        // 集合数据
        List<T> list = new ArrayList();
        list.addAll(collection);
        // 对集合进行排序
        Collections.sort(list, new Comparator<T>() {
            @Override
            public int compare(T o1, T o2) {
                String key1 = StringHelper.getFirst(TableSqlCache.this.getKeyValue(o1));
                String key2 = StringHelper.getFirst(TableSqlCache.this.getKeyValue(o2));
                return key1.compareTo(key2);
            }
        });
        return list;
    }

    /**
     * 获取返回值数组
     *
     * @param collection
     * @return
     */
    public List<String> getCollectionRet(Collection collection) {
        List<String> rets = new ArrayList<>();
        for (Object item : collection) {
            rets.add(this.getKeyValue(item));
        }
        return rets;
    }
}