package com.erp.tenant;

import com.zaxxer.hikari.HikariDataSource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

import javax.sql.DataSource;
import java.util.concurrent.ConcurrentHashMap;

/**
 * {@link DbContextHolder#get()} 값을 lookup key 로 사용해 요청마다 tenant DataSource 로 라우팅.
 *
 * ★ 2026-04-24 Codex 리뷰 반영:
 *   - 이전 버전은 {@code setTargetDataSources() + afterPropertiesSet()} 를 add/remove 때마다 재호출해
 *     내부 {@code resolvedDataSources} 를 rebuild 했음. 그 사이 lookup 이 들어오면 기존 체크아웃
 *     커넥션과 새 풀이 충돌할 수 있음.
 *   - 지금은 {@link #determineTargetDataSource()} 자체를 override 하고 {@link ConcurrentHashMap} 을
 *     직접 조회. {@code afterPropertiesSet()} 은 no-op 로 override 해서 super 의 rebuild 로직을
 *     사용하지 않음.
 *   - add/remove 는 put/remove 단일 연산이라 락 없이 안전.
 */
@Slf4j
public class TenantRoutingDataSource extends AbstractRoutingDataSource {

    private final ConcurrentHashMap<String, DataSource> sources = new ConcurrentHashMap<>();

    public void registerMeta(DataSource meta) {
        sources.put(DbContextHolder.META, meta);
        log.info("[TenantRouting] META DataSource registered");
    }

    public void addTenant(String dbName, DataSource ds) {
        DataSource prev = sources.putIfAbsent(dbName, ds);
        if (prev != null) {
            log.warn("[TenantRouting] tenant '{}' already registered — new instance ignored", dbName);
            closeIfHikari(ds);
            return;
        }
        log.info("[TenantRouting] tenant DataSource added: {}", dbName);
    }

    /** ★ LRU/TTL eviction 전 단계 — 수동으로 풀을 닫고 싶을 때 사용. Phase 6 에서 자동화 예정. */
    public boolean evictTenant(String dbName) {
        DataSource removed = sources.remove(dbName);
        if (removed == null) return false;
        closeIfHikari(removed);
        log.warn("[TenantRouting] tenant DataSource evicted: {}", dbName);
        return true;
    }

    public boolean hasTenant(String dbName) {
        return sources.containsKey(dbName);
    }

    public DataSource getMetaDataSource() {
        return sources.get(DbContextHolder.META);
    }

    // ------------------------------------------------------------------
    // AbstractRoutingDataSource override
    // ------------------------------------------------------------------

    /** super 의 resolvedDataSources rebuild 를 타지 않도록 no-op. */
    @Override
    public void afterPropertiesSet() {
        // intentionally empty
    }

    @Override
    protected DataSource determineTargetDataSource() {
        String key = (String) determineCurrentLookupKey();
        if (key == null) key = DbContextHolder.META;
        DataSource ds = sources.get(key);
        if (ds != null) return ds;

        // 알 수 없는 tenant key → META 로 안전 fallback
        DataSource meta = sources.get(DbContextHolder.META);
        if (meta == null) {
            throw new IllegalStateException("META DataSource not registered yet");
        }
        log.debug("[TenantRouting] key '{}' not found, falling back to META", key);
        return meta;
    }

    @Override
    protected Object determineCurrentLookupKey() {
        String key = DbContextHolder.get();
        return key != null ? key : DbContextHolder.META;
    }

    private static void closeIfHikari(DataSource ds) {
        if (ds instanceof HikariDataSource h) {
            try { h.close(); } catch (Exception ignored) { }
        }
    }
}
