package com.yanzuoguang.db.impl;

import com.yanzuoguang.db.ConfigDb;
import com.yanzuoguang.db.DbExecute;
import com.yanzuoguang.log.AspectLogResult;
import com.yanzuoguang.log.AspectLogStart;
import com.yanzuoguang.log.LogInfoVo;
import com.yanzuoguang.util.vo.MapRow;
import com.yanzuoguang.util.vo.Ref;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowCallbackHandler;
import org.springframework.jdbc.support.rowset.SqlRowSet;
import org.springframework.stereotype.Component;

import java.util.List;

/**
 * 数据库操作类
 *
 * @author 颜佐光
 */
@Component
public class DbExecuteImpl implements DbExecute {

    private final JdbcTemplate jdbcTemplate;
    private final DbExecutePrintSql printSql;
    private final ConfigDb configDb;
    private final AspectLogStart aspectLogStart;
    private final AspectLogResult aspectLogResult;

    public DbExecuteImpl(JdbcTemplate jdbcTemplate,
                         DbExecutePrintSql printSql,
                         ConfigDb configDb,
                         AspectLogStart aspectLogStart,
                         AspectLogResult aspectLogResult) {
        this.jdbcTemplate = jdbcTemplate;
        this.printSql = printSql;
        this.configDb = configDb;
        this.aspectLogStart = aspectLogStart;
        this.aspectLogResult = aspectLogResult;
    }

    public JdbcTemplate getJdbc() {
        return jdbcTemplate;
    }

    /**
     * 更新SQL语句的执行
     *
     * @param targetClass 触发类
     * @param sqlName     SQL语句名称
     * @param sql         SQL语句
     * @param paras       参数信息
     * @return 影响行数
     */
    @Override
    public int update(Class<?> targetClass, String sqlName, String sql, Object... paras) {
        SqlInfo sqlInfo = new SqlInfo(targetClass, sqlName, sql, paras);
        Ref<Integer> ret = new Ref<>(0);
        executeSql(sqlInfo, (row, start) ->
                row.value = ret.value = getJdbc().update(sqlInfo.getSql(), sqlInfo.getParas())
        );
        return ret.value;
    }

    /**
     * 查询数据
     *
     * @param targetClass 触发类
     * @param sqlName     SQL语句名称
     * @param cls         查询的结果的类型
     * @param rowHandle   通过该类来处理结果
     * @param sql         需要查询的SQL语句
     * @param paras       查询语句的参数
     * @param <T>         返回的集合的类型
     */
    @Override
    public <T> void query(Class<?> targetClass, Class<T> cls, DbRow<T> rowHandle, String sqlName, String sql, Object... paras) {
        SqlInfo sqlInfo = new SqlInfo(targetClass, sqlName, sql, paras);
        executeSql(sqlInfo, (row, start) -> {
            RowCallbackHandler rowCallbackHandler = rs -> {
                AllBeanRowMapper<T> rowMap = AllBeanRowMapper.getInstance(cls, configDb);
                T data = rowMap.mapRow(rs, row.value);
                rowHandle.handle(data);
                row.value++;
            };
            this.getJdbc().query(sqlInfo.getSql(), rowCallbackHandler, sqlInfo.getParas());
        });
    }

    /**
     * 查询数据,并返回集合
     *
     * @param targetClass 触发类
     * @param sqlName     SQL语句名称
     * @param cls         查询的结果的类型
     * @param sql         需要查询的SQL语句
     * @param paras       查询语句的参数
     * @param <T>         返回的集合的类型
     * @return 集合
     */
    @Override
    public <T> List<T> query(Class<?> targetClass, Class<T> cls, String sqlName, String sql, Object... paras) {
        SqlInfo sqlInfo = new SqlInfo(targetClass, sqlName, sql, paras);
        Ref<List<T>> ret = new Ref<>(null);
        executeSql(sqlInfo, (row, start) -> {
            ret.value = this.getJdbc().query(sqlInfo.getSql(), sqlInfo.getParas(), AllBeanRowMapper.getInstance(cls, configDb));
            row.value = ret.value.size();
        });
        return ret.value;
    }

    /**
     * 查询数据,并返回集合
     *
     * @param targetClass 触发类
     * @param sqlName     SQL语句名称
     * @param sql         需要查询的SQL语句
     * @param paras       查询语句的参数
     * @return 集合
     */
    @Override
    public List<MapRow> query(Class<?> targetClass, String sqlName, String sql, Object... paras) {
        return query(targetClass, MapRow.class, sqlName, sql, paras);
    }

    /**
     * 查询第一个单元格的信息
     *
     * @param targetClass 触发类
     * @param sqlName     SQL语句名称
     * @param sql         SQL语句
     * @param paras       参数信息
     * @return 第一个单元格的数据
     */
    @Override
    public Object queryCell(Class<?> targetClass, String sqlName, String sql, Object... paras) {
        SqlInfo sqlInfo = new SqlInfo(targetClass, sqlName, sql, paras);
        Ref<Object> ret = new Ref<>(null);
        executeSql(sqlInfo, (row, start) -> {
            SqlRowSet rowSet = getJdbc().queryForRowSet(sqlInfo.getSql(), sqlInfo.getParas());
            if (rowSet.next()) {
                row.value = 1;
                ret.value = rowSet.getObject(1);
            }
        });
        return ret.value;
    }

    /**
     * 执行sql语句
     *
     * @param sqlInfo     sql语句信息
     * @param sqlFunction 执行函数,传入参数为影响的行数引用和开始执行时间
     */
    private void executeSql(SqlInfo sqlInfo, DbSqlFunction<Ref<Integer>, Long> sqlFunction) {
        // 开始记录日志
        LogInfoVo log = aspectLogStart.requestLog(sqlInfo.getTargetClass(), "Sql",
                String.format("%s:%s", sqlInfo.getTargetClass().getSimpleName(), sqlInfo.getSqlName()), sqlInfo, true);
        // 开始时间
        long start = System.currentTimeMillis();
        Ref<Integer> row = new Ref<>(0);
        sqlInfo.setSql(this.handleParas(sqlInfo.getSql()));
        try {
            // 执行得函数
            sqlFunction.accept(row, start);
            // 写入日志
            aspectLogResult.responseLog(log, row, null);
        } catch (Exception ex) {
            // 写入日志
            aspectLogResult.responseLog(log, row, ex);
            throw ex;
        } finally {
            // 打印sql语句
            printSql.print(sqlInfo, log.getUseTime(), row.value);
        }
    }

    /**
     * 处理SQL语句和参数值
     *
     * @param sql 处理sql语句
     * @return 可执行的sql语句
     */
    protected String handleParas(String sql) {
        return sql.replaceAll("1\\s*?=\\s*?1\\s*?(?i)AND", "")
                .replaceAll("(?i)WHERE\\s*?1\\s*?=\\s*?1", "")
                .replaceAll("((?i)ORDER\\s*?(?i)BY\\s*?)1\\s*?,", "$1")
                .replaceAll("(?i)ORDER\\s*?(?i)BY\\s*?1\\s*?", "")
                .replaceAll("((?i)GROUP\\s*?(?i)BY\\s*?)1\\s*?,", "$1")
                .replaceAll("(?i)GROUP\\s*?(?i)BY\\s*?1\\s*?", "");
    }
}