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

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.druid.pool.DruidDataSource;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.qiaoba.api.tenant.entity.SysTenantDatasource;
import com.qiaoba.api.tenant.entity.param.SysTenantDatasourceParam;
import com.qiaoba.common.base.code.DatasourceErrorCode;
import com.qiaoba.common.base.constant.BaseConstant;
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.BackupDatasourceContext;
import com.qiaoba.common.database.context.PrimaryDatasourceContext;
import com.qiaoba.common.database.entity.DynamicDataSource;
import com.qiaoba.common.database.util.DatasourceUtil;
import com.qiaoba.common.database.util.JdbcUtil;
import com.qiaoba.module.tenant.mapper.SysTenantDatasourceMapper;
import com.qiaoba.module.tenant.service.SysTenantDatasourceService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Objects;

/**
 * 租户数据源 服务层实现
 *
 * @author ailanyin
 * @version 1.0
 * @since 2023/6/7 14:49
 */
@Service
@RequiredArgsConstructor
public class SysTenantDatasourceServiceImpl implements SysTenantDatasourceService {

    private final SysTenantDatasourceMapper sysTenantDatasourceMapper;

    @Override
    public SysTenantDatasource selectPrimary(String tenantId) {
        return sysTenantDatasourceMapper.selectOne(paramToWrapper(new SysTenantDatasourceParam(tenantId, BaseEnum.YES.getCode())));
    }

    @Override
    public SysTenantDatasource selectPrimary(String tenantId, boolean allowNull) {
        SysTenantDatasource primary = selectPrimary(tenantId);
        if (!allowNull && Objects.isNull(primary)) {
            throw new ServiceException(DatasourceErrorCode.NOT_FIND.getCode(), DatasourceErrorCode.NOT_FIND.getMsg());
        }
        return primary;
    }

    @Override
    public SysTenantDatasource selectByIp(String tenantId, String ip) {
        SysTenantDatasourceParam param = new SysTenantDatasourceParam(tenantId);
        param.setIp(ip);
        return sysTenantDatasourceMapper.selectOne(paramToWrapper(param));
    }

    @Override
    public int insert(SysTenantDatasource sysTenantDatasource) {
        DynamicDataSource dynamicDataSource = transform(sysTenantDatasource);
        boolean connect = JdbcUtil.checkConnect(dynamicDataSource.getDriver(), dynamicDataSource.getUrl(), dynamicDataSource.getUsername(), dynamicDataSource.getPassword());
        if (!connect) {
            // 连接不可用
            throw new ServiceException(DatasourceErrorCode.CONNECT_ERROR.getCode(), DatasourceErrorCode.CONNECT_ERROR.getMsg());
        }
        int result = sysTenantDatasourceMapper.insert(sysTenantDatasource);
        if (result > BaseConstant.HANDLE_ERROR) {
            // 添加到备用数据源
            BackupDatasourceContext.addBackupMap(sysTenantDatasource.getTenantId(), dynamicDataSource);
        }
        return result;
    }

    @Override
    public int updateById(SysTenantDatasource sysTenantDatasource) {
        DynamicDataSource dynamicDataSource = transform(sysTenantDatasource);
        boolean connect = JdbcUtil.checkConnect(dynamicDataSource.getDriver(), dynamicDataSource.getUrl(), dynamicDataSource.getUsername(), dynamicDataSource.getPassword());
        if (!connect) {
            // 连接不可用
            throw new ServiceException(DatasourceErrorCode.CONNECT_ERROR.getCode(), DatasourceErrorCode.CONNECT_ERROR.getMsg());
        }
        int result = sysTenantDatasourceMapper.updateById(sysTenantDatasource);
        if (result > BaseConstant.HANDLE_ERROR) {
            // 更新备用数据源
            BackupDatasourceContext.updateBackupMap(sysTenantDatasource.getTenantId(), dynamicDataSource);
        }
        return result;
    }

    @Override
    public int deleteByIds(List<String> ids) {
        // 删除备用Map
        return sysTenantDatasourceMapper.deleteBatchIds(ids);
    }

    @Override
    public List<SysTenantDatasource> selectList(String tenantId) {
        return sysTenantDatasourceMapper.selectList(paramToWrapper(new SysTenantDatasourceParam(tenantId)));
    }

    @Override
    public List<SysTenantDatasource> selectList(SysTenantDatasourceParam param) {
        return sysTenantDatasourceMapper.selectList(paramToWrapper(param));
    }

    @Override
    public void setBackupDatasourceExcludeId(String tenantId, String excludeId) {
        sysTenantDatasourceMapper.setBackupDatasourceExcludeId(tenantId, excludeId, BaseEnum.NO.getCode());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public int switchPrimary(String tenantId, String datasourceId) {
        SysTenantDatasource datasource = sysTenantDatasourceMapper.selectById(datasourceId);
        DynamicDataSource dynamicDataSource = transform(datasource);
        Object dataSource = DatasourceUtil.buildDataSource(tenantId, dynamicDataSource);
        if (Objects.isNull(dataSource)) {
            // 连接不可用
            throw new ServiceException(DatasourceErrorCode.CONNECT_ERROR.getCode(), DatasourceErrorCode.CONNECT_ERROR.getMsg());
        }
        // 更新其他的为非主要
        setBackupDatasourceExcludeId(tenantId, datasourceId);
        // 更新他为主要
        datasource.setIsPrimary(BaseEnum.YES.getCode());
        int result = updateById(datasource);
        // 刷新PrimaryDatasourceMap
        if (result > BaseConstant.HANDLE_ERROR) {
            Object oldDataSource = PrimaryDatasourceContext.get(tenantId);
            // 更新新得数据源为租户主要数据源
            DatasourceUtil.changePrimaryDatasource(tenantId, dataSource);
            // 将老的租户数据源设置为备用
            if (Objects.nonNull(oldDataSource)) {
                DruidDataSource druidDataSource = ((DruidDataSource) oldDataSource);
                String ip = DataBaseEnum.getIp(druidDataSource.getUrl(), druidDataSource.getDriverClassName());
                SysTenantDatasource sysTenantDatasource = sysTenantDatasourceMapper.selectByIp(tenantId, ip);
                BackupDatasourceContext.addBackupMap(tenantId, transform(sysTenantDatasource));
                druidDataSource.close();
            }
        } else {
            IoUtil.close((DruidDataSource) dataSource);
        }
        return result;
    }

    @Override
    public DynamicDataSource transform(SysTenantDatasource datasource) {
        DynamicDataSource dynamicDataSource = BeanUtil.copyProperties(datasource, DynamicDataSource.class);
        dynamicDataSource.setInitialSize(datasource.getInitCount());
        dynamicDataSource.setMinIdle(datasource.getMinCount());
        dynamicDataSource.setMaxActive(datasource.getMaxCount());
        dynamicDataSource.setDriver(DataBaseEnum.getDriver(datasource.getType()));
        dynamicDataSource.setUrl(DataBaseEnum.getUrl(datasource.getType(), datasource.getIp(), datasource.getPort(), datasource.getDbName(), datasource.getSchemaName()));
        return dynamicDataSource;
    }

    private QueryWrapper<SysTenantDatasource> paramToWrapper(SysTenantDatasourceParam param) {
        QueryWrapper<SysTenantDatasource> wrapper = new QueryWrapper<>();
        wrapper.lambda()
                .eq(StrUtil.isNotBlank(param.getTenantId()), SysTenantDatasource::getTenantId, param.getTenantId())
                .eq(StrUtil.isNotBlank(param.getIp()), SysTenantDatasource::getIp, param.getIp())
                .eq(StrUtil.isNotBlank(param.getIsPrimary()), SysTenantDatasource::getIsPrimary, param.getIsPrimary());
        return wrapper;
    }
}
