package com.eactive.eai.manage.inflow;

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

import java.util.List;

import org.junit.jupiter.api.Test;

import com.eactive.eai.common.inflow.InflowGroupVO;
import com.eactive.eai.common.inflow.dual.DualInflowControlManager;

/**
 * InflowGroupMetricService 단위 테스트.
 *
 * <p>getManager()를 protected로 오버라이드하는 내부 테스트 서브클래스를 사용하여
 * mockito-inline(static mock) 없이 DualInflowControlManager 의존성을 주입한다.
 */
class InflowGroupMetricServiceTest {

    // -------------------------------------------------------------------------
    // 테스트용 서브클래스 — getManager()를 오버라이드하여 mock 주입
    // -------------------------------------------------------------------------

    private static InflowGroupMetricService serviceWith(DualInflowControlManager manager) {
        return new InflowGroupMetricService() {
            @Override
            protected DualInflowControlManager getManager() {
                return manager;
            }
        };
    }

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

    private InflowGroupVO makeGroupVO(String groupId, String groupName) {
        InflowGroupVO vo = new InflowGroupVO();
        vo.setGroupId(groupId);
        vo.setGroupName(groupName);
        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;
    }

    // =========================================================================
    // getGroupMetric
    // =========================================================================

    @Test
    void getGroupMetric_managerIsNull_returnsNull() {
        assertNull(serviceWith(null).getGroupMetric("G1"));
    }

    @Test
    void getGroupMetric_groupNotFound_returnsNull() {
        DualInflowControlManager manager = mock(DualInflowControlManager.class);
        when(manager.getGroupInflowThreshold("G1")).thenReturn(null);

        assertNull(serviceWith(manager).getGroupMetric("G1"));
    }

    @Test
    void getGroupMetric_noMetricsYet_returnsZeroCounters() {
        DualInflowControlManager manager = mock(DualInflowControlManager.class);
        when(manager.getGroupInflowThreshold("G1")).thenReturn(makeGroupVO("G1", "결제그룹"));
        when(manager.getGroupMetrics("G1")).thenReturn(null);

        GroupMetricDTO dto = serviceWith(manager).getGroupMetric("G1");

        assertNotNull(dto);
        assertEquals("G1", dto.getGroupId());
        assertEquals("결제그룹", dto.getGroupName());
        assertTrue(dto.isActivate());
        assertEquals(0, dto.getAllowed());
        assertEquals(0, dto.getRejectedPerSecond());
        assertEquals(0, dto.getRejectedThreshold());
        assertEquals(0, dto.getTotalRequests());
        assertEquals(0.0, dto.getRejectRatio());
        assertEquals(0, dto.getLastResetTime());
    }

    @Test
    void getGroupMetric_withMetrics_returnsCorrectValues() {
        DualInflowControlManager manager = mock(DualInflowControlManager.class);
        when(manager.getGroupInflowThreshold("G1")).thenReturn(makeGroupVO("G1", "결제그룹"));
        when(manager.getGroupMetrics("G1")).thenReturn(makeMetrics(900, 60, 40));

        GroupMetricDTO dto = serviceWith(manager).getGroupMetric("G1");

        assertEquals(900, dto.getAllowed());
        assertEquals(60, dto.getRejectedPerSecond());
        assertEquals(40, dto.getRejectedThreshold());
        assertEquals(1000, dto.getTotalRequests());
    }

    @Test
    void getGroupMetric_rejectRatioCalculation() {
        DualInflowControlManager manager = mock(DualInflowControlManager.class);
        when(manager.getGroupInflowThreshold("G1")).thenReturn(makeGroupVO("G1", "G1"));
        // allowed=90, rejPs=5, rejTh=5 → reject=10/100 = 10.00%
        when(manager.getGroupMetrics("G1")).thenReturn(makeMetrics(90, 5, 5));

        GroupMetricDTO dto = serviceWith(manager).getGroupMetric("G1");

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

    @Test
    void getGroupMetric_zeroTotal_rejectRatioIsZero() {
        DualInflowControlManager manager = mock(DualInflowControlManager.class);
        when(manager.getGroupInflowThreshold("G1")).thenReturn(makeGroupVO("G1", "G1"));
        when(manager.getGroupMetrics("G1")).thenReturn(makeMetrics(0, 0, 0));

        GroupMetricDTO dto = serviceWith(manager).getGroupMetric("G1");

        assertEquals(0.0, dto.getRejectRatio(), 0.0001);
    }

    @Test
    void getGroupMetric_lastResetTimeIsPropagated() {
        DualInflowControlManager.TargetMetrics m = makeMetrics(10, 0, 0);
        m.lastResetTime = 1234567890L;

        DualInflowControlManager manager = mock(DualInflowControlManager.class);
        when(manager.getGroupInflowThreshold("G1")).thenReturn(makeGroupVO("G1", "G1"));
        when(manager.getGroupMetrics("G1")).thenReturn(m);

        GroupMetricDTO dto = serviceWith(manager).getGroupMetric("G1");

        assertEquals(1234567890L, dto.getLastResetTime());
    }

    // =========================================================================
    // getAllGroupMetrics
    // =========================================================================

    @Test
    void getAllGroupMetrics_managerIsNull_returnsEmptyList() {
        List<GroupMetricDTO> result = serviceWith(null).getAllGroupMetrics();

        assertNotNull(result);
        assertTrue(result.isEmpty());
    }

    @Test
    void getAllGroupMetrics_noGroups_returnsEmptyList() {
        DualInflowControlManager manager = mock(DualInflowControlManager.class);
        when(manager.getGroupAllKeys()).thenReturn(new String[]{});

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

    @Test
    void getAllGroupMetrics_twoGroups_returnsBoth() {
        DualInflowControlManager manager = mock(DualInflowControlManager.class);
        when(manager.getGroupAllKeys()).thenReturn(new String[]{"G1", "G2"});
        when(manager.getGroupInflowThreshold("G1")).thenReturn(makeGroupVO("G1", "그룹1"));
        when(manager.getGroupInflowThreshold("G2")).thenReturn(makeGroupVO("G2", "그룹2"));
        when(manager.getGroupMetrics("G1")).thenReturn(makeMetrics(10, 0, 0));
        when(manager.getGroupMetrics("G2")).thenReturn(makeMetrics(20, 5, 0));

        List<GroupMetricDTO> result = serviceWith(manager).getAllGroupMetrics();

        assertEquals(2, result.size());
        assertEquals("G1", result.get(0).getGroupId());
        assertEquals("G2", result.get(1).getGroupId());
    }

    @Test
    void getAllGroupMetrics_groupNotFoundIsSkipped() {
        DualInflowControlManager manager = mock(DualInflowControlManager.class);
        when(manager.getGroupAllKeys()).thenReturn(new String[]{"G1", "GHOST"});
        when(manager.getGroupInflowThreshold("G1")).thenReturn(makeGroupVO("G1", "그룹1"));
        when(manager.getGroupInflowThreshold("GHOST")).thenReturn(null);
        when(manager.getGroupMetrics("G1")).thenReturn(makeMetrics(5, 0, 0));

        List<GroupMetricDTO> result = serviceWith(manager).getAllGroupMetrics();

        assertEquals(1, result.size());
        assertEquals("G1", result.get(0).getGroupId());
    }

    // =========================================================================
    // resetGroupMetric
    // =========================================================================

    @Test
    void resetGroupMetric_managerIsNull_returnsFalse() {
        assertFalse(serviceWith(null).resetGroupMetric("G1"));
    }

    @Test
    void resetGroupMetric_groupNotFound_returnsFalse() {
        DualInflowControlManager manager = mock(DualInflowControlManager.class);
        when(manager.getGroupInflowThreshold("G1")).thenReturn(null);

        assertFalse(serviceWith(manager).resetGroupMetric("G1"));
        verify(manager, never()).resetGroupMetrics(any());
    }

    @Test
    void resetGroupMetric_success_returnsTrueAndCallsManager() {
        DualInflowControlManager manager = mock(DualInflowControlManager.class);
        when(manager.getGroupInflowThreshold("G1")).thenReturn(makeGroupVO("G1", "그룹1"));

        assertTrue(serviceWith(manager).resetGroupMetric("G1"));
        verify(manager).resetGroupMetrics("G1");
    }

    // =========================================================================
    // resetAllGroupMetrics
    // =========================================================================

    @Test
    void resetAllGroupMetrics_managerIsNull_noException() {
        assertDoesNotThrow(() -> serviceWith(null).resetAllGroupMetrics());
    }

    @Test
    void resetAllGroupMetrics_callsManagerReset() {
        DualInflowControlManager manager = mock(DualInflowControlManager.class);

        serviceWith(manager).resetAllGroupMetrics();

        verify(manager).resetAllGroupMetrics();
    }
}
