package org.example.Utils;

import jakarta.annotation.PreDestroy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;

import javax.sql.DataSource;
import java.time.Duration;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;

@Component
public class DynamicJdbcTemplateRegistry {
    private static final Logger log = LoggerFactory.getLogger(DynamicJdbcTemplateRegistry.class);
    private static final int DEFAULT_MAX_POOLS = 32;
    private static final long DEFAULT_IDLE_CLOSE_MILLIS = Duration.ofMinutes(60).toMillis();
    private static final Map<String, Entry> POOLS = new HashMap<>();

    private static int maxPools = DEFAULT_MAX_POOLS;
    private static long idleCloseMillis = DEFAULT_IDLE_CLOSE_MILLIS;

    @Value("${app.datasource.dynamic.max-pools:32}")
    public void setConfiguredMaxPools(int configuredMaxPools) {
        synchronized (DynamicJdbcTemplateRegistry.class) {
            maxPools = Math.max(1, configuredMaxPools);
            evictOverflow();
        }
    }

    @Value("${app.datasource.dynamic.idle-close-minutes:60}")
    public void setConfiguredIdleCloseMinutes(long minutes) {
        synchronized (DynamicJdbcTemplateRegistry.class) {
            idleCloseMillis = Duration.ofMinutes(Math.max(1, minutes)).toMillis();
        }
    }

    public static synchronized JdbcTemplate getOrCreate(
            String connectionString,
            Function<String, JdbcTemplate> creator
    ) {
        if (connectionString == null || connectionString.trim().isEmpty()) {
            throw new IllegalArgumentException("connectionString must not be empty");
        }
        Objects.requireNonNull(creator, "creator must not be null");

        long now = System.currentTimeMillis();
        closeIdlePools(now);

        Entry entry = POOLS.get(connectionString);
        if (entry != null) {
            entry.lastAccessMillis = now;
            return entry.jdbcTemplate;
        }

        if (POOLS.size() >= maxPools) {
            evictOldest();
        }

        JdbcTemplate jdbcTemplate = Objects.requireNonNull(
                creator.apply(connectionString),
                "creator returned null JdbcTemplate"
        );
        POOLS.put(connectionString, new Entry(jdbcTemplate, now));
        return jdbcTemplate;
    }

    public static synchronized void closeIdlePools() {
        closeIdlePools(System.currentTimeMillis());
    }

    public static synchronized void closeAll() {
        for (Entry entry : POOLS.values()) {
            close(entry.jdbcTemplate);
        }
        POOLS.clear();
    }

    public static synchronized int getActivePoolCount() {
        return POOLS.size();
    }

    public static synchronized void configureForTests(int configuredMaxPools, Duration idleTimeout) {
        closeAll();
        maxPools = Math.max(1, configuredMaxPools);
        idleCloseMillis = Math.max(0, idleTimeout.toMillis());
    }

    public static synchronized void resetForTests() {
        closeAll();
        maxPools = DEFAULT_MAX_POOLS;
        idleCloseMillis = DEFAULT_IDLE_CLOSE_MILLIS;
    }

    @PreDestroy
    public void destroy() {
        closeAll();
    }

    private static void closeIdlePools(long now) {
        Iterator<Map.Entry<String, Entry>> iterator = POOLS.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, Entry> mapEntry = iterator.next();
            Entry entry = mapEntry.getValue();
            if (now - entry.lastAccessMillis >= idleCloseMillis) {
                close(entry.jdbcTemplate);
                iterator.remove();
            }
        }
    }

    private static void evictOverflow() {
        while (POOLS.size() > maxPools) {
            evictOldest();
        }
    }

    private static void evictOldest() {
        POOLS.entrySet().stream()
                .min(Comparator.comparingLong(entry -> entry.getValue().lastAccessMillis))
                .ifPresent(entry -> {
                    close(entry.getValue().jdbcTemplate);
                    POOLS.remove(entry.getKey());
                });
    }

    private static void close(JdbcTemplate jdbcTemplate) {
        DataSource dataSource = jdbcTemplate.getDataSource();
        if (dataSource instanceof AutoCloseable) {
            try {
                ((AutoCloseable) dataSource).close();
            } catch (Exception e) {
                log.warn("Failed to close dynamic datasource", e);
            }
        }
    }

    private static class Entry {
        private final JdbcTemplate jdbcTemplate;
        private long lastAccessMillis;

        private Entry(JdbcTemplate jdbcTemplate, long lastAccessMillis) {
            this.jdbcTemplate = jdbcTemplate;
            this.lastAccessMillis = lastAccessMillis;
        }
    }
}
