/*
 * Copyright [2020-2030] [https://www.stylefeng.cn]
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Guns采用APACHE LICENSE 2.0开源协议，您在使用过程中，需要注意以下几点：
 *
 * 1.请不要删除和修改根目录下的LICENSE文件。
 * 2.请不要删除和修改Guns源码头部的版权声明。
 * 3.请保留源码和相关描述文件的项目出处，作者声明等。
 * 4.分发源码时候，请注明软件出处 https://gitee.com/stylefeng/guns
 * 5.在修改包名，模块名称，项目代码等时，请注明软件出处 https://gitee.com/stylefeng/guns
 * 6.若您的项目无法满足以上几点，可申请商业授权
 */
package cn.stylefeng.roses.kernel.db.init.actuator;

import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import cn.stylefeng.roses.kernel.db.api.exception.enums.DbInitEnum;
import cn.stylefeng.roses.kernel.db.api.pojo.db.TableFieldInfo;
import cn.stylefeng.roses.kernel.db.api.pojo.db.TableInfo;
import cn.stylefeng.roses.kernel.db.api.pojo.druid.DruidProperties;
import cn.stylefeng.roses.kernel.db.api.util.DatabaseUtil;
import cn.stylefeng.roses.kernel.db.init.util.SqlExe;
import cn.stylefeng.roses.kernel.rule.exception.base.ServiceException;
import com.alibaba.fastjson.JSON;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;


/**
 * 数据库初始化，可初始化表，校验字段，校验表名是否存在等
 *
 * @author fengshuonan
 * @since 2018-07-29 22:05
 */
@Slf4j
@Getter
@Setter
public abstract class DbInitializer {

    /**
     * 如果为true，则数据库校验失败会抛出异常
     */
    private Boolean fieldValidatorExceptionFlag = true;

    public DbInitializer() {

    }

    public DbInitializer(Boolean fieldValidatorExceptionFlag) {
        this.fieldValidatorExceptionFlag = fieldValidatorExceptionFlag;
    }

    @Resource
    @Getter
    private DruidProperties druidProperties;

    /**
     * 初始化数据库
     *
     * @author fengshuonan
     * @since 2018/7/30 上午10:30
     */
    public void dbInit() {

        // 初始化表
        initTable();

        // 校验实体和对应表结构是否有不一致的
        fieldsValidate();
    }

    /**
     * 初始化表结构
     *
     * @author fengshuonan
     * @since 2018/7/30 上午10:24
     */
    private void initTable() {

        // 校验参数
        String tableName = this.getTableName();
        String tableInitSql = this.getTableInitSql();
        if (ObjectUtil.isEmpty(tableName) || ObjectUtil.isEmpty(tableInitSql)) {
            if (fieldValidatorExceptionFlag) {
                throw new ServiceException(DbInitEnum.INIT_TABLE_EMPTY_PARAMS);
            }
        }

        // 列出数据库中所有的表
        List<TableInfo> tableInfos = DatabaseUtil.selectTables(druidProperties);
        boolean haveSmsTableFlag = false;
        for (TableInfo tableInfo : tableInfos) {
            if (tableInfo.getTableName().equalsIgnoreCase(tableName)) {
                haveSmsTableFlag = true;
                break;
            }
        }

        // 判断数据库中是否有这张表，如果没有就初始化
        if (!haveSmsTableFlag) {
            SqlExe.update(tableInitSql);
            log.info("初始化" + getTableName() + "成功！");
        }

    }

    /**
     * 校验实体和对应表结构是否有不一致的
     *
     * @author fengshuonan
     * @since 2018/7/30 上午10:24
     */
    private void fieldsValidate() {

        //检查数据库中的字段，是否和实体字段一致
        List<TableFieldInfo> tableFields = DatabaseUtil.getTableFields(druidProperties, getTableName());
        if (tableFields != null && !tableFields.isEmpty()) {

            //用于保存实体中不存在的字段的名称集合
            List<String> fieldsNotInClass = new ArrayList<>();

            //反射获取字段的所有字段名称
            List<String> classFields = this.getClassFields();
            for (TableFieldInfo tableField : tableFields) {
                String fieldName = tableField.getColumnName();
                if (!classFields.contains(fieldName.toLowerCase())) {
                    fieldsNotInClass.add(fieldName);
                }
            }

            //如果集合不为空，代表有实体和数据库不一致的数据
            if (!fieldsNotInClass.isEmpty()) {
                log.error("实体中和数据库字段不一致的字段如下：" + JSON.toJSONString(fieldsNotInClass));
                if (fieldValidatorExceptionFlag) {
                    throw new ServiceException(DbInitEnum.FIELD_VALIDATE_ERROR);
                }
            }
        }
    }

    /**
     * 反射获取类的所有字段
     *
     * @author fengshuonan
     * @since 2018/7/30 上午10:06
     */
    private List<String> getClassFields() {
        Class<?> entityClass = this.getEntityClass();
        Field[] declaredFields = ClassUtil.getDeclaredFields(entityClass);
        ArrayList<String> filedNamesUnderlineCase = new ArrayList<>();
        for (Field declaredField : declaredFields) {
            String fieldName = StrUtil.toUnderlineCase(declaredField.getName());
            filedNamesUnderlineCase.add(fieldName);
        }

        // 获取父类的所有字段名称
        Field[] superfields = ReflectUtil.getFields(entityClass.getSuperclass());
        for (Field superfield : superfields) {
            String fieldName = StrUtil.toUnderlineCase(superfield.getName());
            filedNamesUnderlineCase.add(fieldName);
        }

        return filedNamesUnderlineCase;
    }

    /**
     * 获取表的初始化语句
     *
     * @author stylefeng
     * @since 2018/7/29 22:10
     */
    protected abstract String getTableInitSql();

    /**
     * 获取表的名称
     *
     * @author stylefeng
     * @since 2018/7/29 22:10
     */
    protected abstract String getTableName();

    /**
     * 获取表对应的实体
     *
     * @author stylefeng
     * @since 2018/7/29 22:49
     */
    protected abstract Class<?> getEntityClass();

}
