package com.xjrsoft.common.mybatis;

import cn.hutool.db.DbRuntimeException;
import cn.hutool.db.Entity;
import cn.hutool.db.meta.Column;
import cn.hutool.db.meta.Table;
import cn.hutool.db.meta.TableType;
import com.baomidou.mybatisplus.core.assist.ISqlRunner;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.GlobalConfigUtils;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.ibatis.jdbc.SQL;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionUtils;
import org.springframework.transaction.annotation.Transactional;

import java.io.Closeable;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;

public class SqlRunnerAdapter implements ISqlRunner, Closeable {

    private final Log log = LogFactory.getLog(SqlRunnerAdapter.class);

    public static final SqlRunnerAdapter DEFAULT = new SqlRunnerAdapter();
    // 默认FACTORY
    private SqlSessionFactory sqlSessionFactory;

    private Class<?> clazz;

    public SqlRunnerAdapter() {
        this.sqlSessionFactory = SqlHelper.FACTORY;
    }

    public SqlRunnerAdapter(Class<?> clazz) {
        this.clazz = clazz;
    }

    /**
     * 获取默认的SqlQuery(适用于单库)
     *
     * @return ignore
     */
    public static SqlRunnerAdapter db() {
        // 初始化的静态变量 还是有前后加载的问题 该判断只会执行一次
        if (DEFAULT.sqlSessionFactory == null) {
            DEFAULT.sqlSessionFactory = SqlHelper.FACTORY;
        }
        return DEFAULT;
    }

    /**
     * 根据当前class对象获取SqlQuery(适用于多库)
     *
     * @param clazz ignore
     * @return ignore
     */
    public static SqlRunnerAdapter db(Class<?> clazz) {
        return new SqlRunnerAdapter(clazz);
    }

    @Transactional
    @Override
    public boolean insert(String sql, Object... args) {
        SqlSession sqlSession = sqlSession();
        try {
            return SqlHelper.retBool(sqlSession.insert(INSERT, sqlMap(sql, args)));
        } finally {
            closeSqlSession(sqlSession);
        }
    }

    @Transactional
    @Override
    public boolean delete(String sql, Object... args) {
        SqlSession sqlSession = sqlSession();
        try {
            return SqlHelper.retBool(sqlSession.delete(DELETE, sqlMap(sql, args)));
        } finally {
            closeSqlSession(sqlSession);
        }
    }

    /**
     * 获取sqlMap参数
     *
     * @param sql  指定参数的格式: {0}, {1}
     * @param args 仅支持String
     * @return ignore
     */
    private Map<String, String> sqlMap(String sql, Object... args) {
        Map<String, String> sqlMap = CollectionUtils.newHashMapWithExpectedSize(1);
        sqlMap.put(SQL, StringUtils.sqlArgsFill(sql, args));
        return sqlMap;
    }

    /**
     * 获取sqlMap参数
     *
     * @param sql  指定参数的格式: {0}, {1}
     * @param page 分页模型
     * @param args 仅支持String
     * @return ignore
     */
    private Map<String, Object> sqlMap(String sql, IPage<?> page, Object... args) {
        Map<String, Object> sqlMap = CollectionUtils.newHashMapWithExpectedSize(2);
        sqlMap.put(PAGE, page);
        sqlMap.put(SQL, StringUtils.sqlArgsFill(sql, args));
        return sqlMap;
    }

    @Transactional
    @Override
    public boolean update(String sql, Object... args) {
        SqlSession sqlSession = sqlSession();
        try {
            return SqlHelper.retBool(sqlSession.update(UPDATE, sqlMap(sql, args)));
        } finally {
            closeSqlSession(sqlSession);
        }
    }

    /**
     * 根据sql查询Map结果集
     * <p>SqlRunner.db().selectList("select * from tbl_user where name={0}", "Caratacus")</p>
     *
     * @param sql  sql语句，可添加参数，格式：{0},{1}
     * @param args 只接受String格式
     * @return ignore
     */
    @Override
    public List<Map<String, Object>> selectList(String sql, Object... args) {
        SqlSession sqlSession = sqlSession();
        try {
            return sqlSession.selectList(SELECT_LIST, sqlMap(sql, args));
        } finally {
            closeSqlSession(sqlSession);
        }
    }

    /**
     * 根据sql查询一个字段值的结果集
     * <p>注意：该方法只会返回一个字段的值， 如果需要多字段，请参考{@code selectList()}</p>
     *
     * @param sql  sql语句，可添加参数，格式：{0},{1}
     * @param args 只接受String格式
     * @return ignore
     */
    @Override
    public List<Object> selectObjs(String sql, Object... args) {
        SqlSession sqlSession = sqlSession();
        try {
            return sqlSession.selectList(SELECT_OBJS, sqlMap(sql, args));
        } finally {
            closeSqlSession(sqlSession);
        }
    }

    /**
     * 根据sql查询一个字段值的一条结果
     * <p>注意：该方法只会返回一个字段的值， 如果需要多字段，请参考{@code selectOne()}</p>
     *
     * @param sql  sql语句，可添加参数，格式：{0},{1}
     * @param args 只接受String格式
     * @return ignore
     */
    @Override
    public Object selectObj(String sql, Object... args) {
        return SqlHelper.getObject(log, selectObjs(sql, args));
    }

    @Override
    public long selectCount(String sql, Object... args) {
        SqlSession sqlSession = sqlSession();
        try {
            return SqlHelper.retCount(sqlSession.<Long>selectOne(COUNT, sqlMap(sql, args)));
        } finally {
            closeSqlSession(sqlSession);
        }
    }

    @Override
    public Map<String, Object> selectOne(String sql, Object... args) {
        return SqlHelper.getObject(log, selectList(sql, args));
    }

    @Override
    public <E extends IPage<Map<String, Object>>> E selectPage(E page, String sql, Object... args) {
        if (null == page) {
            return null;
        }
        SqlSession sqlSession = sqlSession();
        try {
            page.setRecords(sqlSession.selectList(SELECT_LIST, sqlMap(sql, page, args)));
        } finally {
            closeSqlSession(sqlSession);
        }
        return page;
    }

    /**
     * 获取Session 默认自动提交
     */
    private SqlSession sqlSession() {
        return SqlSessionUtils.getSqlSession(getSqlSessionFactory());
    }

    /**
     * 释放sqlSession
     *
     * @param sqlSession session
     */
    private void closeSqlSession(SqlSession sqlSession) {
        SqlSessionUtils.closeSqlSession(sqlSession, getSqlSessionFactory());
    }

    /**
     * 获取SqlSessionFactory
     */
    private SqlSessionFactory getSqlSessionFactory() {
        return Optional.ofNullable(clazz).map(GlobalConfigUtils::currentSessionFactory).orElse(sqlSessionFactory);
    }

    @Override
    public void close() {
        DEFAULT.sqlSessionFactory = null;
    }

    @Transactional
    public Boolean dynamicInsert(String tableName, Entity params) {
        ImmutablePair<String, List<String>> sql = dynamicInsertBuild(tableName, params);
        //执行sql
         return this.insert(sql.left, sql.right.toArray());
    }

    @Transactional
    public Boolean dynamicInsertBatch(String tableName, List<Entity> params) {
        SqlSession sqlSession = sqlSession();
        try {
            for (Entity param : params) {
                ImmutablePair<String, List<String>> sql = dynamicInsertBuild(tableName, param);
                boolean result = SqlHelper.retBool(sqlSession.insert(INSERT, sqlMap(sql.left, sql.right.toArray())));
                if (!result) return false;
            }
            return true;
        } finally {
            closeSqlSession(sqlSession);
        }
    }

    private ImmutablePair<String, List<String>> dynamicInsertBuild(String tableName, Entity params) {
        SQL sql = new SQL().INSERT_INTO(tableName);
        List<String> args = new ArrayList<>();
        int i = 0;

        // 循环字段列表并赋值
        for (String key : params.keySet()) {
            Object value = params.get(key);
            if (value == null) {
                continue;
            }
            String strValue = value.toString();
            sql.INTO_COLUMNS(String.format("`%s`", key));
            sql.INTO_VALUES(String.format("{%d}", i));
            args.add(strValue);
            i++;
        }

        return new ImmutablePair<>(sql.toString(), args);
    }

    @Transactional
    public Boolean dynamicUpdate(String tableName, Entity params, Entity where) {
        SQL sql = new SQL().UPDATE(tableName);
        List<String> args = new ArrayList<>();
        int i = 0;
        // 循环字段列表并赋值
        for (String key : params.keySet()) {
            Object value = params.get(key);
            if (value == null) {
                continue;
            }
            String strValue = value.toString();
            sql.SET(String.format("%s={%d}", key, i));
            args.add(strValue);
            i++;
        }

        // 循环where条件
        for (String key : where.keySet()) {
            Object value = where.get(key);
            if (value == null) {
                continue;
            }
            // 判断参数是否是数组
            if (value instanceof ArrayList) {
                List<String> vp = new ArrayList<>();
                for (Object v : ((ArrayList<?>) value)) {
                    vp.add(String.format("{%d}",i));
                    args.add(v.toString());
                    i++;
                }
                sql.WHERE(String.format("%s in (%s)", key, String.join(",",vp)));
            } else {
                String strValue = value.toString();
                sql.WHERE(String.format("%s={%d}", key, i));
                args.add(strValue);
                i++;
            }

        }

        //执行sql
        return this.update(sql.toString(), args.toArray());
    }

    @Transactional
    public Boolean dynamicDelete(String tableName, Entity where) {
        SQL sql = new SQL().DELETE_FROM(tableName);
        List<String> args = new ArrayList<>();
        int i = 0;

        // 循环where条件
        for (String key : where.keySet()) {
            Object value = where.get(key);
            if (value == null) {
                continue;
            }
            // 判断参数是否是数组
            if (value instanceof ArrayList) {
                List<String> vp = new ArrayList<>();
                for (Object v : ((ArrayList<?>) value)) {
                    vp.add(String.format("{%d}",i));
                    args.add(v.toString());
                    i++;
                }
                sql.WHERE(String.format("%s in (%s)", key, String.join(",",vp)));
            } else {
                String strValue = value.toString();
                sql.WHERE(String.format("%s={%d}", key, i));
                args.add(strValue);
                i++;
            }
        }

        //执行sql
        return this.delete(sql.toString(), args.toArray());
    }

    /**
     * 查询单条数据
     *
     * @param tableName
     * @param where
     * @return
     */
    public Map<String, Object> dynamicSelectOne(String tableName, Entity where) {
        SQL sql = new SQL().FROM(tableName);
        List<String> args = new ArrayList<>();
        int i = 0;
        // 查询列
        for (String field : where.getFieldNames()) {
            sql.SELECT(field);
        }

        // 循环where条件
        for (String key : where.keySet()) {
            Object value = where.get(key);
            if (value == null) {
                continue;
            }
            String strValue = value.toString();
            sql.WHERE(String.format("%s={%d}", key, i));
            args.add(strValue);
            i++;
        }
        return this.selectOne(sql.toString(), args.toArray());
    }

    /**
     * 按条件查询数据
     *
     * @param tableName
     * @param where
     * @return
     */
    public List<Map<String, Object>> dynamicSelect(String tableName, Entity where) {
        SQL sql = new SQL().FROM(tableName);
        List<String> args = new ArrayList<>();
        int i = 0;
        // 查询列
        for (String field : where.getFieldNames()) {
            sql.SELECT(field);
        }

        // 循环where条件
        for (String key : where.keySet()) {
            Object value = where.get(key);
            if (value == null) {
                continue;
            }
            String strValue = value.toString();
            sql.WHERE(String.format("%s={%d}", key, i));
            args.add(strValue);
            i++;
        }
        return this.selectList(sql.toString(), args.toArray());
    }

    public <E extends IPage<Map<String, Object>>> E dynamicSelectPage(E page, String tableName, Entity where) {
        SQL sql = new SQL().FROM(tableName);
        List<String> args = new ArrayList<>();
        int i = 0;
        // 查询列
        for (String field : where.getFieldNames()) {
            sql.SELECT(field);
        }

        // 循环where条件
        for (String key : where.keySet()) {
            Object value = where.get(key);
            if (value == null) {
                continue;
            }
            String strValue = value.toString();
            sql.WHERE(String.format("%s={%d}", key, i));
            args.add(strValue);
            i++;
        }
        return this.selectPage(page, sql.toString(), args.toArray());
    }

    /**
     * 获得表的元信息<br>
     * 注意如果需要获取注释，某些数据库如MySQL，需要在配置中添加:
     * <pre>
     *     remarks = true
     *     useInformationSchema = true
     * </pre>
     *
     * @param tableName 表名
     * @return Table对象
     */
    public Table getTableMeta(String tableName) {
        final Table table = Table.create(tableName);
        // 获取 SqlSession 对象
        SqlSession sqlSession = sqlSessionFactory.openSession();
        Connection conn = null;
        try {
            conn = sqlSession.getConnection();

            // catalog和schema获取失败默认使用null代替
            final String catalog = conn.getCatalog();
            table.setCatalog(catalog);
            final String schema = conn.getSchema();
            table.setSchema(schema);

            final DatabaseMetaData metaData = conn.getMetaData();

            // 获得表元数据（表注释）
            try (ResultSet rs = metaData.getTables(catalog, schema, tableName, new String[]{TableType.TABLE.value()})) {
                if (null != rs) {
                    if (rs.next()) {
                        table.setComment(rs.getString("REMARKS"));
                    }
                }
            }

            // 获得主键
            try (ResultSet rs = metaData.getPrimaryKeys(catalog, schema, tableName)) {
                if (null != rs) {
                    while (rs.next()) {
                        table.addPk(rs.getString("COLUMN_NAME"));
                    }
                }
            }

            // 获得列
            try (ResultSet rs = metaData.getColumns(catalog, schema, tableName, null)) {
                if (null != rs) {
                    while (rs.next()) {
                        table.setColumn(Column.create(table, rs));
                    }
                }
            }

        } catch (SQLException e) {
            throw new DbRuntimeException("Get columns error!", e);
        } finally {
            closeSqlSession(sqlSession);
        }
        return table;
    }
}
