package com.sqx.config;

import com.sqx.modules.utils.InvitationCodeUtil;
import com.sqx.sharding.MasterSlaveRules;
import com.sqx.sharding.ShardingDataBase;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.shardingsphere.api.config.masterslave.MasterSlaveRuleConfiguration;
import org.apache.shardingsphere.api.config.sharding.KeyGeneratorConfiguration;
import org.apache.shardingsphere.api.config.sharding.ShardingRuleConfiguration;
import org.apache.shardingsphere.api.config.sharding.TableRuleConfiguration;
import org.apache.shardingsphere.api.config.sharding.strategy.InlineShardingStrategyConfiguration;
import org.apache.shardingsphere.shardingjdbc.api.ShardingDataSourceFactory;
import org.codehaus.groovy.util.StringUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.*;


/**
 * sharding-jdbc 配置文件
 */
@Slf4j
@Data
@Configuration
@ConfigurationProperties(prefix = "spring.shardingsphere")
public class ShardingConfig {

    @Value("${spring.profiles.active}")
    private String activeProfile;

    /**
     * 读取数据源信息
     */
    private Map<String, ShardingDataBase> datasource;
    /**
     * 读写分离的配置
     */
    private Map<String, MasterSlaveRules> masterSlaveRules;
    /**
     * 显示sharding-jdbc的sql
     */
    private String showSql;

    /**
     * 广播表
     */
    private Set<String> broadcastTables;

    /**
     * 中心库的节点
     */
    private String centerTablesDataNode;

    /**
     * 中心表,不进行分库操作
     */
    private Set<String> centerTables;

    /**
     * 区域库的节点
     */
    private String regionTablesDataNode;

    /**
     * 区域表分库策略的字段
     */
    private String regionTablesShardingDatabaseColumn;

    /**
     * 区域表分库的算法
     */
    private String regionTablesShardingDatabaseAlgorithm;

    /**
     * 分库表,通过userId进行分库
     */
    private Set<String> regionTables;


    /**
     * 区域表分库策略的字段
     */
    private String courseDetailsShardingDatabaseColumn;

    /**
     * 区域表分库的算法
     */
    private String courseDetailsShardingDatabaseAlgorithm;

    /**
     * 分库表,通过userId进行分库
     */
    private Set<String> courseDetails;

    /**
     * 配置sharding-jdbc数据源
     */
    @Bean
    public DataSource dataSource() throws SQLException {
        ShardingRuleConfiguration shardingRuleConfig = new ShardingRuleConfiguration();

        // 配置数据库主从
        shardingRuleConfig.setMasterSlaveRuleConfigs(masterSlaveRuleConfigs());

        // 配置广播表
        shardingRuleConfig.setBroadcastTables(broadcastTables);

        // 配置表的切分策略
        shardingRuleConfig.setTableRuleConfigs(addTableRuleConfigs());

        // 配置表绑定规则
        List<Set<String>> sets = new ArrayList<>();
        sets.add(regionTables);
        shardingRuleConfig.setBindingTableGroups(bindingTableGroups(sets));


        // 配置是否显示sql
        Properties props = new Properties();
        props.put("sql.show", showSql);

        // 配置数据源
        Map<String, DataSource> dataSourceMap = getShardingDataBase();
        return ShardingDataSourceFactory.createDataSource(dataSourceMap, shardingRuleConfig, props);
    }

    /**
     * 配置
     */
    private Set<TableRuleConfiguration> addTableRuleConfigs() {
        Set<TableRuleConfiguration> sets = new HashSet<>();
        for (String centerTable : centerTables) {
            TableRuleConfiguration tableRuleConfig = new TableRuleConfiguration(centerTable,
                    String.format(centerTablesDataNode, centerTable));
            sets.add(tableRuleConfig);
        }
        // 定义区域表的分库规则
        InlineShardingStrategyConfiguration databaseShardingStrategyConfig = new InlineShardingStrategyConfiguration(
                regionTablesShardingDatabaseColumn, regionTablesShardingDatabaseAlgorithm);
        for (String regionTable : regionTables) {
            TableRuleConfiguration tableRuleConfig = new TableRuleConfiguration(regionTable, String.format(regionTablesDataNode, regionTable));
            tableRuleConfig.setDatabaseShardingStrategyConfig(databaseShardingStrategyConfig);
            // 设置区域表使用雪花算法生成主键
            switch (regionTable){
                case "orders":
                    tableRuleConfig.setKeyGeneratorConfig(new KeyGeneratorConfiguration("SNOWFLAKE", "orders_id"));
                    break;
                case "course_collect":
                    tableRuleConfig.setKeyGeneratorConfig(new KeyGeneratorConfiguration("SNOWFLAKE", "course_collect_id"));
                    break;
                case "course_user":
                    tableRuleConfig.setKeyGeneratorConfig(new KeyGeneratorConfiguration("SNOWFLAKE", "course_user_id"));
                    break;
                case "tb_user":
                    tableRuleConfig.setKeyGeneratorConfig(new KeyGeneratorConfiguration("SNOWFLAKE", "user_id"));
                    break;
                default:
                    tableRuleConfig.setKeyGeneratorConfig(new KeyGeneratorConfiguration("SNOWFLAKE", "id"));
                    break;
            }
            sets.add(tableRuleConfig);
        }

        // 定义区域表的分库规则
        InlineShardingStrategyConfiguration courseDetailsShardingStrategyConfig = new InlineShardingStrategyConfiguration(
                courseDetailsShardingDatabaseColumn, courseDetailsShardingDatabaseAlgorithm);
        for (String regionTable : courseDetails) {
            TableRuleConfiguration tableRuleConfig = new TableRuleConfiguration(regionTable, String.format(regionTablesDataNode, regionTable));
            tableRuleConfig.setDatabaseShardingStrategyConfig(courseDetailsShardingStrategyConfig);
            // 设置区域表使用雪花算法生成主键
            KeyGeneratorConfiguration keyGeneratorConfig = new KeyGeneratorConfiguration("SNOWFLAKE", "id");
            tableRuleConfig.setKeyGeneratorConfig(keyGeneratorConfig);

            sets.add(tableRuleConfig);
        }
        return sets;
    }

    /**
     * 配置数据源
     */
    private Map<String, DataSource> getShardingDataBase() {
        String testQuery = "SELECT 1";
        Map<String, DataSource> map = new HashMap<>();
        datasource.forEach((datasourceName, shardingDataBase) -> {
            HikariConfig config = new HikariConfig();
            config.setDriverClassName(shardingDataBase.getDriverClassName());
            config.setJdbcUrl(shardingDataBase.getJdbcUrl());
            config.setUsername(shardingDataBase.getUsername());
            config.setPassword(shardingDataBase.getPassword());
            config.setPoolName(datasourceName);
            config.setMinimumIdle(shardingDataBase.getMinimumIdle());
            config.setMaximumPoolSize(shardingDataBase.getMaximumPoolSize());
            config.setIdleTimeout(shardingDataBase.getIdleTimeout());
            config.setMaxLifetime(shardingDataBase.getMaxLifetime());
            config.setConnectionTimeout(shardingDataBase.getConnectionTimeout());
            config.setConnectionTestQuery(testQuery);
            map.put(datasourceName, new HikariDataSource(config));
        });
        return map;
    }

    /**
     * 配置读写分离
     */
    private Set<MasterSlaveRuleConfiguration> masterSlaveRuleConfigs() {
        Set<MasterSlaveRuleConfiguration> sets = new HashSet<>();
        masterSlaveRules.forEach((databaseName, masterSlaveRules) -> {
            MasterSlaveRuleConfiguration masterSlaveRuleConfig = new MasterSlaveRuleConfiguration(databaseName,
                    masterSlaveRules.getMasterDataSourceName(), masterSlaveRules.getSlaveDataSourceNames());
            sets.add(masterSlaveRuleConfig);
        });
        return sets;
    }

    /**
     * 绑定表的分片规则
     */
    public static Set<String> bindingTableGroups(List<Set<String>> sets) {
        if (sets != null && !sets.isEmpty()) {
            Set<String> tableGroups = new HashSet<>();
            for (Set<String> set : sets) {
                String tableNames = bindingTable(set.toArray(new String[0]));
                if (StringUtils.isNotBlank(tableNames)) {
                    tableGroups.add(tableNames);
                }
            }
            return tableGroups;
        }
        return null;
    }

    /**
     * 批量绑定表规则
     *
     * @param tables 批量绑定的标规则
     * @return tableNames
     */
    private static String bindingTable(String... tables) {
        StringBuilder tableNames = new StringBuilder();
        if (tables != null && tables.length != 0) {
            for (String table : tables) {
                tableNames.append(table).append(",");
            }
            tableNames.deleteCharAt(tableNames.length() - 1);
        }
        return tableNames.toString();
    }
}