package com.magistr.app.config.tenant;

import com.zaxxer.hikari.HikariDataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * DataSource, который переключается между БД разных тенантов.
 * На каждый запрос determineCurrentLookupKey() возвращает текущий тенант из TenantContext.
 */
public class TenantRoutingDataSource extends AbstractRoutingDataSource {

    private static final Logger log = LoggerFactory.getLogger(TenantRoutingDataSource.class);

    private final Map<String, TenantConfig> tenantConfigs = new ConcurrentHashMap<>();
    private final Map<Object, Object> dataSources = new ConcurrentHashMap<>();
    private boolean initialized = false;

    public TenantRoutingDataSource() {
        // Устанавливаем пустой map чтобы afterPropertiesSet не падал
        setTargetDataSources(new HashMap<>());
        setLenientFallback(false);
    }

    @Override
    protected Object determineCurrentLookupKey() {
        String tenant = TenantContext.getCurrentTenant();

        if (tenant == null) {
            // Нет HTTP контекста (JPA init, background tasks) — берём первый доступный
            if (!dataSources.isEmpty()) {
                return dataSources.keySet().iterator().next().toString();
            }
            return "default";
        }

        // HTTP запрос — возвращаем точный ключ тенанта
        // Если тенанта нет — TenantInterceptor уже вернул 404
        return tenant;
    }

    /**
     * Добавляет тенант и создаёт для него HikariCP пул.
     */
    public void addTenant(TenantConfig config) {
        String domain = config.getDomain().toLowerCase();
        HikariDataSource ds = createDataSource(config);

        dataSources.put(domain, ds);
        tenantConfigs.put(domain, config);

        // Обновляем target data sources
        setTargetDataSources(dataSources);
        afterPropertiesSet();
        initialized = true;

        log.info("Added tenant '{}' -> {}", domain, config.getUrl());
    }

    /**
     * Удаляет тенант и закрывает его пул соединений.
     */
    public void removeTenant(String domain) {
        domain = domain.toLowerCase();
        Object removed = dataSources.remove(domain);
        tenantConfigs.remove(domain);

        if (removed instanceof HikariDataSource ds) {
            ds.close();
            log.info("Removed and closed tenant '{}'", domain);
        }

        setTargetDataSources(dataSources);
        afterPropertiesSet();
    }

    /**
     * Проверяет подключение к БД для указанного тенанта.
     */
    public boolean testConnection(String domain) {
        DataSource ds = (DataSource) dataSources.get(domain.toLowerCase());
        if (ds == null) return false;

        try (Connection conn = ds.getConnection()) {
            return conn.isValid(5);
        } catch (SQLException e) {
            log.warn("Connection test failed for tenant '{}': {}", domain, e.getMessage());
            return false;
        }
    }

    /**
     * Тестирует подключение по произвольным параметрам (без регистрации тенанта).
     */
    public String testExternalConnection(String url, String username, String password) {
        HikariDataSource ds = new HikariDataSource();
        ds.setJdbcUrl(url);
        ds.setUsername(username);
        ds.setPassword(password);
        ds.setMaximumPoolSize(1);
        ds.setConnectionTimeout(5000);

        try (Connection conn = ds.getConnection()) {
            if (conn.isValid(5)) {
                return "OK";
            }
            return "Подключение не валидно";
        } catch (Exception e) {
            return e.getMessage();
        } finally {
            ds.close();
        }
    }

    public Map<String, TenantConfig> getTenantConfigs() {
        return tenantConfigs;
    }

    public boolean hasTenant(String domain) {
        return tenantConfigs.containsKey(domain.toLowerCase());
    }

    public boolean isInitialized() {
        return initialized && !dataSources.isEmpty();
    }

    private HikariDataSource createDataSource(TenantConfig config) {
        HikariDataSource ds = new HikariDataSource();
        ds.setJdbcUrl(config.getUrl());
        ds.setUsername(config.getUsername());
        ds.setPassword(config.getPassword());
        ds.setPoolName("tenant-" + config.getDomain());
        ds.setMaximumPoolSize(10);
        ds.setMinimumIdle(2);
        ds.setConnectionTimeout(10000);
        ds.setIdleTimeout(300000);
        ds.setMaxLifetime(600000);
        // Не падать при инициализации если БД недоступна
        ds.setInitializationFailTimeout(-1);
        return ds;
    }
}
