package com.agent.management.service;

import com.agent.management.common.exception.BusinessException;
import com.agent.management.model.entity.DataSource;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * 动态 JDBC 连接池管理：按 datasourceId 维护 HikariCP 连接池缓存
 *
 * <p>使用 LRU 策略（accessOrder=true），最大 10 个连接池。
 * 超过限制时关闭最久未使用的池。</p>
 *
 * <p>线程安全：通过 synchronizedMap 包装 LinkedHashMap 实现。</p>
 */
@Slf4j
@Service
public class DynamicJdbcService {

    private static final int MAX_POOLS = 10;
    private static final int POOL_SIZE = 5;
    private static final long CONNECTION_TIMEOUT_MS = 10_000L;   // 10s 获取连接超时
    private static final long MAX_LIFETIME_MS = 30 * 60 * 1000L; // 30 分钟连接最长生命周期

    /**
     * LRU 缓存：datasourceId -> HikariDataSource
     * accessOrder=true 表示按访问顺序排序（最近访问的在末尾，最旧的在头部）
     */
    private final Map<Long, HikariDataSource> poolCache =
            Collections.synchronizedMap(new LinkedHashMap<Long, HikariDataSource>(16, 0.75f, true) {
                @Override
                protected boolean removeEldestEntry(Map.Entry<Long, HikariDataSource> eldest) {
                    if (size() > MAX_POOLS) {
                        log.info("连接池缓存超过 {} 上限，关闭 eldest: datasourceId={}", MAX_POOLS, eldest.getKey());
                        try {
                            eldest.getValue().close();
                        } catch (Exception e) {
                            log.warn("关闭旧连接池失败: {}", e.getMessage());
                        }
                        return true;
                    }
                    return false;
                }
            });

    /**
     * 获取一个 JDBC Connection（来自对应数据源的连接池）。
     * 注意：调用方负责关闭 Connection（用 try-with-resources）。
     */
    public Connection getConnection(DataSource ds) {
        HikariDataSource pool = getOrCreatePool(ds);
        try {
            return pool.getConnection();
        } catch (SQLException e) {
            throw new BusinessException("获取数据库连接失败: " + e.getMessage());
        }
    }

    /**
     * 测试连接（创建临时池，验证后关闭）
     * 注意：调用方需通过 ds.getDecryptedPassword() 或显式传入明文密码
     */
    public void testConnection(DataSource ds, String plainPassword) {
        HikariConfig cfg = buildConfig(ds, plainPassword);
        cfg.setMaximumPoolSize(1);  // 测试用最小池
        cfg.setConnectionTimeout(CONNECTION_TIMEOUT_MS);
        try (HikariDataSource testPool = new HikariDataSource(cfg);
             Connection conn = testPool.getConnection()) {
            try (var stmt = conn.createStatement();
                 var rs = stmt.executeQuery(testQuery(ds.getType()))) {
                rs.next();
            }
            log.info("数据源 {} (id={}) 连接测试成功", ds.getName(), ds.getId());
        } catch (SQLException e) {
            log.warn("数据源 {} (id={}) 连接测试失败: {}", ds.getName(), ds.getId(), e.getMessage());
            throw new BusinessException("连接测试失败: " + e.getMessage());
        }
    }

    /**
     * 关闭并清除指定数据源的连接池（数据源更新/删除时调用）
     */
    public void invalidate(Long datasourceId) {
        HikariDataSource pool = poolCache.remove(datasourceId);
        if (pool != null) {
            try {
                pool.close();
                log.info("已关闭数据源 {} 的连接池", datasourceId);
            } catch (Exception e) {
                log.warn("关闭连接池失败: {}", e.getMessage());
            }
        }
    }

    /**
     * 关闭所有连接池（应用关闭时调用）
     */
    public void closeAll() {
        synchronized (poolCache) {
            for (Map.Entry<Long, HikariDataSource> e : poolCache.entrySet()) {
                try { e.getValue().close(); } catch (Exception ex) { /* 静默 */ }
            }
            poolCache.clear();
        }
    }

    // =========================== 内部方法 ===========================

    private HikariDataSource getOrCreatePool(DataSource ds) {
        return poolCache.computeIfAbsent(ds.getId(), id -> createPool(ds));
    }

    /**
     * 创建连接池：从 DataSourceRepository 拿到的是加密密码，
     * 由上层调用方（DataSourceService）通过 CredentialManager.decryptToTransient 解密后写入 decryptedPassword。
     * 此处直接读取 ds.getDecryptedPassword() 取得明文（不再复用 passwordEnc 字段）。
     */
    private HikariDataSource createPool(DataSource ds) {
        String plain = ds.getDecryptedPassword();
        if (plain == null || plain.isEmpty()) {
            throw new BusinessException("数据源密码未解密，无法创建连接池");
        }
        HikariConfig cfg = buildConfig(ds, plain);
        log.info("为数据源 id={} 创建 HikariCP 连接池: {}", ds.getId(), ds.getJdbcUrl());
        return new HikariDataSource(cfg);
    }

    private HikariConfig buildConfig(DataSource ds, String plainPassword) {
        HikariConfig cfg = new HikariConfig();
        cfg.setJdbcUrl(ds.getJdbcUrl());
        cfg.setUsername(ds.getUsername());
        cfg.setPassword(plainPassword);
        cfg.setDriverClassName(resolveDriver(ds.getType()));
        cfg.setMaximumPoolSize(POOL_SIZE);
        cfg.setMinimumIdle(1);
        cfg.setConnectionTimeout(CONNECTION_TIMEOUT_MS);
        cfg.setMaxLifetime(MAX_LIFETIME_MS);
        cfg.setLeakDetectionThreshold(60_000L);
        cfg.setPoolName("ds-" + ds.getId());
        return cfg;
    }

    /**
     * 根据数据源类型解析 JDBC 驱动类名
     */
    private String resolveDriver(String type) {
        if (type == null) throw new BusinessException("数据源类型不能为空");
        switch (type.toUpperCase()) {
            case "MYSQL":
                return "com.mysql.cj.jdbc.Driver";
            case "POSTGRESQL":
            case "PG":
                return "org.postgresql.Driver";
            case "ORACLE":
                return "oracle.jdbc.OracleDriver";
            case "SQLSERVER":
                return "com.microsoft.sqlserver.jdbc.SQLServerDriver";
            case "SQLITE":
                return "org.sqlite.JDBC";
            case "H2":
                return "org.h2.Driver";
            case "DAMENG":
            case "DM":
                return "dm.jdbc.driver.DmDriver";
            default:
                throw new BusinessException("不支持的数据源类型：" + type);
        }
    }

    /**
     * 各数据库测试连接用的 SQL
     */
    private String testQuery(String type) {
        if (type == null) return "SELECT 1";
        String t = type.toUpperCase();
        // Oracle / 达梦都使用 DUAL 表
        if ("ORACLE".equals(t) || "DAMENG".equals(t) || "DM".equals(t)) {
            return "SELECT 1 FROM DUAL";
        }
        return "SELECT 1";
    }
}
