package com.eactive.eai.custom.inflow;

import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
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 static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import java.util.List;

import org.junit.jupiter.api.Test;

import com.eactive.eai.common.inflow.InflowTargetVO;
import com.eactive.eai.common.inflow.dual.DualInflowControlManager;
import com.eactive.eai.manage.inflow.TargetMetricDTO;

class ClientInflowTargetMetricServiceTest {

    private static ClientInflowTargetMetricService serviceWith(ClientDualInflowControlManager manager) {
        return new ClientInflowTargetMetricService() {
            @Override
            protected ClientDualInflowControlManager getClientDualManager() { return manager; }
        };
    }

    private InflowTargetVO makeVO(String name) {
        InflowTargetVO vo = new InflowTargetVO();
        vo.setName(name);
        vo.setActivate(true);
        return vo;
    }

    private DualInflowControlManager.TargetMetrics makeMetrics(long allowed, long rejPs, long rejTh) {
        DualInflowControlManager.TargetMetrics m = new DualInflowControlManager.TargetMetrics();
        m.allowed.set(allowed);
        m.rejectedPerSecond.set(rejPs);
        m.rejectedThreshold.set(rejTh);
        return m;
    }

    // =========================================================================
    // null 관리자 (비활성 환경)
    // =========================================================================

    @Test
    void getClientMetric_managerIsNull_returnsNull() {
        assertNull(serviceWith(null).getClientMetric("C1"));
    }

    @Test
    void getAllClientMetrics_managerIsNull_returnsEmptyList() {
        assertTrue(serviceWith(null).getAllClientMetrics().isEmpty());
    }

    @Test
    void resetClientMetric_managerIsNull_returnsFalse() {
        assertFalse(serviceWith(null).resetClientMetric("C1"));
    }

    @Test
    void resetAllClientMetrics_managerIsNull_noException() {
        assertDoesNotThrow(() -> serviceWith(null).resetAllClientMetrics());
    }

    // =========================================================================
    // 단일 조회
    // =========================================================================

    @Test
    void getClientMetric_found_mapsFields() {
        ClientDualInflowControlManager manager = mock(ClientDualInflowControlManager.class);
        when(manager.getClientInflowThreshold("C1")).thenReturn(makeVO("C1"));
        when(manager.getClientMetrics("C1")).thenReturn(makeMetrics(200, 30, 20));

        TargetMetricDTO dto = serviceWith(manager).getClientMetric("C1");

        assertNotNull(dto);
        assertEquals("C1",     dto.getTargetId());
        assertEquals("CLIENT", dto.getTargetType());
        assertTrue(dto.isActivate());
        assertEquals(200, dto.getAllowed());
        assertEquals(30,  dto.getRejectedPerSecond());
        assertEquals(20,  dto.getRejectedThreshold());
        assertEquals(250, dto.getTotalRequests());
    }

    @Test
    void getClientMetric_notRegistered_returnsNull() {
        ClientDualInflowControlManager manager = mock(ClientDualInflowControlManager.class);
        when(manager.getClientInflowThreshold("C1")).thenReturn(null);

        assertNull(serviceWith(manager).getClientMetric("C1"));
    }

    @Test
    void getClientMetric_noMetricsYet_returnsZeroCounters() {
        ClientDualInflowControlManager manager = mock(ClientDualInflowControlManager.class);
        when(manager.getClientInflowThreshold("C1")).thenReturn(makeVO("C1"));
        when(manager.getClientMetrics("C1")).thenReturn(null);

        TargetMetricDTO dto = serviceWith(manager).getClientMetric("C1");

        assertNotNull(dto);
        assertEquals(0,   dto.getAllowed());
        assertEquals(0,   dto.getTotalRequests());
        assertEquals(0.0, dto.getRejectRatio(), 0.001);
    }

    @Test
    void getClientMetric_rejectRatioCalculation() {
        ClientDualInflowControlManager manager = mock(ClientDualInflowControlManager.class);
        when(manager.getClientInflowThreshold("C1")).thenReturn(makeVO("C1"));
        // allowed=90, rejPs=5, rejTh=5 → reject=10/100 = 10.00%
        when(manager.getClientMetrics("C1")).thenReturn(makeMetrics(90, 5, 5));

        TargetMetricDTO dto = serviceWith(manager).getClientMetric("C1");

        assertEquals(10.0, dto.getRejectRatio(), 0.01);
    }

    // =========================================================================
    // 전체 조회
    // =========================================================================

    @Test
    void getAllClientMetrics_twoEntries_returnsBoth() {
        ClientDualInflowControlManager manager = mock(ClientDualInflowControlManager.class);
        java.util.Map<String, com.eactive.eai.common.inflow.dual.DualCustomBucket> bucketMap =
            new java.util.LinkedHashMap<>();
        bucketMap.put("C1", null);
        bucketMap.put("C2", null);
        when(manager.getClientBucketMap()).thenReturn(bucketMap);
        when(manager.getClientInflowThreshold("C1")).thenReturn(makeVO("C1"));
        when(manager.getClientInflowThreshold("C2")).thenReturn(makeVO("C2"));
        when(manager.getClientMetrics("C1")).thenReturn(makeMetrics(10, 0, 0));
        when(manager.getClientMetrics("C2")).thenReturn(makeMetrics(20, 5, 0));

        List<TargetMetricDTO> result = serviceWith(manager).getAllClientMetrics();

        assertEquals(2, result.size());
    }

    @Test
    void getAllClientMetrics_noKeys_returnsEmptyList() {
        ClientDualInflowControlManager manager = mock(ClientDualInflowControlManager.class);
        when(manager.getClientBucketMap()).thenReturn(new java.util.HashMap<>());

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

    // =========================================================================
    // reset
    // =========================================================================

    @Test
    void resetClientMetric_success_returnsTrueAndCallsManager() {
        ClientDualInflowControlManager manager = mock(ClientDualInflowControlManager.class);
        when(manager.getClientInflowThreshold("C1")).thenReturn(makeVO("C1"));

        assertTrue(serviceWith(manager).resetClientMetric("C1"));
        verify(manager).resetClientMetrics("C1");
    }

    @Test
    void resetClientMetric_notRegistered_returnsFalseAndNoReset() {
        ClientDualInflowControlManager manager = mock(ClientDualInflowControlManager.class);
        when(manager.getClientInflowThreshold("C1")).thenReturn(null);

        assertFalse(serviceWith(manager).resetClientMetric("C1"));
        verify(manager, never()).resetClientMetrics(any());
    }

    @Test
    void resetAllClientMetrics_callsManager() {
        ClientDualInflowControlManager manager = mock(ClientDualInflowControlManager.class);
        serviceWith(manager).resetAllClientMetrics();
        verify(manager).resetAllClientMetrics();
    }
}
