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

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

import java.time.Duration;
import java.util.Map;
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 단위 테스트.
 *
 * <p>start() 호출 없이 adapterBucketMap / interfaceBucketMap을
 * ReflectionTestUtils로 직접 주입하여 detail 메서드만 검증한다.
 */
class DualInflowControlManagerTest {

    private DualInflowControlManager manager;

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

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

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

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

    private LocalBucket exhaustedBucket(long capacity) {
        LocalBucket b = freshBucket(capacity);
        b.tryConsume(capacity);
        return b;
    }

    private DualCustomBucket passBucket(String name) {
        return new DualCustomBucket(freshBucket(10), freshBucket(100), makeVO(name));
    }

    private DualCustomBucket blockedPerSecondBucket(String name) {
        return new DualCustomBucket(exhaustedBucket(1), freshBucket(100), makeVO(name));
    }

    private DualCustomBucket blockedThresholdBucket(String name) {
        return new DualCustomBucket(freshBucket(10), exhaustedBucket(1), makeVO(name));
    }

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

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

    // -------------------------------------------------------------------------
    // isAdapterPassDetail
    // -------------------------------------------------------------------------

    @Test
    void isAdapterPassDetail_adapterNotInMap_returnsNull() {
        assertNull(manager.isAdapterPassDetail("UNKNOWN"));
    }

    @Test
    void isAdapterPassDetail_adapterInMap_pass_returnsNull() {
        Map<String, DualCustomBucket> map = new ConcurrentHashMap<>();
        map.put("ADAPTER_A", passBucket("ADAPTER_A"));
        injectAdapterMap(map);

        assertNull(manager.isAdapterPassDetail("ADAPTER_A"));
    }

    @Test
    void isAdapterPassDetail_adapterInMap_blockedPerSecond() {
        Map<String, DualCustomBucket> map = new ConcurrentHashMap<>();
        map.put("ADAPTER_A", blockedPerSecondBucket("ADAPTER_A"));
        injectAdapterMap(map);

        assertEquals(DualCustomBucket.RESULT_BLOCKED_PER_SECOND,
                manager.isAdapterPassDetail("ADAPTER_A"));
    }

    @Test
    void isAdapterPassDetail_adapterInMap_blockedThreshold() {
        Map<String, DualCustomBucket> map = new ConcurrentHashMap<>();
        map.put("ADAPTER_A", blockedThresholdBucket("ADAPTER_A"));
        injectAdapterMap(map);

        assertEquals(DualCustomBucket.RESULT_BLOCKED_THRESHOLD,
                manager.isAdapterPassDetail("ADAPTER_A"));
    }

    // -------------------------------------------------------------------------
    // isInterfacePassDetail
    // -------------------------------------------------------------------------

    @Test
    void isInterfacePassDetail_interfaceNotInMap_returnsNull() {
        assertNull(manager.isInterfacePassDetail("UNKNOWN"));
    }

    @Test
    void isInterfacePassDetail_interfaceInMap_pass_returnsNull() {
        Map<String, DualCustomBucket> map = new ConcurrentHashMap<>();
        map.put("IF_001", passBucket("IF_001"));
        injectInterfaceMap(map);

        assertNull(manager.isInterfacePassDetail("IF_001"));
    }

    @Test
    void isInterfacePassDetail_interfaceInMap_blockedPerSecond() {
        Map<String, DualCustomBucket> map = new ConcurrentHashMap<>();
        map.put("IF_001", blockedPerSecondBucket("IF_001"));
        injectInterfaceMap(map);

        assertEquals(DualCustomBucket.RESULT_BLOCKED_PER_SECOND,
                manager.isInterfacePassDetail("IF_001"));
    }

    @Test
    void isInterfacePassDetail_interfaceInMap_blockedThreshold() {
        Map<String, DualCustomBucket> map = new ConcurrentHashMap<>();
        map.put("IF_001", blockedThresholdBucket("IF_001"));
        injectInterfaceMap(map);

        assertEquals(DualCustomBucket.RESULT_BLOCKED_THRESHOLD,
                manager.isInterfacePassDetail("IF_001"));
    }

    // -------------------------------------------------------------------------
    // isAdapterPass — boolean 래퍼
    // -------------------------------------------------------------------------

    @Test
    void isAdapterPass_notInMap_returnsTrue() {
        assertTrue(manager.isAdapterPass("UNKNOWN"));
    }

    @Test
    void isAdapterPass_pass_returnsTrue() {
        Map<String, DualCustomBucket> map = new ConcurrentHashMap<>();
        map.put("ADAPTER_A", passBucket("ADAPTER_A"));
        injectAdapterMap(map);

        assertTrue(manager.isAdapterPass("ADAPTER_A"));
    }

    @Test
    void isAdapterPass_blocked_returnsFalse() {
        Map<String, DualCustomBucket> map = new ConcurrentHashMap<>();
        map.put("ADAPTER_A", blockedPerSecondBucket("ADAPTER_A"));
        injectAdapterMap(map);

        assertFalse(manager.isAdapterPass("ADAPTER_A"));
    }

    // -------------------------------------------------------------------------
    // isInterfacePass — boolean 래퍼
    // -------------------------------------------------------------------------

    @Test
    void isInterfacePass_notInMap_returnsTrue() {
        assertTrue(manager.isInterfacePass("UNKNOWN"));
    }

    @Test
    void isInterfacePass_pass_returnsTrue() {
        Map<String, DualCustomBucket> map = new ConcurrentHashMap<>();
        map.put("IF_001", passBucket("IF_001"));
        injectInterfaceMap(map);

        assertTrue(manager.isInterfacePass("IF_001"));
    }

    @Test
    void isInterfacePass_blocked_returnsFalse() {
        Map<String, DualCustomBucket> map = new ConcurrentHashMap<>();
        map.put("IF_001", blockedThresholdBucket("IF_001"));
        injectInterfaceMap(map);

        assertFalse(manager.isInterfacePass("IF_001"));
    }

    // -------------------------------------------------------------------------
    // 모니터링 접근자
    // -------------------------------------------------------------------------

    @Test
    void getAdapterBucket_returnsCorrectBucket() {
        Map<String, DualCustomBucket> map = new ConcurrentHashMap<>();
        DualCustomBucket expected = passBucket("ADAPTER_A");
        map.put("ADAPTER_A", expected);
        injectAdapterMap(map);

        assertSame(expected, manager.getAdapterBucket("ADAPTER_A"));
        assertNull(manager.getAdapterBucket("UNKNOWN"));
    }

    @Test
    void getInterfaceBucket_returnsCorrectBucket() {
        Map<String, DualCustomBucket> map = new ConcurrentHashMap<>();
        DualCustomBucket expected = passBucket("IF_001");
        map.put("IF_001", expected);
        injectInterfaceMap(map);

        assertSame(expected, manager.getInterfaceBucket("IF_001"));
        assertNull(manager.getInterfaceBucket("UNKNOWN"));
    }

    @Test
    void getAdapterBucketMap_containsInjectedEntries() {
        Map<String, DualCustomBucket> injected = new ConcurrentHashMap<>();
        injected.put("ADAPTER_A", passBucket("ADAPTER_A"));
        injectAdapterMap(injected);

        assertTrue(manager.getAdapterBucketMap().containsKey("ADAPTER_A"));
    }
}
