package com.dayou.dao;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.enums.SqlMethod;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.baomidou.mybatisplus.core.toolkit.*;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.baomidou.mybatisplus.extension.conditions.query.QueryChainWrapper;
import com.baomidou.mybatisplus.extension.conditions.update.LambdaUpdateChainWrapper;
import com.baomidou.mybatisplus.extension.conditions.update.UpdateChainWrapper;
import com.baomidou.mybatisplus.extension.toolkit.ChainWrappers;
import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;
import com.dayou.exception.ErrorCode;
import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.reflection.ExceptionUtil;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.MyBatisExceptionTranslator;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Collection;
import java.util.Objects;
import java.util.function.Function;

/**
 * @author wucl
 * @version 1.1
 * @date 2020/9/14 12:48
 */
public interface CustomBaseMapper<T> extends BaseMapper<T> {


    /**
     * 暂时不要在事务里使用
     * 根据某一个属性查询id 值
     *
     * @param propretyValue
     * @param nameCloumn    传入驼峰名自动转下划线格式
     * @return
     * @see CustomBaseMapper#selectOnePropertyByAnotherProperty
     */
    default Long selectPKByOneProperty(Object propretyValue, String nameCloumn) {
        return selectOnePropertyByAnotherProperty(propretyValue, SqlHelper.table(currentModelClass()).getKeyColumn(), Long.class, nameCloumn);
    }


    /**
     * 暂时不要在事务里使用
     * 根据id属性查询某一个值
     *
     * @param propretyValue
     * @param nameCloumn    传入驼峰名自动转下划线格式
     * @return
     * @see CustomBaseMapper#selectOnePropertyByAnotherProperty
     */
    default String selectNameByPK(Object propretyValue, String nameCloumn) {
        if (StrUtil.isBlank(nameCloumn)) {
            nameCloumn = "name";
        }
        return selectOnePropertyByAnotherProperty(propretyValue, nameCloumn, String.class, SqlHelper.table(currentModelClass()).getKeyColumn());
    }


    /**
     * 暂时不要在事务里使用
     * 根据一个属性 查询另外一个属性
     * 适用于 根据id 查询 name 或根据name 查询id 等类似情况
     * 执行sql形式：   select   selectnameCloumn  from tableName  where  wherecolumn=? and deleted=0
     * 如果有多条返回 那么只取第一条的属性
     * 没有记录就返回NULL
     *
     * @param value            where后的值
     * @param selectnameCloumn 要查询的属性     传入驼峰名自动转下划线格式
     * @param s                要查询的属性的值的类型
     * @param wherecolumn      根据哪个属性查询  传入驼峰名自动转下划线格式
     * @param <S>
     * @return
     */
    @SuppressWarnings("unchecked")
    default <S> S selectOnePropertyByAnotherProperty(Object value, String selectnameCloumn, Class<S> s, String wherecolumn) {
        assert value != null;
        assert selectnameCloumn != null;
        assert wherecolumn != null;
        assert s != null;

        S res = null;
        selectnameCloumn = StrUtil.toUnderlineCase(selectnameCloumn);
        wherecolumn = StrUtil.toUnderlineCase(wherecolumn);
        String tableName = SqlHelper.table(currentModelClass()).getTableName();
        String logicDeleteSql = SqlHelper.table(currentModelClass()).getLogicDeleteSql(true, true);
        String sql = getSql("select %s from  %s where %s=? ", selectnameCloumn, tableName, wherecolumn);
        if (StringUtils.isNotBlank(logicDeleteSql)) {
            //追加逻辑删除字段
            sql += (" " + logicDeleteSql);
        }
        // 借助mybatis 预留的原始jdbc口子 执行原生简单查询
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        SqlSessionFactory sqlSessionFactory = SqlHelper.sqlSessionFactory(currentModelClass());
        SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.SIMPLE);
        try {
            connection = sqlSession.getConnection();
            preparedStatement = connection.prepareStatement(sql);
            preparedStatement.setObject(1, value);
            resultSet = preparedStatement.executeQuery();
            // 只取一行
            if (resultSet.next()) {
                res = resultSet.getObject(selectnameCloumn, s);
            }
        } catch (Exception e) {
            e.printStackTrace();
            ErrorCode.throwBusinessException(ErrorCode.DEFAULTERROR, "查询DB出现未知错误..");
        } finally {
            closeResources(resultSet, preparedStatement, connection, sqlSession);
        }
        return res;
    }

    /**
     * lamda 链式query
     *
     * @return
     */
    default LambdaQueryChainWrapper<T> lambdaQuery() {
        return ChainWrappers.lambdaQueryChain(this);
    }

    /**
     * wrapper query
     *
     * @return
     */
    default QueryChainWrapper<T> query() {
        return ChainWrappers.queryChain(this);
    }

    /**
     * lamda 链式 更新query
     *
     * @return
     */
    default LambdaUpdateChainWrapper<T> lambdaUpdate() {
        return ChainWrappers.lambdaUpdateChain(this);
    }

    /**
     * 更新
     *
     * @return
     */
    default UpdateChainWrapper<T> update() {
        return ChainWrappers.updateChain(this);
    }

    /**
     * 伪批量插入
     *
     * @param entityList
     * @return
     */
    default int insertBatch(Collection<T> entityList) {
        return insertBatch(entityList, 1000);
    }


    /**
     * 关闭资源
     *
     * @param autoCloseable
     */
    default void closeResources(AutoCloseable... autoCloseable) {
        if (autoCloseable == null || autoCloseable.length <= 0) {
            return;
        }
        for (int i = 0; i < autoCloseable.length; i++) {
            AutoCloseable closeable = autoCloseable[i];
            if (closeable != null) {
                try {
                    closeable.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 伪批量
     *
     * @param entityList
     * @param batchSize
     * @return
     */
    default int insertBatch(Collection<T> entityList, int batchSize) {
        String sqlStatement = sqlStatement(SqlMethod.INSERT_ONE);
        int size = entityList.size();
        return executeBatch(sqlSession -> {
            int insertCount = 0;
            //单条依次插入
            for (T entity : entityList) {
                sqlSession.insert(sqlStatement, entity);
                insertCount++;
                if ((insertCount % batchSize == 0) || insertCount == size) {
                    sqlSession.flushStatements();
                }
            }
            return insertCount;
        });
    }

    /**
     * 插入或更新 根据有无id
     *
     * @param entity
     * @return
     */
    default int insertOrUpdate(T entity) {
        if (null != entity) {
            Class<?> cls = entity.getClass();
            TableInfo tableInfo = TableInfoHelper.getTableInfo(cls);
            Assert.notNull(tableInfo, "error: can not execute. because can not find cache of TableInfo for entity!");
            String keyProperty = tableInfo.getKeyProperty();
            Assert.notEmpty(keyProperty, "error: can not execute. because can not find column for id from entity!");
            Object idVal = ReflectionKit.getMethodValue(cls, entity, tableInfo.getKeyProperty());
            return StringUtils.checkValNull(idVal) || Objects.isNull(selectById((Serializable) idVal)) ?
                    insert(entity) : updateById(entity);
        }
        return 0;
    }

    /**
     * 同上 伪批量
     *
     * @param entityList
     * @param batchSize
     * @return
     */
    default int insertOrUpdateBatch(Collection<T> entityList, int batchSize) {
        Assert.notEmpty(entityList, "error: entityList must not be empty");
        Class<?> cls = currentModelClass();
        TableInfo tableInfo = TableInfoHelper.getTableInfo(cls);
        Assert.notNull(tableInfo, "error: can not execute. because can not find cache of TableInfo for entity!");
        String keyProperty = tableInfo.getKeyProperty();
        Assert.notEmpty(keyProperty, "error: can not execute. because can not find column for id from entity!");
        int size = entityList.size();
        return executeBatch(sqlSession -> {
            int insertOrUpdateCount = 0;
            for (T entity : entityList) {
                Object idVal = ReflectionKit.getMethodValue(cls, entity, keyProperty);
                if (StringUtils.checkValNull(idVal) || Objects.isNull(this.selectById((Serializable) idVal))) {
                    int insert = sqlSession.insert(sqlStatement(SqlMethod.INSERT_ONE), entity);
                    insertOrUpdateCount++;
                } else {
                    MapperMethod.ParamMap<T> param = new MapperMethod.ParamMap<>();
                    param.put(Constants.ENTITY, entity);
                    int update = sqlSession.update(sqlStatement(SqlMethod.UPDATE_BY_ID), param);
                    insertOrUpdateCount++;
                }
                if ((insertOrUpdateCount % batchSize == 0) || insertOrUpdateCount == size) {
                    sqlSession.flushStatements();
                }
            }
            return insertOrUpdateCount;
        });
    }

    /**
     * 根据id 更新
     *
     * @param entityList
     * @param batchSize
     * @return
     */
    default int updateBatchById(Collection<T> entityList, int batchSize) {
        Assert.notEmpty(entityList, "error: entityList must not be empty");
        String sqlStatement = sqlStatement(SqlMethod.UPDATE_BY_ID);
        int size = entityList.size();
        return executeBatch(sqlSession -> {
            int updateCount = 0;
            for (T anEntityList : entityList) {
                MapperMethod.ParamMap<T> param = new MapperMethod.ParamMap<>();
                param.put(Constants.ENTITY, anEntityList);
                int update = sqlSession.update(sqlStatement, param);
                updateCount++;
                if ((updateCount % batchSize == 0) || updateCount == size) {
                    sqlSession.flushStatements();
                }
            }
            return updateCount;
        });
    }

    /**
     * 私有方法
     *
     * @param sqlMethod
     * @return
     */
    default String sqlStatement(SqlMethod sqlMethod) {
        return SqlHelper.table(currentModelClass()).getSqlStatement(sqlMethod.getMethod());
    }

    /**
     * 建议重写该方法，返回T——数据库表实体类的Class类型
     *
     * @return T.class
     */
    default Class<T> currentModelClass() {
        try {
            String baseMapperClassTypeName = this.getClass().getGenericInterfaces()[0].getTypeName();
            Class<? extends BaseMapper> baseMapperClass = (Class<? extends BaseMapper>) Class.forName(baseMapperClassTypeName);
            ParameterizedType parameterizedType = (ParameterizedType) baseMapperClass.getGenericInterfaces()[0];
            String entityClassName = parameterizedType.getActualTypeArguments()[0].getTypeName();
            return (Class<T>) Class.forName(entityClassName);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException("请重写该方法，返回T的Class类型", e);
        }
    }

    /**
     * 私有方法
     *
     * @param fun
     * @param <R>
     * @return
     */
    default <R> R executeBatch(Function<SqlSession, R> fun) {
        Class<T> tClass = currentModelClass();
        SqlHelper.clearCache(tClass);
        SqlSessionFactory sqlSessionFactory = SqlHelper.sqlSessionFactory(tClass);
        SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
        try {
            R apply = fun.apply(sqlSession);
            sqlSession.commit();
            return apply;
        } catch (Throwable t) {
            sqlSession.rollback();
            Throwable unwrapped = ExceptionUtil.unwrapThrowable(t);
            if (unwrapped instanceof RuntimeException) {
                MyBatisExceptionTranslator myBatisExceptionTranslator
                        = new MyBatisExceptionTranslator(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), true);
                throw Objects.requireNonNull(myBatisExceptionTranslator.translateExceptionIfPossible((RuntimeException) unwrapped));
            }
            throw ExceptionUtils.mpe(unwrapped);
        } finally {
            sqlSession.close();
        }
    }

    /**
     * 私有方法
     *
     * @param sqlTemple
     * @param params
     * @return
     */
    default String getSql(String sqlTemple, Object... params) {
        return String.format(sqlTemple, params);
    }

}
