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

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

import java.time.Duration;

import org.junit.jupiter.api.Test;

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

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

class DualCustomBucketTest {

    private InflowTargetVO makeVO(String name, long perSec, long threshold, String unit) {
        InflowTargetVO vo = new InflowTargetVO();
        vo.setName(name);
        vo.setThresholdPerSecond(perSec);
        vo.setThreshold(threshold);
        vo.setThresholdTimeUnit(unit);
        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;
    }

    // -------------------------------------------------------------------------
    // tryConsume — 통과 케이스
    // -------------------------------------------------------------------------

    @Test
    void bothBucketsNull_returnsPass() {
        DualCustomBucket bucket = new DualCustomBucket(null, null, makeVO("A", 0, 0, null));
        assertNull(bucket.tryConsume());
    }

    @Test
    void onlyPerSecond_withinLimit_returnsPass() {
        DualCustomBucket bucket = new DualCustomBucket(freshBucket(5), null, makeVO("A", 5, 0, null));
        assertNull(bucket.tryConsume());
    }

    @Test
    void onlyThreshold_withinLimit_returnsPass() {
        DualCustomBucket bucket = new DualCustomBucket(null, freshBucket(100), makeVO("A", 0, 100, "MIN"));
        assertNull(bucket.tryConsume());
    }

    @Test
    void bothBuckets_bothWithinLimit_returnsPass() {
        DualCustomBucket bucket = new DualCustomBucket(freshBucket(10), freshBucket(100), makeVO("A", 10, 100, "MIN"));
        assertNull(bucket.tryConsume());
    }

    // -------------------------------------------------------------------------
    // tryConsume — 차단 케이스
    // -------------------------------------------------------------------------

    @Test
    void onlyPerSecond_exceeded_returnsBlockedPerSecond() {
        DualCustomBucket bucket = new DualCustomBucket(exhaustedBucket(1), null, makeVO("A", 1, 0, null));
        assertEquals(DualCustomBucket.RESULT_BLOCKED_PER_SECOND, bucket.tryConsume());
    }

    @Test
    void onlyThreshold_exceeded_returnsBlockedThreshold() {
        DualCustomBucket bucket = new DualCustomBucket(null, exhaustedBucket(1), makeVO("A", 0, 1, "MIN"));
        assertEquals(DualCustomBucket.RESULT_BLOCKED_THRESHOLD, bucket.tryConsume());
    }

    @Test
    void perSecondExceeded_thresholdRemaining_returnsBlockedPerSecond() {
        DualCustomBucket bucket = new DualCustomBucket(exhaustedBucket(1), freshBucket(100), makeVO("A", 1, 100, "MIN"));
        assertEquals(DualCustomBucket.RESULT_BLOCKED_PER_SECOND, bucket.tryConsume());
    }

    @Test
    void perSecondRemaining_thresholdExceeded_returnsBlockedThreshold() {
        DualCustomBucket bucket = new DualCustomBucket(freshBucket(10), exhaustedBucket(1), makeVO("A", 10, 1, "MIN"));
        assertEquals(DualCustomBucket.RESULT_BLOCKED_THRESHOLD, bucket.tryConsume());
    }

    @Test
    void bothExceeded_returnsBlockedPerSecond() {
        DualCustomBucket bucket = new DualCustomBucket(exhaustedBucket(1), exhaustedBucket(1), makeVO("A", 1, 1, "MIN"));
        assertEquals(DualCustomBucket.RESULT_BLOCKED_PER_SECOND, bucket.tryConsume());
    }

    // -------------------------------------------------------------------------
    // tryConsume — 연속 호출
    // -------------------------------------------------------------------------

    @Test
    void consecutiveConsumes_blockedAfterCapacityExhausted() {
        DualCustomBucket bucket = new DualCustomBucket(freshBucket(3), null, makeVO("A", 3, 0, null));
        assertNull(bucket.tryConsume());
        assertNull(bucket.tryConsume());
        assertNull(bucket.tryConsume());
        assertEquals(DualCustomBucket.RESULT_BLOCKED_PER_SECOND, bucket.tryConsume());
    }

    @Test
    void bothBuckets_thresholdExhaustedAfterMultiplePasses() {
        DualCustomBucket bucket = new DualCustomBucket(freshBucket(10), freshBucket(3), makeVO("A", 10, 3, "MIN"));
        assertNull(bucket.tryConsume());
        assertNull(bucket.tryConsume());
        assertNull(bucket.tryConsume());
        assertEquals(DualCustomBucket.RESULT_BLOCKED_THRESHOLD, bucket.tryConsume());
    }

    // -------------------------------------------------------------------------
    // 가용 토큰 수 조회
    // -------------------------------------------------------------------------

    @Test
    void nullBuckets_availableTokensReturnMinusOne() {
        DualCustomBucket bucket = new DualCustomBucket(null, null, makeVO("A", 0, 0, null));
        assertEquals(-1, bucket.getPerSecondAvailableTokens());
        assertEquals(-1, bucket.getThresholdAvailableTokens());
    }

    @Test
    void availableTokensReflectInitialCapacity() {
        DualCustomBucket bucket = new DualCustomBucket(freshBucket(5), freshBucket(100), makeVO("A", 5, 100, "MIN"));
        assertEquals(5, bucket.getPerSecondAvailableTokens());
        assertEquals(100, bucket.getThresholdAvailableTokens());
    }

    @Test
    void availableTokensDecrementAfterEachConsume() {
        DualCustomBucket bucket = new DualCustomBucket(freshBucket(5), freshBucket(10), makeVO("A", 5, 10, "MIN"));
        bucket.tryConsume();
        assertEquals(4, bucket.getPerSecondAvailableTokens());
        assertEquals(9, bucket.getThresholdAvailableTokens());
        bucket.tryConsume();
        assertEquals(3, bucket.getPerSecondAvailableTokens());
        assertEquals(8, bucket.getThresholdAvailableTokens());
    }

    @Test
    void perSecondBlocked_thresholdTokenNotConsumed() {
        DualCustomBucket bucket = new DualCustomBucket(exhaustedBucket(1), freshBucket(10), makeVO("A", 1, 10, "MIN"));
        long thresholdBefore = bucket.getThresholdAvailableTokens();
        bucket.tryConsume();
        assertEquals(thresholdBefore, bucket.getThresholdAvailableTokens());
    }

    @Test
    void resultPassConstant_isNull() {
        assertNull(DualCustomBucket.RESULT_PASS);
    }
}
