package com.qiaoba.module.tenant.service.impl;

import cn.hutool.core.io.IoUtil;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpStatus;
import com.alibaba.druid.pool.DruidDataSource;
import com.qiaoba.api.system.service.SysConfigApiService;
import com.qiaoba.api.system.service.SysDictApiService;
import com.qiaoba.api.tenant.entity.SysTenant;
import com.qiaoba.api.tenant.entity.SysTenantDatasource;
import com.qiaoba.api.tenant.enums.TenantModeEnum;
import com.qiaoba.api.tenant.utils.TenantUtil;
import com.qiaoba.common.base.code.DatasourceErrorCode;
import com.qiaoba.common.base.code.TenantErrorCode;
import com.qiaoba.common.base.constant.BaseConstant;
import com.qiaoba.common.base.context.BaseContext;
import com.qiaoba.common.base.enums.BaseEnum;
import com.qiaoba.common.base.enums.DataBaseEnum;
import com.qiaoba.common.base.exception.ServiceException;
import com.qiaoba.common.database.context.PrimaryDatasourceContext;
import com.qiaoba.common.database.context.TenantDbTypeContext;
import com.qiaoba.common.database.properties.TenantSchema;
import com.qiaoba.common.database.util.DbUtil;
import com.qiaoba.common.database.util.JdbcUtil;
import com.qiaoba.module.tenant.entity.vo.TenantInitCheckVo;
import com.qiaoba.module.tenant.entity.vo.TenantInitVo;
import com.qiaoba.module.tenant.init.InitTablesStrategyFactory;
import com.qiaoba.module.tenant.service.SysTenantDatasourceService;
import com.qiaoba.module.tenant.service.SysTenantInitService;
import com.qiaoba.module.tenant.service.SysTenantService;
import com.qiaoba.module.tenant.util.InitDataUtil;
import com.qiaoba.module.tenant.util.MenuUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Objects;

/**
 * 租户初始化 服务层实现
 *
 * @author ailanyin
 * @version 1.0
 * @since 2023/6/27 9:07
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class SysTenantInitServiceImpl implements SysTenantInitService {

    private final SysTenantService sysTenantService;
    private final SysTenantDatasourceService sysTenantDatasourceService;
    private final SysConfigApiService sysConfigApiService;
    private final SysDictApiService sysDictApiService;

    @Override
    public TenantInitCheckVo check(String tenantId) {
        // 1. 查询租户信息, 获取租户名称|租户模式
        SysTenant sysTenant = sysTenantService.selectById(tenantId, false);
        checkInitialized(sysTenant);
        // 2. 租户模式 = 3 数据源模式
        if (TenantModeEnum.isDatasource(sysTenant.getMode())) {
            // 3. 根据租户ID查询主要数据源(1条)
            SysTenantDatasource datasource = sysTenantDatasourceService.selectPrimary(tenantId);
            // 4. 若没有主要数据源, 返回 未找到数据源信息
            if (Objects.isNull(datasource)) {
                return new TenantInitCheckVo(sysTenant.getCompanyName(), sysTenant.getMode(), DatasourceErrorCode.NOT_FIND.getCode(), DatasourceErrorCode.NOT_FIND.getMsg());
            }

            Connection connection = null;
            try {
                connection = JdbcUtil.connection(DataBaseEnum.getDriver(datasource.getType()),
                        DataBaseEnum.getUrl(datasource.getType(), datasource.getIp(), datasource.getPort(), datasource.getDbName(), datasource.getSchemaName()),
                        datasource.getUsername(), datasource.getPassword());

                // 检查 SCHEMA 是否存在
                if (StrUtil.isNotBlank(datasource.getSchemaName())) {
                    Boolean schemaExist = DbUtil.checkSchema(datasource.getType(), connection, datasource.getSchemaName());
                    if (!schemaExist) {
                        // 6. 数据源无法连接, 返回
                        return new TenantInitCheckVo(sysTenant.getCompanyName(), sysTenant.getMode(), datasource,
                                DatasourceErrorCode.SCHEMA_NOT_EXIST_ERROR.getCode(), StrUtil.format(DatasourceErrorCode.SCHEMA_NOT_EXIST_ERROR.getMsg(), datasource.getSchemaName()));
                    }
                }
            } catch (Exception e) {
                // 6. 数据源无法连接, 返回
                return new TenantInitCheckVo(sysTenant.getCompanyName(), sysTenant.getMode(), datasource, DatasourceErrorCode.CONNECT_ERROR.getCode(), e.getMessage());
            } finally {
                IoUtil.close(connection);
            }

            // 7. 全部正常
            return new TenantInitCheckVo(sysTenant.getCompanyName(), sysTenant.getMode(), datasource, HttpStatus.HTTP_OK, null);

        }

        return new TenantInitCheckVo(sysTenant.getCompanyName(), sysTenant.getMode(), HttpStatus.HTTP_OK, null);
    }

    @Override
    public TenantInitVo createTables(String tenantId) {
        SysTenant sysTenant = sysTenantService.selectById(tenantId, false);
        checkInitialized(sysTenant);
        // 字段模式
        if (TenantModeEnum.isColumn(sysTenant.getMode())) {
            return new TenantInitVo(HttpStatus.HTTP_OK, "字段模式, 无需新建表");
        }
        // Schema模式
        if (TenantModeEnum.isSchema(sysTenant.getMode())) {
            Connection connection = null;
            try {
                // 获取主库Connection 和 Schema
                DruidDataSource dataSource = (DruidDataSource) PrimaryDatasourceContext.getDefault();
                // 创建新的连接
                connection = JdbcUtil.getConnection(dataSource.getDriverClassName(), dataSource.getUrl(), dataSource.getUsername(), dataSource.getPassword());
                return InitTablesStrategyFactory.getStrategy(TenantDbTypeContext.getDefault()).create(connection, TenantSchema.getSchema(tenantId));
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                IoUtil.close(connection);
            }
        }
        // 数据源模式
        if (TenantModeEnum.isDatasource(sysTenant.getMode())) {
            SysTenantDatasource primary = sysTenantDatasourceService.selectPrimary(tenantId, false);
            Connection connection = JdbcUtil.getConnection(DataBaseEnum.getDriver(primary.getType()),
                    DataBaseEnum.getUrl(primary.getType(), primary.getIp(), primary.getPort(), primary.getDbName(), primary.getSchemaName()),
                    primary.getUsername(), primary.getPassword());
            return InitTablesStrategyFactory.getStrategy(primary.getType()).create(connection);
        }
        return new TenantInitVo(TenantErrorCode.MODE_NOT_FIND.getCode(), TenantErrorCode.MODE_NOT_FIND.getMsg());
    }

    @Override
    public TenantInitVo initData(String tenantId) throws SQLException {
        SysTenant sysTenant = sysTenantService.selectById(tenantId, false);
        checkInitialized(sysTenant);
        // 字段模式
        if (TenantModeEnum.isColumn(sysTenant.getMode())) {
            // 获取默认租户的主数据源
            DataSource dataSource = (DataSource) PrimaryDatasourceContext.getDefault();
            // 初始化
            return init(dataSource.getConnection(), tenantId);
        }
        // SCHEMA
        else if (TenantModeEnum.isSchema(sysTenant.getMode())) {
            // 获取主库Connection 和 Schema
            DruidDataSource dataSource = (DruidDataSource) PrimaryDatasourceContext.getDefault();
            // 创建新的连接
            Connection connection = JdbcUtil.getConnection(dataSource.getDriverClassName(), dataSource.getUrl(), dataSource.getUsername(), dataSource.getPassword());
            String dbType = TenantDbTypeContext.getDefault();
            DbUtil.setSchema(dbType, connection, TenantSchema.getSchema(tenantId));
            // 初始化
            return init(connection, tenantId);
        }
        // Datasource
        else if (TenantModeEnum.isDatasource(sysTenant.getMode())) {
            SysTenantDatasource primary = sysTenantDatasourceService.selectPrimary(tenantId, false);
            Connection connection = JdbcUtil.getConnection(DataBaseEnum.getDriver(primary.getType()),
                    DataBaseEnum.getUrl(primary.getType(), primary.getIp(), primary.getPort(), primary.getDbName(), primary.getSchemaName()),
                    primary.getUsername(), primary.getPassword());
            // 初始化
            return init(connection, tenantId);
        }
        return new TenantInitVo(TenantErrorCode.MODE_NOT_FIND.getCode(), TenantErrorCode.MODE_NOT_FIND.getMsg());
    }

    @Override
    public void initCompleted(String tenantId) {
        int result = sysTenantService.initCompleted(tenantId);
        if (result > BaseConstant.HANDLE_ERROR) {
            syncTenantData2Cache(sysTenantService.selectById(tenantId));
        }
    }

    @Override
    public void syncTenantData2Cache(SysTenant sysTenant) {
        ThreadUtil.execAsync(()->{
            try {
                TenantUtil.setContext(sysTenant);
                // 系统配置
                sysConfigApiService.resetConfigCache();
                // 字典数据
                sysDictApiService.resetDictCache();
                log.info("初始化租户数据完成, 租户ID: {}", sysTenant.getTenantId());
            } catch (Exception e) {
                log.error("初始化租户数据完成, 租户ID: {}, 错误原因: {}", sysTenant.getTenantId(), e.getMessage());
            } finally {
                BaseContext.clearAllHolder();
            }
        });
    }

    private void checkInitialized(SysTenant sysTenant) {
        if (BaseEnum.YES.getCode().equals(sysTenant.getInitialized())) {
            throw new ServiceException(TenantErrorCode.INITIALIZED.getCode(), TenantErrorCode.INITIALIZED.getMsg());
        }
    }

    private TenantInitVo init(Connection conn, String tenantId) {

        try {
            // 手动提交
            conn.setAutoCommit(false);
            // 处理 sys_config
            InitDataUtil.handleSysConfig(conn, tenantId, "init/data/sys_config_data");
            // 处理 sys_post
            InitDataUtil.handleSysPost(conn, tenantId, "init/data/sys_post_data");
            // 处理 sys_role
            InitDataUtil.handleSysRole(conn, tenantId, "init/data/sys_role_data");
            // 处理 sys_user
            InitDataUtil.handleSysUser(conn, tenantId, "init/data/sys_user_data");
            // 处理 sys_user_role
            InitDataUtil.bindUserAndRole(conn, tenantId);
            // 处理 sys_menu
            MenuUtil.handleMenu(conn, tenantId);
            // 处理 sys_role_menu
            InitDataUtil.bindRoleAndMenu(conn, tenantId);
            conn.commit();
            return new TenantInitVo(HttpStatus.HTTP_OK, null);
        } catch (Exception e) {
            try {
                conn.rollback();
            } catch (SQLException ex) {
                ex.printStackTrace();
            }
            return new TenantInitVo(DatasourceErrorCode.INIT_DATA_ERROR.getCode(), e.getMessage());
        } finally {
            InitDataUtil.removeLocal();
            cn.hutool.db.DbUtil.close(conn);
        }
    }
}
