package com.eactive.eai.common.inflow.dual;

import static org.junit.jupiter.api.Assertions.*;

import java.time.Duration;
import java.util.concurrent.ConcurrentHashMap;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.test.util.ReflectionTestUtils;

import com.eactive.eai.common.inflow.InflowTargetVO;

import io.github.bucket4j.Bandwidth;
import io.github.bucket4j.Bucket4j;
import io.github.bucket4j.local.LocalBucket;

/**
 * DualInflowControlManager의 Dual 맵 기반 오버라이드 메서드 단위 테스트.
 *
 * <p>검증 포인트:
 * - getAdapterAllKeys / getInterfaceAllKeys 가 Dual 맵(adapterBucketMap/interfaceBucketMap) 을 사용
 * - getAdapterInflowThreashold / getInterfaceInflowThreashold 가 Dual 맵의 VO 를 반환
 * - removeAdapter / removeInterface 가 Dual 맵에서 항목을 제거
 * - base 의 adapterBucketList / interfaceBucketList 상태와 무관하게 동작 (DAO 이중 호출 제거 효과)
 */
class DualInflowControlManagerBucketMethodsTest {

    private DualInflowControlManager manager;

    @BeforeEach
    void setUp() {
        manager = new DualInflowControlManager();
    }

    // -------------------------------------------------------------------------
    // Helpers
    // -------------------------------------------------------------------------

    private InflowTargetVO makeVO(String name) {
        InflowTargetVO vo = new InflowTargetVO();
        vo.setName(name);
        vo.setThresholdPerSecond(100);
        vo.setThreshold(10000);
        vo.setThresholdTimeUnit("DAY");
        vo.setActivate(true);
        return vo;
    }

    private LocalBucket bucket(long capacity) {
        return Bucket4j.builder()
                .addLimit(Bandwidth.simple(capacity, Duration.ofSeconds(1)))
                .build();
    }

    private DualCustomBucket dualBucket(String name) {
        return new DualCustomBucket(bucket(100), bucket(1000), makeVO(name));
    }

    private void setAdapterMap(ConcurrentHashMap<String, DualCustomBucket> map) {
        ReflectionTestUtils.setField(manager, "adapterBucketMap", map);
    }

    private void setInterfaceMap(ConcurrentHashMap<String, DualCustomBucket> map) {
        ReflectionTestUtils.setField(manager, "interfaceBucketMap", map);
    }

    // =========================================================================
    // getAdapterAllKeys
    // =========================================================================

    @Test
    void getAdapterAllKeys_emptyMap_returnsEmptyArray() {
        setAdapterMap(new ConcurrentHashMap<>());

        assertEquals(0, manager.getAdapterAllKeys().length);
    }

    @Test
    void getAdapterAllKeys_twoEntries_returnsSortedKeys() {
        ConcurrentHashMap<String, DualCustomBucket> map = new ConcurrentHashMap<>();
        map.put("ADAPTER_B", dualBucket("ADAPTER_B"));
        map.put("ADAPTER_A", dualBucket("ADAPTER_A"));
        setAdapterMap(map);

        String[] keys = manager.getAdapterAllKeys();

        assertArrayEquals(new String[]{"ADAPTER_A", "ADAPTER_B"}, keys);
    }

    @Test
    void getAdapterAllKeys_usesDualMap_notBaseList() {
        // Dual 맵에만 항목 존재 — base 의 adapterBucketList 는 비어 있음
        ConcurrentHashMap<String, DualCustomBucket> map = new ConcurrentHashMap<>();
        map.put("DUAL_ONLY", dualBucket("DUAL_ONLY"));
        setAdapterMap(map);

        String[] keys = manager.getAdapterAllKeys();

        assertEquals(1, keys.length);
        assertEquals("DUAL_ONLY", keys[0]);
    }

    // =========================================================================
    // getInterfaceAllKeys
    // =========================================================================

    @Test
    void getInterfaceAllKeys_emptyMap_returnsEmptyArray() {
        setInterfaceMap(new ConcurrentHashMap<>());

        assertEquals(0, manager.getInterfaceAllKeys().length);
    }

    @Test
    void getInterfaceAllKeys_threeEntries_returnsSortedKeys() {
        ConcurrentHashMap<String, DualCustomBucket> map = new ConcurrentHashMap<>();
        map.put("IF_C", dualBucket("IF_C"));
        map.put("IF_A", dualBucket("IF_A"));
        map.put("IF_B", dualBucket("IF_B"));
        setInterfaceMap(map);

        String[] keys = manager.getInterfaceAllKeys();

        assertArrayEquals(new String[]{"IF_A", "IF_B", "IF_C"}, keys);
    }

    @Test
    void getInterfaceAllKeys_usesDualMap_notBaseList() {
        ConcurrentHashMap<String, DualCustomBucket> map = new ConcurrentHashMap<>();
        map.put("DUAL_IF", dualBucket("DUAL_IF"));
        setInterfaceMap(map);

        String[] keys = manager.getInterfaceAllKeys();

        assertEquals(1, keys.length);
        assertEquals("DUAL_IF", keys[0]);
    }

    // =========================================================================
    // getAdapterInflowThreashold
    // =========================================================================

    @Test
    void getAdapterInflowThreashold_found_returnsVO() {
        InflowTargetVO vo = makeVO("ADAPTER_A");
        ConcurrentHashMap<String, DualCustomBucket> map = new ConcurrentHashMap<>();
        map.put("ADAPTER_A", new DualCustomBucket(bucket(10), bucket(100), vo));
        setAdapterMap(map);

        InflowTargetVO result = manager.getAdapterInflowThreashold("ADAPTER_A");

        assertSame(vo, result);
    }

    @Test
    void getAdapterInflowThreashold_notFound_returnsNull() {
        setAdapterMap(new ConcurrentHashMap<>());

        assertNull(manager.getAdapterInflowThreashold("MISSING"));
    }

    @Test
    void getAdapterInflowThreashold_usesDualMap_notBaseList() {
        // base adapterBucketList 에는 항목 없고 Dual 맵에만 존재
        InflowTargetVO vo = makeVO("ADAPTER_A");
        ConcurrentHashMap<String, DualCustomBucket> map = new ConcurrentHashMap<>();
        map.put("ADAPTER_A", new DualCustomBucket(null, null, vo));
        setAdapterMap(map);

        assertSame(vo, manager.getAdapterInflowThreashold("ADAPTER_A"));
    }

    // =========================================================================
    // getInterfaceInflowThreashold
    // =========================================================================

    @Test
    void getInterfaceInflowThreashold_found_returnsVO() {
        InflowTargetVO vo = makeVO("IF_001");
        ConcurrentHashMap<String, DualCustomBucket> map = new ConcurrentHashMap<>();
        map.put("IF_001", new DualCustomBucket(bucket(10), bucket(100), vo));
        setInterfaceMap(map);

        assertSame(vo, manager.getInterfaceInflowThreashold("IF_001"));
    }

    @Test
    void getInterfaceInflowThreashold_notFound_returnsNull() {
        setInterfaceMap(new ConcurrentHashMap<>());

        assertNull(manager.getInterfaceInflowThreashold("MISSING"));
    }

    @Test
    void getInterfaceInflowThreashold_usesDualMap_notBaseList() {
        InflowTargetVO vo = makeVO("IF_001");
        ConcurrentHashMap<String, DualCustomBucket> map = new ConcurrentHashMap<>();
        map.put("IF_001", new DualCustomBucket(null, null, vo));
        setInterfaceMap(map);

        assertSame(vo, manager.getInterfaceInflowThreashold("IF_001"));
    }

    // =========================================================================
    // removeAdapter
    // =========================================================================

    @Test
    void removeAdapter_existingKey_removedFromDualMap() {
        ConcurrentHashMap<String, DualCustomBucket> map = new ConcurrentHashMap<>();
        map.put("ADAPTER_A", dualBucket("ADAPTER_A"));
        map.put("ADAPTER_B", dualBucket("ADAPTER_B"));
        setAdapterMap(map);

        manager.removeAdapter("ADAPTER_A");

        assertNull(manager.getAdapterBucket("ADAPTER_A"));
        assertNotNull(manager.getAdapterBucket("ADAPTER_B"));
    }

    @Test
    void removeAdapter_keysReflectRemoval() {
        ConcurrentHashMap<String, DualCustomBucket> map = new ConcurrentHashMap<>();
        map.put("ADAPTER_A", dualBucket("ADAPTER_A"));
        map.put("ADAPTER_B", dualBucket("ADAPTER_B"));
        setAdapterMap(map);

        manager.removeAdapter("ADAPTER_A");

        String[] keys = manager.getAdapterAllKeys();
        assertEquals(1, keys.length);
        assertEquals("ADAPTER_B", keys[0]);
    }

    @Test
    void removeAdapter_missingKey_noException() {
        setAdapterMap(new ConcurrentHashMap<>());

        assertDoesNotThrow(() -> manager.removeAdapter("NOT_EXISTS"));
    }

    // =========================================================================
    // removeInterface
    // =========================================================================

    @Test
    void removeInterface_existingKey_removedFromDualMap() {
        ConcurrentHashMap<String, DualCustomBucket> map = new ConcurrentHashMap<>();
        map.put("IF_001", dualBucket("IF_001"));
        map.put("IF_002", dualBucket("IF_002"));
        setInterfaceMap(map);

        manager.removeInterface("IF_001");

        assertNull(manager.getInterfaceBucket("IF_001"));
        assertNotNull(manager.getInterfaceBucket("IF_002"));
    }

    @Test
    void removeInterface_keysReflectRemoval() {
        ConcurrentHashMap<String, DualCustomBucket> map = new ConcurrentHashMap<>();
        map.put("IF_001", dualBucket("IF_001"));
        map.put("IF_002", dualBucket("IF_002"));
        setInterfaceMap(map);

        manager.removeInterface("IF_001");

        String[] keys = manager.getInterfaceAllKeys();
        assertEquals(1, keys.length);
        assertEquals("IF_002", keys[0]);
    }

    @Test
    void removeInterface_missingKey_noException() {
        setInterfaceMap(new ConcurrentHashMap<>());

        assertDoesNotThrow(() -> manager.removeInterface("NOT_EXISTS"));
    }
}
