package com.eactive.eai.custom.inbound.processor;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

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 com.eactive.eai.common.inflow.dual.DualCustomBucket;
import com.eactive.eai.common.inflow.dual.DualInflowControlManager;
import com.eactive.eai.custom.inflow.ClientDualInflowControlManager;

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

/**
 * ClientDualInflowControlManager 클라이언트 메트릭 단위 테스트.
 */
class ClientDualInflowControlManagerMetricsTest {

    private ClientDualInflowControlManager manager;

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

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

    private InflowTargetVO makeTargetVO(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 stableBucket(long capacity) {
        return Bucket4j.builder()
                .addLimit(Bandwidth.simple(capacity, Duration.ofHours(1)))
                .build();
    }

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

    private DualCustomBucket passDualBucket(String name) {
        return new DualCustomBucket(stableBucket(100), null, makeTargetVO(name));
    }

    private DualCustomBucket blockedPerSecondDualBucket(String name) {
        return new DualCustomBucket(exhaustedBucket(1), null, makeTargetVO(name));
    }

    private DualCustomBucket blockedThresholdDualBucket(String name) {
        return new DualCustomBucket(stableBucket(100), exhaustedBucket(1), makeTargetVO(name));
    }

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

    // =========================================================================
    // isClientPassDetail() — 카운터 증가
    // =========================================================================

    @Test
    void isClientPassDetail_bucketNotRegistered_noMetricsCreated() {
        manager.isClientPassDetail("UNKNOWN");

        assertNull(manager.getClientMetrics("UNKNOWN"));
    }

    @Test
    void isClientPassDetail_pass_incrementsAllowed() {
        Map<String, DualCustomBucket> map = new ConcurrentHashMap<>();
        map.put("C1", passDualBucket("C1"));
        injectClientMap(map);

        manager.isClientPassDetail("C1");

        assertEquals(1, manager.getClientMetrics("C1").allowed.get());
    }

    @Test
    void isClientPassDetail_blockedThreshold_incrementsRejectedThreshold() {
        Map<String, DualCustomBucket> map = new ConcurrentHashMap<>();
        map.put("C1", blockedThresholdDualBucket("C1"));
        injectClientMap(map);

        manager.isClientPassDetail("C1");

        assertEquals(1, manager.getClientMetrics("C1").rejectedThreshold.get());
    }

    @Test
    void isClientPassDetail_blockedPerSecond_incrementsRejectedPerSecond() {
        Map<String, DualCustomBucket> map = new ConcurrentHashMap<>();
        map.put("C1", blockedPerSecondDualBucket("C1"));
        injectClientMap(map);

        manager.isClientPassDetail("C1");

        assertEquals(1, manager.getClientMetrics("C1").rejectedPerSecond.get());
    }

    // =========================================================================
    // getAllClientMetrics — 조회 및 불변성
    // =========================================================================

    @Test
    void getAllClientMetrics_empty_returnsEmptyMap() {
        assertTrue(manager.getAllClientMetrics().isEmpty());
    }

    @Test
    void getAllClientMetrics_afterCalls_containsEntries() {
        Map<String, DualCustomBucket> map = new ConcurrentHashMap<>();
        map.put("C1", passDualBucket("C1"));
        map.put("C2", passDualBucket("C2"));
        injectClientMap(map);

        manager.isClientPassDetail("C1");
        manager.isClientPassDetail("C2");

        Map<String, DualInflowControlManager.TargetMetrics> all = manager.getAllClientMetrics();
        assertEquals(2, all.size());
        assertTrue(all.containsKey("C1"));
        assertTrue(all.containsKey("C2"));
    }

    // =========================================================================
    // resetClientMetrics / resetAllClientMetrics
    // =========================================================================

    @Test
    void resetClientMetrics_resetsTargetClient() {
        Map<String, DualCustomBucket> map = new ConcurrentHashMap<>();
        map.put("C1", passDualBucket("C1"));
        injectClientMap(map);

        manager.isClientPassDetail("C1");
        manager.resetClientMetrics("C1");

        assertEquals(0, manager.getClientMetrics("C1").allowed.get());
    }

    @Test
    void resetAllClientMetrics_resetsAll() {
        Map<String, DualCustomBucket> map = new ConcurrentHashMap<>();
        map.put("C1", passDualBucket("C1"));
        map.put("C2", passDualBucket("C2"));
        injectClientMap(map);

        manager.isClientPassDetail("C1");
        manager.isClientPassDetail("C2");
        manager.resetAllClientMetrics();

        assertEquals(0, manager.getClientMetrics("C1").allowed.get());
        assertEquals(0, manager.getClientMetrics("C2").allowed.get());
    }

    // =========================================================================
    // removeClient — 메트릭 자동 삭제
    // =========================================================================

    @Test
    void removeClient_cleansUpMetrics() {
        Map<String, DualCustomBucket> map = new ConcurrentHashMap<>();
        map.put("C1", passDualBucket("C1"));
        injectClientMap(map);

        manager.isClientPassDetail("C1");
        assertNotNull(manager.getClientMetrics("C1"));

        manager.removeClient("C1");

        assertNull(manager.getClientMetrics("C1"));
    }
}
