package com.qiaoba.common.database.config;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import com.alibaba.druid.pool.DruidDataSource;
import com.qiaoba.common.base.constants.TenantConstant;
import com.qiaoba.common.base.context.BaseContext;
import com.qiaoba.common.base.enums.BaseEnum;
import com.qiaoba.common.database.constants.DynamicDatasourceConstant;
import com.qiaoba.common.database.entity.DynamicDataSource;
import com.qiaoba.common.database.properties.DefaultDataSourceProperties;
import com.qiaoba.common.database.properties.MasterInfo;
import com.qiaoba.common.database.properties.PoolInfo;
import com.qiaoba.common.database.properties.SlaveInfo;
import com.qiaoba.common.database.service.DynamicDatasourceService;
import com.qiaoba.common.database.utils.JdbcUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import java.sql.SQLException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 多数据源配置
 *
 * @author ailanyin
 * @version 1.0
 * @since 2021/10/15 0015 下午 16:43
 */
@Slf4j
@Configuration
public class DynamicDataSourceConfig {

    /**
     * Bean 必须叫 dataSource
     */
    @Resource
    private DynamicDataSourceContext dataSource;
    @Resource
    private DefaultDataSourceProperties defaultDataSourceProperties;
    @Resource
    private DynamicDatasourceService dynamicDatasourceService;

    /**
     * 使用中的数据源
     */
    public static Map<Object, Object> MASTER_SOURCE_MAP = new ConcurrentHashMap<>();

    /**
     * 备用数据源
     */
    public static Map<String, List<SlaveInfo>> SLAVE_SOURCE_MAP = new ConcurrentHashMap<>();

    /**
     * 把DynamicDataSourceContext 纳入容器管理，其他地方使用DynamicDataSourceConfig 类可以直接从容器取对象，并调用freshDataSource方法
     */
    @PostConstruct
    public void init() {
        // 加载系统默认数据源
        initDefault();
        // 加载租户数据源
        initTenant();
    }

    private void initDefault() {
        MasterInfo master = defaultDataSourceProperties.getMaster();
        List<SlaveInfo> slaves = defaultDataSourceProperties.getSlaves();
        // 将主数据源使用
        addMasterMap(DynamicDatasourceConstant.MASTER_PREFIX + TenantConstant.DEFAULT_TENANT_ID, buildDataSource(master.getDriver(), master.getUrl(), master.getUsername(), master.getPassword(), master.getPool()));
        dataSource.freshDataSource(MASTER_SOURCE_MAP);
        // 将从数据源备用
        if (CollUtil.isNotEmpty(slaves)) {
            for (int i = 0; i < slaves.size(); i++) {
                if (!slaves.get(i).getIsUse()) {
                    slaves.remove(i);
                }
            }
            if (CollUtil.isNotEmpty(slaves)) {
                SLAVE_SOURCE_MAP.put(DynamicDatasourceConstant.SLAVE_PREFIX + TenantConstant.DEFAULT_TENANT_ID, slaves);
            }
        }
    }

    private void initTenant() {
        setDefaultSetting();
        List<DynamicDataSource> dynamicDataSourceList = dynamicDatasourceService.loadAllTenantDatasource();
        for (DynamicDataSource dynamicDataSource : dynamicDataSourceList) {
            // 将主数据源加载
            if (BaseEnum.YES.getCode().equals(dynamicDataSource.getIsMaster())) {
                addMasterMap(DynamicDatasourceConstant.MASTER_PREFIX + dynamicDataSource.getTenantId(), buildDataSource(dynamicDataSource.getDriver(), dynamicDataSource.getUrl(), dynamicDataSource.getUsername(), dynamicDataSource.getPassword(), new PoolInfo(dynamicDataSource.getInitCount(), dynamicDataSource.getMinCount(), dynamicDataSource.getMaxCount())));
                dataSource.freshDataSource(MASTER_SOURCE_MAP);
            }
            // 将从数据源备用
            else {
                if (Objects.isNull(SLAVE_SOURCE_MAP.get(DynamicDatasourceConstant.MASTER_PREFIX + dynamicDataSource.getTenantId()))) {
                    List<SlaveInfo> slaves = new ArrayList<>();
                    slaves.add(datasourceToSlave(dynamicDataSource));
                    SLAVE_SOURCE_MAP.put(DynamicDatasourceConstant.MASTER_PREFIX + dynamicDataSource.getTenantId(), slaves);
                } else {
                    List<SlaveInfo> slaves = SLAVE_SOURCE_MAP.get(DynamicDatasourceConstant.MASTER_PREFIX + dynamicDataSource.getTenantId());
                    slaves.add(datasourceToSlave(dynamicDataSource));
                }
            }
        }
        BaseContext.clearAllHolder();
    }

    public void addTenantDataSource(String tenantId, String driver, String url, String username, String password, PoolInfo poolInfo) {
        addMasterMap(DynamicDatasourceConstant.MASTER_PREFIX + tenantId, buildDataSource(driver, url, username, password, poolInfo));
        dataSource.freshDataSource(MASTER_SOURCE_MAP);
    }

    public void deleteTenantDataSource(String tenantId) {
        MASTER_SOURCE_MAP.remove(DynamicDatasourceConstant.MASTER_PREFIX + tenantId);
        dataSource.freshDataSource(MASTER_SOURCE_MAP);
    }

    private static Object buildDataSource(String driver, String url, String username, String password, PoolInfo poolInfo) {
        boolean flag = JdbcUtil.checkConnect(driver, url, username, password);
        if (!flag) {
            log.error("数据源: " + url + " , 连接失败，请检查！");
            return null;
        }
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        dataSource.setDriverClassName(driver);
        dataSource.setInitialSize(poolInfo.getInit());
        dataSource.setMinIdle(poolInfo.getMin());
        dataSource.setMaxActive(poolInfo.getMax());
        try {
            dataSource.init();
            return dataSource;
        } catch (SQLException e) {
            dataSource.close();
            return null;
        }
    }

    /**
     * 程序关闭后,要释放数据源连接池
     */
    @PreDestroy
    public void close() {
        Set<Map.Entry<Object, Object>> entries = MASTER_SOURCE_MAP.entrySet();
        for (Map.Entry<Object, Object> entry : entries) {
            DruidDataSource dataSource = (DruidDataSource) entry.getValue();
            dataSource.close();
        }
    }

    private void addMasterMap(String name, Object dataSource) {
        if (Objects.nonNull(dataSource)) {
            MASTER_SOURCE_MAP.put(name, dataSource);
        }
    }

    private SlaveInfo datasourceToSlave(DynamicDataSource dynamicDataSource) {
        SlaveInfo slaveInfo = BeanUtil.copyProperties(dynamicDataSource, SlaveInfo.class);
        slaveInfo.setPool(new PoolInfo(dynamicDataSource.getInitCount(), dynamicDataSource.getMinCount(), dynamicDataSource.getMaxCount()));
        return slaveInfo;
    }

    public void setDefaultSetting() {
        BaseContext.setDataSource(DynamicDatasourceConstant.MASTER_PREFIX + TenantConstant.DEFAULT_TENANT_ID);
        BaseContext.setTenantId(TenantConstant.DEFAULT_TENANT_ID);
    }
}
