package com.ruoyi.business.service.impl;

import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.parser.Feature;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.business.domain.Database;
import com.ruoyi.business.domain.vo.DatabaseVo;
import com.ruoyi.business.mapper.DatabaseMapper;
import com.ruoyi.business.service.IDatabaseService;
import com.ruoyi.business.utils.AES;
import com.ruoyi.business.utils.MysqlJdbc;
import com.ruoyi.common.core.utils.DateUtils;
import com.ruoyi.common.core.web.domain.AjaxResult;
import com.ruoyi.common.security.utils.SecurityUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.sql.*;
import java.util.*;

import static com.ruoyi.common.core.constant.Constants.DELETE_YES;

/**
 * 外部接入连接信息Service业务层处理
 * 
 * @author ruoyi
 * @date 2024-01-10
 */
@Service
@Slf4j
@RequiredArgsConstructor
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
public class DatabaseServiceImpl extends ServiceImpl<DatabaseMapper, Database> implements IDatabaseService {

    private final DatabaseMapper databaseMapper;

    /**
     * 查询外部接入连接信息列表
     *
     * @param database 外部接入连接信息
     * @return 外部接入连接信息
     */
    @Override
    public List<Database> selectDatabaseList(Database database) {
        LambdaQueryWrapper<Database> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.like(StringUtils.isNotEmpty(database.getDatabaseName()), Database::getDatabaseName, database.getDatabaseName());
        queryWrapper.eq(StringUtils.isNotEmpty(database.getDbType()), Database::getDbType, database.getDbType());
        queryWrapper.eq(StringUtils.isNotEmpty(database.getDbConnType()), Database::getDbConnType, database.getDbConnType());
        queryWrapper.eq(StringUtils.isNotEmpty(database.getDbChartset()), Database::getDbChartset, database.getDbChartset());
        queryWrapper.eq(ObjectUtils.isNotEmpty(database.getBelongSystem()), Database::getBelongSystem, database.getBelongSystem());
        queryWrapper.orderByDesc(Database::getCreateTime);
        return this.baseMapper.selectList(queryWrapper);
    }

    @Override
    public List<Database> selectDatabaseDict(Database database) {
        LambdaQueryWrapper<Database> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ObjectUtils.isNotEmpty(database.getBelongSystem()), Database::getBelongSystem, database.getBelongSystem());
        queryWrapper.select(Database::getId, Database::getDatabaseName);
        queryWrapper.orderByDesc(Database::getCreateTime);
        return this.baseMapper.selectList(queryWrapper);
    }

    /**
     * 查询外部接入连接信息详情
     *
     * @param id 外部接入连接信息主键
     * @return 外部接入连接信息
     */
    @Override
    public Database selectDatabaseById(Long id) {
        Database database = this.baseMapper.selectById(id);
        if (database.getDbConnParam() != null) {
            DatabaseVo dataBaseVo = JSONObject.parseObject(AES.decrypt(database.getDbConnParam()), DatabaseVo.class, Feature.IgnoreNotMatch);
            database.setDbip(dataBaseVo.getDbip());
            database.setDbname(dataBaseVo.getDbname());
            database.setDbport(dataBaseVo.getDbport());
            database.setDbuserid(dataBaseVo.getDbuserid());
            database.setDbpwd("*");
        }
        return database;
    }

    /**
     * 新增外部接入连接信息
     *
     * @param database 外部接入连接信息
     * @return 结果
     */
    @Override
    public int insertDatabase(Database database) {
        DatabaseVo dataBaseVo = new DatabaseVo();
        dataBaseVo.setDbip(database.getDbip());
        dataBaseVo.setDbname(database.getDbname());
        dataBaseVo.setDbport(database.getDbport());
        dataBaseVo.setDbuserid(database.getDbuserid());
        dataBaseVo.setDbpwd(database.getDbpwd());
        // 忽略null
        database.setDbConnParam(AES.encrypt(JSONObject.toJSONString(dataBaseVo)));
        return this.baseMapper.insert(database);
    }

    /**
     * 修改外部接入连接信息
     *
     * @param database 外部接入连接信息
     * @return 结果
     */
    @Override
    public Boolean updateDatabase(Database database) {
        DatabaseVo dataBaseVo = new DatabaseVo();
        dataBaseVo.setDbip(database.getDbip());
        dataBaseVo.setDbname(database.getDbname());
        dataBaseVo.setDbport(database.getDbport());
        dataBaseVo.setDbuserid(database.getDbuserid());
        dataBaseVo.setDbpwd(database.getDbpwd());
        if ("*".equals(database.getDbpwd())) {
            String dbConnParam = this.baseMapper.selectById(database.getId()).getDbConnParam();
            DatabaseVo dataBaseVoDecrypt = JSONObject.parseObject(AES.decrypt(dbConnParam), DatabaseVo.class);
            dataBaseVo.setDbpwd(dataBaseVoDecrypt.getDbpwd());
        }
        database.setDbConnParam(AES.encrypt(JSONObject.toJSONString(dataBaseVo)));
        database.setUpdateTime(DateUtils.getNowDate());
        return this.updateById(database);
    }

    /**
     * 批量删除外部接入连接信息
     *
     * @param ids 需要删除的外部接入连接信息主键
     * @return 结果
     */
    @Override
    public int deleteDatabaseByIds(Long[] ids) {
        List<Long> list = Arrays.asList(ids);
        return updateByIds(list);
    }

    /**
     * 删除外部接入连接信息信息
     *
     * @param id 外部接入连接信息主键
     * @return 结果
     */
    @Override
    public int deleteDatabaseById(Long id) {
        List<Long> list = new ArrayList<>();
        list.add(id);
        return updateByIds(list);
    }

    /**
     * 批量更新
     *
     * @param list ids
     */
    private int updateByIds(List<Long> list) {
        LambdaUpdateWrapper<Database> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.set(Database::getDeleteBy, SecurityUtils.getUsername())
                .set(Database::getDeleteTime, DateUtils.getNowDate())
                .in(Database::getId, list)
                .set(Database::getDeleteFlag, DELETE_YES);
        return this.baseMapper.updateByIds(updateWrapper);
    }

    /**
     * 测试数据源连接
     *
     * @param databaseVo 数据源配置
     */
    public Boolean testConnection(DatabaseVo databaseVo) throws SQLException, ClassNotFoundException {
        Connection conn = getConnection(databaseVo.getId(), databaseVo.getSource());
        if (conn != null) {
            try {
                conn.close();
                return true;
            } catch (Exception e) {
                log.error("关闭连接失败", e);
                return false;
            }
        }
        return false;
    }

    /**
     * 编辑时测试数据源连接
     *
     * @param databaseVo 数据源配置
     * @throws ClassNotFoundException
     * @throws SQLException
     * @throws IllegalAccessException
     * @throws InstantiationException
     */
    public Boolean testConnection_update(DatabaseVo databaseVo)
            throws ClassNotFoundException, SQLException {
        Connection conn = getConnection(databaseVo);
        if (conn != null) {
            try {
                conn.close();
                return true;
            } catch (Exception e) {
                log.error("关闭连接失败", e);
                return false;
            }
        }
        return false;
    }

    /**
     * 通过数据源id获取jdbc连接
     *
     * @param id 数据源配置id
     * @return jdbc连接
     * @throws InstantiationException
     * @throws IllegalAccessException
     */
    @Override
    public Connection getConnection(Long id, String source) throws SQLException, ClassNotFoundException {
        String dbConnParam = "";
        dbConnParam = this.baseMapper.selectById(id).getDbConnParam();
        DatabaseVo databaseVo = JSONObject.parseObject(AES.decrypt(dbConnParam), DatabaseVo.class);
        return getRealConn(databaseVo);
    }


    /**
     * 编辑时获取jdbc连接
     *
     * @param databaseVo 数据源配置
     * @return jdbc连接
     * @throws ClassNotFoundException
     * @throws SQLException
     * @throws InstantiationException
     * @throws IllegalAccessException
     */
    private Connection getConnection(DatabaseVo databaseVo)
            throws ClassNotFoundException, SQLException {
        //编辑时默认提示符*，如果传入密码为*，说明未修改密码，通过id查询密码
        String dbConnParam = "";
        if ("*".equals(databaseVo.getDbpwd())) {
            dbConnParam = this.baseMapper.selectById(databaseVo.getId()).getDbConnParam();
            DatabaseVo dataBaseVoDecrypt = JSONObject.parseObject(AES.decrypt(dbConnParam), DatabaseVo.class);
            databaseVo.setDbpwd(dataBaseVoDecrypt.getDbpwd());
        }
        return getRealConn(databaseVo);
    }
    private Connection getRealConn (DatabaseVo databaseVo) throws ClassNotFoundException, SQLException {
        MysqlJdbc jdbc = new MysqlJdbc();
        Class.forName(jdbc.getClassName());
        java.util.Properties info = new java.util.Properties();
        info.put("user", databaseVo.getDbuserid());
        info.put("password", ObjectUtil.cloneIfPossible(databaseVo.getDbpwd()));
        info.put("ResultSetMetaDataOptions", 1);
        info.put("remarksReporting", "true");
        return DriverManager.getConnection(jdbc.getUrl(databaseVo), info);
    }

    @Override
    public AjaxResult getTables(String dataSourceId) {
        try {
            Database database = getById(dataSourceId);
            DatabaseVo dataBaseVoDecrypt = JSONObject.parseObject(AES.decrypt(database.getDbConnParam()), DatabaseVo.class);
            database.setDbname(dataBaseVoDecrypt.getDbname());
            List<Map<String, String>> tables = getTables(database);
            return AjaxResult.success(tables);
        } catch (Exception e) {
            log.info("读取数据源的所有表失败，数据源id为：" + ObjectUtil.cloneIfPossible(dataSourceId), e);
            return AjaxResult.error("读取数据源的所有表失败");
        }
    }

    /**
     * 获取数据源下的所有表、视图
     *
     * @param database 数据源配置
     * @return 表名集合
     */
    public List<Map<String, String>> getTables(Database database)
            throws ClassNotFoundException, SQLException {
        List<Map<String, String>> list = new ArrayList<>();
        Map<String, String> tableMap;
        Connection conn = getConnection(database.getId(), null);
        //只查询jr开头的和sc_开头的表
        String sqlStr = "select table_name `name`, table_comment as `remark` " +
                "from information_schema.TABLES where TABLE_SCHEMA =? AND (table_name like 'jr_%' OR table_name like 'sc_%' OR table_name like 'cq_%')";
        PreparedStatement st = conn.prepareStatement(sqlStr);
        st.setString(1, database.getDbname());
        ResultSet rs = st.executeQuery();
        List<Map<String, String>> tableColInfo;
        while (rs.next()) {
            tableMap = new HashMap<>();
            tableMap.put("databaseId", String.valueOf(database.getId()));
            tableMap.put("dbname", database.getDbname()); //库名
            tableMap.put("name", rs.getString(1)); //表名
            tableMap.put("remark", rs.getString(2)); //表备注
            list.add(tableMap);
        }
        try {
            rs.close();
        } catch (Exception e) {
            log.error("关闭ResultSet失败", e);
        }
        try {
            st.close();
        } catch (Exception e) {
            log.error("关闭PreparedStatement失败", e);
        }
        try {
            conn.close();
        } catch (Exception e) {
            log.error("关闭连接失败", e);
        }
        return list;
    }

    /**
     * 获取表字段
     *
     * @param dbId 数据源配置id
     * @param tableName 表名
     * @return 字段集合
     */
    public List<Map<String, String>> getTableFields(Long dbId, String tableName) throws SQLException, ClassNotFoundException {
        List<Map<String, String>> list = new ArrayList<>();
        Map<String, String> fieldMap;
        Connection conn = getConnection(dbId, null);
        String sqlStr = "SELECT COLUMN_NAME name, DATA_TYPE type, COLUMN_COMMENT remark FROM information_schema.COLUMNS WHERE TABLE_NAME = ? ORDER BY ORDINAL_POSITION"; // 按照字段顺序排序
        PreparedStatement st = conn.prepareStatement(sqlStr);
        st.setString(1, tableName);
        ResultSet rs = st.executeQuery();
        while (rs.next()) {
            fieldMap = new HashMap<>();
            fieldMap.put("name", rs.getString(1)); //字段名
            fieldMap.put("type", rs.getString(2)); //字段类型
            fieldMap.put("remark", rs.getString(3)); //字段备注
            list.add(fieldMap);
        }
        try {
            rs.close();
        } catch (Exception e) {
            log.error("关闭ResultSet失败", e);
        }
        try {
            st.close();
        } catch (Exception e) {
            log.error("关闭PreparedStatement失败", e);
        }
        try {
            conn.close();
        } catch (Exception e) {
            log.error("关闭连接失败", e);
        }
        return list;
    }
}