package com.eactive.eai.manage.session;

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

import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.List;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import com.eactive.eai.common.session.CacheInfoVO;
import com.eactive.eai.common.session.SessionManager;

/**
 * SessionCacheManageService 단위 테스트.
 *
 * <p>SessionManager.getInstance()는 static singleton(private static instance 필드)이라
 * 실제 Ignite/Ehcache를 기동하지 않고, 리플렉션으로 그 필드를 Mockito mock으로 교체하여 검증한다.
 * 테스트가 끝나면 반드시 필드를 원복(null)하여 다른 테스트에 영향을 주지 않는다.
 */
class SessionCacheManageServiceTest {

    private SessionManager mockSessionManager;
    private final SessionCacheManageService service = new SessionCacheManageService();

    @BeforeEach
    void setUp() throws Exception {
        mockSessionManager = mock(SessionManager.class);
        setInstance(mockSessionManager);
    }

    @AfterEach
    void tearDown() throws Exception {
        setInstance(null);
    }

    private void setInstance(SessionManager manager) throws Exception {
        Field field = SessionManager.class.getDeclaredField("instance");
        field.setAccessible(true);
        field.set(null, manager);
    }

    private CacheInfoVO makeCacheInfo(String name, int size) {
        CacheInfoVO vo = new CacheInfoVO();
        vo.setName(name);
        vo.setSize(size);
        vo.setKeys(Arrays.asList("k1", "k2"));
        vo.setTimeToLive(60000);
        return vo;
    }

    // =========================================================================
    // 1. 단건 위임
    // =========================================================================

    @Test
    @DisplayName("1-1. getCacheStatus() — SessionManager.getCacheStatus() 위임")
    void testGetCacheStatus_delegates() {
        when(mockSessionManager.getCacheStatus()).thenReturn("<Caches type=\"Ignite\"></Caches>");

        assertEquals("<Caches type=\"Ignite\"></Caches>", service.getCacheStatus());
        verify(mockSessionManager).getCacheStatus();
    }

    @Test
    @DisplayName("1-2. getMasterInstId() — SessionManager.getMasterInstId() 위임")
    void testGetMasterInstId_delegates() {
        when(mockSessionManager.getMasterInstId()).thenReturn("SERVER01");

        assertEquals("SERVER01", service.getMasterInstId());
    }

    @Test
    @DisplayName("1-3. getLoginCache() — 위임")
    void testGetLoginCache_delegates() {
        CacheInfoVO vo = makeCacheInfo("LoginCache", 2);
        when(mockSessionManager.getLoginCache()).thenReturn(vo);

        assertSame(vo, service.getLoginCache());
    }

    @Test
    @DisplayName("1-4. getHttpLoginCache() — 위임")
    void testGetHttpLoginCache_delegates() {
        CacheInfoVO vo = makeCacheInfo("HttpLoginCache", 1);
        when(mockSessionManager.getHttpLoginCache()).thenReturn(vo);

        assertSame(vo, service.getHttpLoginCache());
    }

    @Test
    @DisplayName("1-5. getEvictMasterCache() — 위임")
    void testGetEvictMasterCache_delegates() {
        CacheInfoVO vo = makeCacheInfo("EvictMasterCache", 1);
        when(mockSessionManager.getEvictMasterCache()).thenReturn(vo);

        assertSame(vo, service.getEvictMasterCache());
    }

    @Test
    @DisplayName("1-6. getTerminalCache() — 위임")
    void testGetTerminalCache_delegates() {
        CacheInfoVO vo = makeCacheInfo("TerminalCache", 5);
        when(mockSessionManager.getTerminalCache()).thenReturn(vo);

        assertSame(vo, service.getTerminalCache());
    }

    @Test
    @DisplayName("1-7. getConvertUserIdCache() — 위임")
    void testGetConvertUserIdCache_delegates() {
        CacheInfoVO vo = makeCacheInfo("ConvertUserIdCache", 3);
        when(mockSessionManager.getConvertUserIdCache()).thenReturn(vo);

        assertSame(vo, service.getConvertUserIdCache());
    }

    @Test
    @DisplayName("1-8. getSocketCache() — 위임")
    void testGetSocketCache_delegates() {
        CacheInfoVO vo = makeCacheInfo("SocketCache", 4);
        when(mockSessionManager.getSocketCache()).thenReturn(vo);

        assertSame(vo, service.getSocketCache());
    }

    @Test
    @DisplayName("1-9. getOutboundAccessTokenCache() — 위임")
    void testGetOutboundAccessTokenCache_delegates() {
        CacheInfoVO vo = makeCacheInfo("OutBoundAccessToken", 7);
        when(mockSessionManager.getOutboundAccessTokenCache()).thenReturn(vo);

        assertSame(vo, service.getOutboundAccessTokenCache());
    }

    @Test
    @DisplayName("1-10. getOutboundAccessTokenCache() — 미지원 백엔드는 예외 그대로 전파")
    void testGetOutboundAccessTokenCache_unsupported_throwsException() {
        when(mockSessionManager.getOutboundAccessTokenCache()).thenThrow(new UnsupportedOperationException());

        assertThrows(UnsupportedOperationException.class, service::getOutboundAccessTokenCache);
    }

    // =========================================================================
    // 2. getAllCaches() 집계
    // =========================================================================

    @Test
    @DisplayName("2-1. getAllCaches() — OutboundAccessToken 포함 7개 캐시 반환")
    void testGetAllCaches_outboundSupported_returnsSevenCaches() {
        when(mockSessionManager.getLoginCache()).thenReturn(makeCacheInfo("LoginCache", 1));
        when(mockSessionManager.getHttpLoginCache()).thenReturn(makeCacheInfo("HttpLoginCache", 1));
        when(mockSessionManager.getEvictMasterCache()).thenReturn(makeCacheInfo("EvictMasterCache", 1));
        when(mockSessionManager.getTerminalCache()).thenReturn(makeCacheInfo("TerminalCache", 1));
        when(mockSessionManager.getConvertUserIdCache()).thenReturn(makeCacheInfo("ConvertUserIdCache", 1));
        when(mockSessionManager.getSocketCache()).thenReturn(makeCacheInfo("SocketCache", 1));
        when(mockSessionManager.getOutboundAccessTokenCache()).thenReturn(makeCacheInfo("OutBoundAccessToken", 1));

        List<CacheInfoVO> all = service.getAllCaches();

        assertEquals(7, all.size());
        assertTrue(all.stream().anyMatch(c -> "OutBoundAccessToken".equals(c.getName())));
    }

    @Test
    @DisplayName("2-2. getAllCaches() — OutboundAccessToken 미지원(Ehcache)이면 예외 없이 6개만 반환")
    void testGetAllCaches_outboundUnsupported_excludedGracefully() {
        when(mockSessionManager.getLoginCache()).thenReturn(makeCacheInfo("LoginCache", 1));
        when(mockSessionManager.getHttpLoginCache()).thenReturn(makeCacheInfo("HttpLoginCache", 1));
        when(mockSessionManager.getEvictMasterCache()).thenReturn(makeCacheInfo("EvictMasterCache", 1));
        when(mockSessionManager.getTerminalCache()).thenReturn(makeCacheInfo("TerminalCache", 1));
        when(mockSessionManager.getConvertUserIdCache()).thenReturn(makeCacheInfo("ConvertUserIdCache", 1));
        when(mockSessionManager.getSocketCache()).thenReturn(makeCacheInfo("SocketCache", 1));
        when(mockSessionManager.getOutboundAccessTokenCache()).thenThrow(new UnsupportedOperationException());

        List<CacheInfoVO> all = assertDoesNotThrow(service::getAllCaches);

        assertEquals(6, all.size());
        assertTrue(all.stream().noneMatch(c -> "OutBoundAccessToken".equals(c.getName())));
    }
}
