package com.eactive.eai.common.security.keyderiv;

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

import java.util.HashMap;
import java.util.Map;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.context.support.GenericApplicationContext;

import com.eactive.eai.common.hsm.HsmCryptoService;
import com.eactive.eai.common.security.keyderiv.strategy.HsmKeyAndIvDerivationStrategy;
import com.eactive.eai.common.util.ApplicationContextProvider;

/**
 * HsmKeyAndIvDerivationStrategy 단위 테스트
 */
@TestMethodOrder(MethodOrderer.DisplayName.class)
class HsmKeyAndIvDerivationStrategyTest {

    private static final String HSM_KEY_ALIAS = "MASTER_KEY_AES";
    private static final String HSM_IV_ALIAS  = "MASTER_IV_AES";
    private static final byte[] MASTER_KEY    = new byte[]{0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,
                                                            0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,0x10};
    private static final byte[] MASTER_IV     = new byte[]{0x10,0x20,0x30,0x40,0x50,0x60,0x70,(byte)0x80,
                                                            0x11,0x21,0x31,0x41,0x51,0x61,0x71,(byte)0x81};

    private static GenericApplicationContext      ctx;
    private static HsmCryptoService               mockHsmCryptoService;
    private static HsmKeyAndIvDerivationStrategy  strategy;

    private Map<String, String> params;
    private Map<String, String> runtimeContext;

    @BeforeAll
    static void setUpClass() throws Exception {
        mockHsmCryptoService = mock(HsmCryptoService.class);
        when(mockHsmCryptoService.getSecretKey(HSM_KEY_ALIAS))
                .thenReturn(new SecretKeySpec(MASTER_KEY, "AES"));
        when(mockHsmCryptoService.getSecretKey(HSM_IV_ALIAS))
                .thenReturn(new SecretKeySpec(MASTER_IV, "AES"));

        ctx = new GenericApplicationContext();
        ctx.getBeanFactory().registerSingleton("hsmCryptoService", mockHsmCryptoService);
        ctx.registerBeanDefinition("applicationContextProvider",
                BeanDefinitionBuilder.genericBeanDefinition(ApplicationContextProvider.class)
                        .getBeanDefinition());
        ctx.refresh();

        strategy = new HsmKeyAndIvDerivationStrategy();
    }

    @BeforeEach
    void setUpParams() {
        params = new HashMap<>();
        params.put("hsmKeyAlias", HSM_KEY_ALIAS);
        params.put("hsmIvAlias",  HSM_IV_ALIAS);

        runtimeContext = new HashMap<>();
    }

    // =========================================================================
    // 1. deriveKey
    // =========================================================================

    @Test
    @DisplayName("1-1. 정상 파라미터 — DerivedKey 반환, key와 iv 모두 non-null")
    void testDeriveKey_validParams_returnsKeyAndIv() throws Exception {
        DerivedKey dk = strategy.deriveKey(params, runtimeContext);

        assertNotNull(dk);
        assertNotNull(dk.getEncKey(), "encKey는 null이 아니어야 한다");
        assertNotNull(dk.getDecKey(), "decKey는 null이 아니어야 한다");
        assertNotNull(dk.getIv(),     "iv는 null이 아니어야 한다");
    }

    @Test
    @DisplayName("1-2. encKey == decKey (대칭 방식)")
    void testDeriveKey_encKeyEqualsDecKey() throws Exception {
        DerivedKey dk = strategy.deriveKey(params, runtimeContext);

        assertArrayEquals(dk.getEncKey(), dk.getDecKey());
    }

    @Test
    @DisplayName("1-3. encKey는 hsmKeyAlias에 해당하는 HSM 키")
    void testDeriveKey_encKeyMatchesMasterKey() throws Exception {
        DerivedKey dk = strategy.deriveKey(params, runtimeContext);

        assertArrayEquals(MASTER_KEY, dk.getEncKey(), "encKey는 hsmKeyAlias로 가져온 마스터키여야 한다");
    }

    @Test
    @DisplayName("1-4. iv는 hsmIvAlias에 해당하는 HSM 키")
    void testDeriveKey_ivMatchesMasterIv() throws Exception {
        DerivedKey dk = strategy.deriveKey(params, runtimeContext);

        assertArrayEquals(MASTER_IV, dk.getIv(), "iv는 hsmIvAlias로 가져온 HSM 값이어야 한다");
    }

    @Test
    @DisplayName("1-5. 필수 파라미터 누락(hsmKeyAlias) — IllegalArgumentException")
    void testDeriveKey_missingHsmKeyAlias_throwsException() {
        params.remove("hsmKeyAlias");

        assertThrows(IllegalArgumentException.class,
                () -> strategy.deriveKey(params, runtimeContext));
    }

    @Test
    @DisplayName("1-6. 필수 파라미터 누락(hsmIvAlias) — IllegalArgumentException")
    void testDeriveKey_missingHsmIvAlias_throwsException() {
        params.remove("hsmIvAlias");

        assertThrows(IllegalArgumentException.class,
                () -> strategy.deriveKey(params, runtimeContext));
    }

    // =========================================================================
    // 2. buildCacheKey
    // =========================================================================

    @Test
    @DisplayName("2-1. buildCacheKey — cryptoName 단독 반환")
    void testBuildCacheKey_returnsCryptoName() {
        String cacheKey = strategy.buildCacheKey("CRYPTO_A", params, runtimeContext);

        assertEquals("CRYPTO_A", cacheKey);
    }

    @Test
    @DisplayName("2-2. buildCacheKey — 다른 cryptoName은 다른 캐시 키")
    void testBuildCacheKey_differentCryptoName_differentKey() {
        String key1 = strategy.buildCacheKey("CRYPTO_A", params, runtimeContext);
        String key2 = strategy.buildCacheKey("CRYPTO_B", params, runtimeContext);

        assertNotEquals(key1, key2);
    }
}
