package com.eactive.eai.manage.crypto;

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

import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.http.ResponseEntity;
import org.springframework.test.util.ReflectionTestUtils;

import com.eactive.eai.common.security.CryptoModuleDiagnosticDTO;

/**
 * CryptoModuleManageController 단위 테스트.
 *
 * <p>Spring MVC를 기동하지 않고 컨트롤러를 직접 인스턴스화하여
 * 응답 구조(success, data, count, message)를 검증한다.
 */
class CryptoModuleManageControllerTest {

    private CryptoModuleManageController controller;
    private CryptoModuleManageService mockService;

    @BeforeEach
    void setUp() {
        controller  = new CryptoModuleManageController();
        mockService = mock(CryptoModuleManageService.class);
        ReflectionTestUtils.setField(controller, "cryptoModuleManageService", mockService);
    }

    // -------------------------------------------------------------------------
    // Helper
    // -------------------------------------------------------------------------

    private CryptoModuleDiagnosticDTO makeDTO(String name, String keySourceType) {
        CryptoModuleDiagnosticDTO dto = new CryptoModuleDiagnosticDTO();
        dto.setCryptoName(name);
        dto.setKeySourceType(keySourceType);
        dto.setAlgType("AES");
        dto.setCipherMode("CBC");
        return dto;
    }

    @SuppressWarnings("unchecked")
    private Map<String, Object> body(ResponseEntity<?> response) {
        return (Map<String, Object>) response.getBody();
    }

    // =========================================================================
    // 목록 조회 — listAll
    // =========================================================================

    @Test
    @DisplayName("listAll() — 등록된 모듈 목록과 개수 반환")
    void listAll_returnsListAndCount() {
        List<CryptoModuleDiagnosticDTO> list = Arrays.asList(
                makeDTO("MOD_A", "STATIC"),
                makeDTO("MOD_B", "DYNAMIC")
        );
        when(mockService.listAll()).thenReturn(list);

        Map<String, Object> body = body(controller.listAll());

        assertEquals(Boolean.TRUE, body.get("success"));
        assertSame(list, body.get("data"));
        assertEquals(2, body.get("count"));
    }

    @Test
    @DisplayName("listAll() — 빈 목록이면 count=0")
    void listAll_emptyList_countZero() {
        when(mockService.listAll()).thenReturn(Collections.emptyList());

        Map<String, Object> body = body(controller.listAll());

        assertEquals(Boolean.TRUE, body.get("success"));
        assertEquals(0, body.get("count"));
    }

    // =========================================================================
    // 단일 조회 — get
    // =========================================================================

    @Test
    @DisplayName("get() — 정상 조회 시 success=true, data 포함")
    void get_found_successTrueAndDataPresent() {
        CryptoModuleDiagnosticDTO dto = makeDTO("MOD_A", "STATIC");
        when(mockService.get("MOD_A")).thenReturn(dto);

        ResponseEntity<?> response = controller.get("MOD_A");
        Map<String, Object> body = body(response);

        assertEquals(200, response.getStatusCodeValue());
        assertEquals(Boolean.TRUE, body.get("success"));
        assertSame(dto, body.get("data"));
        assertFalse(body.containsKey("message"));
    }

    @Test
    @DisplayName("get() — 미등록 cryptoName 예외 발생 시 success=false, message에 원인 포함")
    void get_unknownName_successFalseWithMessage() {
        when(mockService.get("GHOST")).thenThrow(new IllegalArgumentException("암호화모듈 미등록: GHOST"));

        Map<String, Object> body = body(controller.get("GHOST"));

        assertEquals(Boolean.FALSE, body.get("success"));
        assertTrue(body.get("message").toString().contains("GHOST"));
        assertFalse(body.containsKey("data"));
    }

    // =========================================================================
    // 동적 키 캐시 목록 — listDynamicCacheKeys
    // =========================================================================

    @Test
    @DisplayName("listDynamicCacheKeys() — 캐시키 목록과 개수 반환")
    void listDynamicCacheKeys_returnsListAndCount() {
        List<String> keys = Arrays.asList("MOD_A:ctx1", "MOD_A:ctx2");
        when(mockService.listDynamicCacheKeys()).thenReturn(keys);

        Map<String, Object> body = body(controller.listDynamicCacheKeys());

        assertEquals(Boolean.TRUE, body.get("success"));
        assertSame(keys, body.get("data"));
        assertEquals(2, body.get("count"));
    }

    // =========================================================================
    // 암복호화 테스트 — testEncrypt / testDecrypt
    // =========================================================================

    @Test
    @DisplayName("testEncrypt() — 정상 암호화 시 success=true, cipherTextBase64 포함")
    void testEncrypt_success_returnsCipherTextBase64() throws Exception {
        CryptoTestRequestDTO request = new CryptoTestRequestDTO();
        request.setPlainTextBase64("cGxhaW4=");
        when(mockService.testEncrypt(eq("MOD_A"), isNull(), eq("cGxhaW4="), isNull()))
                .thenReturn("Y2lwaGVy");

        Map<String, Object> body = body(controller.testEncrypt("MOD_A", request));

        assertEquals(Boolean.TRUE, body.get("success"));
        @SuppressWarnings("unchecked")
        Map<String, Object> data = (Map<String, Object>) body.get("data");
        assertEquals("Y2lwaGVy", data.get("cipherTextBase64"));
    }

    @Test
    @DisplayName("testEncrypt() — 서비스 예외 발생 시 success=false, message 포함")
    void testEncrypt_serviceThrows_successFalseWithMessage() throws Exception {
        CryptoTestRequestDTO request = new CryptoTestRequestDTO();
        request.setPlainTextBase64("not-valid-base64!!");
        when(mockService.testEncrypt(anyString(), any(), anyString(), any()))
                .thenThrow(new IllegalArgumentException("Illegal base64 character"));

        Map<String, Object> body = body(controller.testEncrypt("MOD_A", request));

        assertEquals(Boolean.FALSE, body.get("success"));
        assertNotNull(body.get("message"));
    }

    @Test
    @DisplayName("testDecrypt() — runtimeContext 전달 시 서비스에 그대로 위임")
    void testDecrypt_withRuntimeContext_delegatesToService() throws Exception {
        Map<String, String> runtimeContext = new HashMap<>();
        runtimeContext.put("X-Api-Enc-Key", "clientKeyValue01");

        CryptoTestRequestDTO request = new CryptoTestRequestDTO();
        request.setCipherTextBase64("Y2lwaGVy");
        request.setRuntimeContext(runtimeContext);
        when(mockService.testDecrypt("MOD_B", runtimeContext, "Y2lwaGVy", null))
                .thenReturn("cGxhaW4=");

        Map<String, Object> body = body(controller.testDecrypt("MOD_B", request));

        assertEquals(Boolean.TRUE, body.get("success"));
        @SuppressWarnings("unchecked")
        Map<String, Object> data = (Map<String, Object>) body.get("data");
        assertEquals("cGxhaW4=", data.get("plainTextBase64"));
        verify(mockService).testDecrypt("MOD_B", runtimeContext, "Y2lwaGVy", null);
    }
}
