package com.qiaoba.common.database.config;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.io.IoUtil;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidPooledConnection;
import com.qiaoba.common.base.constant.TenantConstant;
import com.qiaoba.common.base.context.BaseContext;
import com.qiaoba.common.base.enums.BaseEnum;
import com.qiaoba.common.database.context.BackupDatasourceContext;
import com.qiaoba.common.database.context.DynamicDataSourceContext;
import com.qiaoba.common.database.context.PrimaryDatasourceContext;
import com.qiaoba.common.database.context.TenantDbTypeContext;
import com.qiaoba.common.database.entity.DynamicDataSource;
import com.qiaoba.common.database.monitor.NotOnlineDatasourceMonitor;
import com.qiaoba.common.database.properties.DataSourceProperties;
import com.qiaoba.common.database.service.DynamicDatasourceService;
import com.qiaoba.common.database.util.DatasourceUtil;
import com.qiaoba.common.database.util.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.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

/**
 * 多数据源配置
 *
 * @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 DataSourceProperties dataSourceProperties;
    @Resource
    private DynamicDatasourceService dynamicDatasourceService;

    /**
     * 数据源加载完成
     */
    public static Boolean COMPLETE_LOAD_DATASOURCE = false;

    /**
     * 租户 ids
     */
    public static List<String> TENANT_IDS = ListUtil.toList(TenantConstant.DEFAULT_TENANT_ID);


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

    public static DruidDataSource getPrimaryDataSource(String tenantId) {
        return (DruidDataSource) PrimaryDatasourceContext.get(tenantId);
    }

    private void initDefault() {
        List<DynamicDataSource> dataSources = dataSourceProperties.getDataSources();
        for (int i = 0; i < dataSources.size(); i++) {
            // 0索引作为主数据源
            Object dataSource = DatasourceUtil.buildDataSource(TenantConstant.DEFAULT_TENANT_ID, dataSources.get(i));
            if (Objects.isNull(dataSource)) {
                // 默认的主数据源挂了
                // 加入到错误数据源Map 等待重试
                NotOnlineDatasourceMonitor.addErrorDatasource(TenantConstant.DEFAULT_TENANT_ID, dataSources.get(i));
                dataSources.remove(i);
            } else {
                addPrimaryMap(TenantConstant.DEFAULT_TENANT_ID, dataSource);
                dataSources.remove(i);
                break;
            }
        }

        if (CollUtil.isEmpty(PrimaryDatasourceContext.getAll())) {
            log.error("主系统配置数据源全部无效, 请检查 yml 中相关配置");
        }
        // 其他数据源备用
        addBackupMap(TenantConstant.DEFAULT_TENANT_ID, dataSources);
        // 刷新数据源
        this.dataSource.freshDataSource(PrimaryDatasourceContext.getAll());
    }

    private void initTenant() {
        setDefaultSetting();
        Map<String, List<DynamicDataSource>> tenantDatasource = dynamicDatasourceService.loadAllTenantDatasource();
        for (String tenantId : tenantDatasource.keySet()) {
            List<DynamicDataSource> dataSources = tenantDatasource.get(tenantId);
            for (int i = 0; i < dataSources.size(); i++) {
                DynamicDataSource dynamicDataSource = dataSources.get(i);
                if (BaseEnum.YES.getCode().equals(dynamicDataSource.getIsPrimary())) {
                    Object dataSource = DatasourceUtil.buildDataSource(dataSources.get(i).getTenantId(), dataSources.get(i));
                    if (Objects.isNull(dataSource)) {
                        // 默认的主数据源挂了
                        // 加入到错误数据源Map 等待重试
                        NotOnlineDatasourceMonitor.addErrorDatasource(tenantId, dataSources.get(i));
                        // 在数据源集合中删除, 防止将错误的数据源加载到备用数据源中
                        dataSources.remove(i);
                    } else {
                        addPrimaryMap(tenantId, dataSource);
                        // 去除主要数据源，剩下皆为备用数据源
                        dataSources.remove(i);
                        break;
                    }
                }
            }
            // 备用数据源
            addBackupMap(tenantId, dataSources);
            TENANT_IDS.add(tenantId);
        }
        BaseContext.clearAllHolder();
        // 刷新数据源
        dataSource.freshDataSource(PrimaryDatasourceContext.getAll());

    }

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

    private void addPrimaryMap(String tenantId, Object dataSource) {
        if (Objects.nonNull(dataSource)) {
            try {
                PrimaryDatasourceContext.set(tenantId, dataSource);
                // 将数据源的类型保存
                DruidPooledConnection connection = ((DruidDataSource) dataSource).getConnection();
                TenantDbTypeContext.set(tenantId, connection.getMetaData().getDatabaseProductName());
                // 归还 connection
                IoUtil.close(connection);
            } catch (SQLException e) {
                e.printStackTrace();
            }

        }
    }

    private void addBackupMap(String tenantId, List<DynamicDataSource> dataSources) {
        if (CollUtil.isNotEmpty(dataSources)) {
            BackupDatasourceContext.set(tenantId, dataSources);
        }
    }

    public void setDefaultSetting() {
        BaseContext.setDataSource(TenantConstant.DEFAULT_TENANT_ID);
        BaseContext.setTenantId(TenantConstant.DEFAULT_TENANT_ID);
        BaseContext.setDatabaseType(TenantDbTypeContext.getDefault());
    }
}
