package com.eactive.eai.manage.inflow;

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

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

import org.junit.jupiter.api.Test;

import com.eactive.eai.common.inflow.InflowTargetVO;
import com.eactive.eai.common.inflow.dual.DualCustomBucket;
import com.eactive.eai.common.inflow.dual.DualInflowControlManager;

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

/**
 * InflowTargetBucketService 단위 테스트.
 *
 * <p>어댑터/인터페이스: getDualManager()를 오버라이드하는 서브클래스 사용.
 * 클라이언트 관련 테스트는 ClientInflowTargetBucketServiceTest 에서 검증한다.
 */
class InflowTargetBucketServiceTest {

    // -------------------------------------------------------------------------
    // 테스트용 서브클래스
    // -------------------------------------------------------------------------

    private static InflowTargetBucketService serviceWith(DualInflowControlManager manager) {
        return new InflowTargetBucketService() {
            @Override
            protected DualInflowControlManager getDualManager() { return manager; }
        };
    }

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

    private InflowTargetVO makeVO(String name, long perSec, long threshold, String unit, boolean activate) {
        InflowTargetVO vo = new InflowTargetVO();
        vo.setName(name);
        vo.setThresholdPerSecond(perSec);
        vo.setThreshold(threshold);
        vo.setThresholdTimeUnit(unit);
        vo.setActivate(activate);
        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 makeDualBucket(String name, long perSec, long threshold) {
        return new DualCustomBucket(freshBucket(perSec), freshBucket(threshold),
                makeVO(name, perSec, threshold, "DAY", true));
    }

    // =========================================================================
    // DualManager null (비활성 환경)
    // =========================================================================

    @Test
    void getAdapterBucketStatus_managerIsNull_returnsNull() {
        assertNull(serviceWith(null).getAdapterBucketStatus("ADAPTER_A"));
    }

    @Test
    void getAllAdapterBucketStatus_managerIsNull_returnsEmptyList() {
        List<TargetBucketStatusDTO> result = serviceWith(null).getAllAdapterBucketStatus();
        assertNotNull(result);
        assertTrue(result.isEmpty());
    }

    @Test
    void getInterfaceBucketStatus_managerIsNull_returnsNull() {
        assertNull(serviceWith(null).getInterfaceBucketStatus("IF_001"));
    }

    @Test
    void getAllInterfaceBucketStatus_managerIsNull_returnsEmptyList() {
        assertTrue(serviceWith(null).getAllInterfaceBucketStatus().isEmpty());
    }

    // =========================================================================
    // 버킷 미존재
    // =========================================================================

    @Test
    void getAdapterBucketStatus_notFound_returnsNull() {
        DualInflowControlManager manager = mock(DualInflowControlManager.class);
        when(manager.getAdapterBucket("ADAPTER_A")).thenReturn(null);

        assertNull(serviceWith(manager).getAdapterBucketStatus("ADAPTER_A"));
    }

    @Test
    void getInterfaceBucketStatus_notFound_returnsNull() {
        DualInflowControlManager manager = mock(DualInflowControlManager.class);
        when(manager.getInterfaceBucket("IF_001")).thenReturn(null);

        assertNull(serviceWith(manager).getInterfaceBucketStatus("IF_001"));
    }

    // =========================================================================
    // 단일 조회 — targetType, targetId 매핑
    // =========================================================================

    @Test
    void getAdapterBucketStatus_found_mapsAdapterType() {
        DualInflowControlManager manager = mock(DualInflowControlManager.class);
        when(manager.getAdapterBucket("ADAPTER_A")).thenReturn(makeDualBucket("ADAPTER_A", 10, 1000));

        TargetBucketStatusDTO dto = serviceWith(manager).getAdapterBucketStatus("ADAPTER_A");

        assertNotNull(dto);
        assertEquals("ADAPTER_A", dto.getTargetId());
        assertEquals("ADAPTER", dto.getTargetType());
        assertTrue(dto.isActivate());
        assertEquals(2, dto.getBuckets().size());
    }

    @Test
    void getInterfaceBucketStatus_found_mapsInterfaceType() {
        DualInflowControlManager manager = mock(DualInflowControlManager.class);
        when(manager.getInterfaceBucket("IF_001")).thenReturn(makeDualBucket("IF_001", 20, 2000));

        TargetBucketStatusDTO dto = serviceWith(manager).getInterfaceBucketStatus("IF_001");

        assertNotNull(dto);
        assertEquals("IF_001", dto.getTargetId());
        assertEquals("INTERFACE", dto.getTargetType());
        assertTrue(dto.isActivate());
        assertEquals(2, dto.getBuckets().size());
    }

    // =========================================================================
    // BucketInfo 용량/시간단위 매핑
    // =========================================================================

    @Test
    void toDto_capacity_perSecondBucketInfoCapacityMatchesVO() {
        DualInflowControlManager manager = mock(DualInflowControlManager.class);
        when(manager.getAdapterBucket("A1")).thenReturn(makeDualBucket("A1", 30, 3000));

        TargetBucketStatusDTO dto = serviceWith(manager).getAdapterBucketStatus("A1");

        GroupBucketStatusDTO.BucketInfo ps = dto.getBuckets().get(0);
        assertEquals(30, ps.getCapacity());
        assertEquals("SEC", ps.getTimeUnit());

        GroupBucketStatusDTO.BucketInfo th = dto.getBuckets().get(1);
        assertEquals(3000, th.getCapacity());
        assertEquals("DAY", th.getTimeUnit());
    }

    // =========================================================================
    // 전체 목록 조회
    // =========================================================================

    @Test
    void getAllAdapterBucketStatus_emptyMap_returnsEmptyList() {
        DualInflowControlManager manager = mock(DualInflowControlManager.class);
        when(manager.getAdapterBucketMap()).thenReturn(new ConcurrentHashMap<>());

        assertTrue(serviceWith(manager).getAllAdapterBucketStatus().isEmpty());
    }

    @Test
    void getAllInterfaceBucketStatus_oneEntry_targetTypeIsInterface() {
        DualInflowControlManager manager = mock(DualInflowControlManager.class);
        Map<String, DualCustomBucket> map = new ConcurrentHashMap<>();
        map.put("IF_001", makeDualBucket("IF_001", 10, 1000));
        when(manager.getInterfaceBucketMap()).thenReturn(map);

        List<TargetBucketStatusDTO> result = serviceWith(manager).getAllInterfaceBucketStatus();

        assertEquals(1, result.size());
        assertEquals("INTERFACE", result.get(0).getTargetType());
        assertEquals("IF_001", result.get(0).getTargetId());
    }
}
