package com.eactive.eai.common.hsm;

import static org.junit.jupiter.api.Assumptions.assumeTrue;

import java.io.File;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.Provider;
import java.security.Security;
import java.util.Enumeration;
import java.util.concurrent.TimeUnit;

import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;

import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;

/**
 * SoftHSM2 마스터키 등록 유틸리티 테스트.
 *
 * 용도: CryptoModuleConfig.keyDerivParams 의 hsmKeyAlias 에 대응하는
 *       AES-256 마스터키를 SoftHSM2 토큰에 등록한다.
 *
 * 사전 조건:
 *   C:\SoftHSM2 에 SoftHSM2 설치
 *   (토큰이 없으면 자동 초기화, 있으면 기존 토큰 재사용)
 *
 * 실행 후 확인:
 *   softhsm2-util.exe --show-slots
 *   또는 pkcs11-tool --list-objects (OpenSC 설치 시)
 */
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class HsmKeyRegisterUtil {

    private static final String SOFTHSM2_DLL  = "C:/SoftHSM2/lib/softhsm2-x64.dll";
    private static final String SOFTHSM2_UTIL = "C:/SoftHSM2/bin/softhsm2-util.exe";
    private static final String TOKEN_LABEL   = "eapim-test";
    private static final String PIN           = "1234";

    /** DB CryptoModuleConfig.keyDerivParams.hsmKeyAlias 와 반드시 일치해야 한다. */
    private static final String MASTER_KEY_ALIAS = "MASTER_KEY";

    private static Provider  provider;
    private static KeyStore  keyStore;

    @BeforeAll
    static void setUp() throws Exception {
        System.setProperty("SOFTHSM2_CONF", "C:\\SoftHSM2\\etc\\softhsm2.conf");
        assumeTrue(new File(SOFTHSM2_DLL).exists(),
                "SoftHSM2 미설치 (C:/SoftHSM2) - 테스트 건너뜀");

        ensureTokenInitialized();

        String cfgContent = "name = SoftHSM\n"
                + "library = " + SOFTHSM2_DLL + "\n"
                + "slotListIndex = 0\n";

        provider = HsmManager.createProvider(cfgContent);
        Provider existing = Security.getProvider(provider.getName());
        if (existing != null) Security.removeProvider(existing.getName());
        Security.addProvider(provider);

        keyStore = KeyStore.getInstance("PKCS11", provider);
        keyStore.load(null, PIN.toCharArray());
    }

    // ------------------------------------------------------------------
    // 1. 현재 등록된 키 목록 출력
    // ------------------------------------------------------------------

    @Test
    @Order(1)
    void listKeys() throws Exception {
        System.out.println("=== HSM 키 목록 ===");
        Enumeration<String> aliases = keyStore.aliases();
        if (!aliases.hasMoreElements()) {
            System.out.println("  (등록된 키 없음)");
        }
        while (aliases.hasMoreElements()) {
            String alias = aliases.nextElement();
            String type = keyStore.isKeyEntry(alias) ? "KEY" : "CERT";
            System.out.println("  [" + type + "] " + alias);
        }
        System.out.println("==================");
    }

    // ------------------------------------------------------------------
    // 2. 마스터키 등록 (없는 경우에만)
    // ------------------------------------------------------------------

    @Test
    @Order(2)
    void registerMasterKey() throws Exception {
        if (keyStore.containsAlias(MASTER_KEY_ALIAS)) {
            System.out.println("[SKIP] 이미 등록됨: " + MASTER_KEY_ALIAS);
            return;
        }

        KeyGenerator kg = KeyGenerator.getInstance("AES", provider);
        kg.init(256);
        SecretKey sk = kg.generateKey();
        keyStore.setKeyEntry(MASTER_KEY_ALIAS, sk, null, null);

        System.out.println("[OK] AES-256 마스터키 등록 완료: alias=" + MASTER_KEY_ALIAS);
    }

    // ------------------------------------------------------------------
    // 3. 등록 후 조회 확인
    // ------------------------------------------------------------------

    @Test
    @Order(3)
    void verifyMasterKey() throws Exception {
        keyStore.load(null, PIN.toCharArray()); // 갱신

        assert keyStore.containsAlias(MASTER_KEY_ALIAS)
                : "마스터키 등록 실패: " + MASTER_KEY_ALIAS;

        SecretKey sk = (SecretKey) keyStore.getKey(MASTER_KEY_ALIAS, null);
        assert sk != null : "키 조회 실패";
        assert "AES".equals(sk.getAlgorithm()) : "알고리즘 불일치: " + sk.getAlgorithm();

        System.out.println("[OK] 마스터키 조회 성공: alias=" + MASTER_KEY_ALIAS
                + " / algorithm=" + sk.getAlgorithm());
    }

    // ------------------------------------------------------------------
    // 헬퍼
    // ------------------------------------------------------------------

    private static void ensureTokenInitialized() throws Exception {
        ProcessBuilder pb = new ProcessBuilder(
                SOFTHSM2_UTIL, "--init-token", "--free",
                "--label", TOKEN_LABEL,
                "--pin",    PIN,
                "--so-pin", PIN);
        pb.redirectErrorStream(true);
        Process p   = pb.start();
        String  out = readOutput(p);
        p.waitFor(10, TimeUnit.SECONDS);
        System.out.println("[SoftHSM2] init-token: " + out.trim());
    }

    private static String readOutput(Process p) throws Exception {
        InputStream   is  = p.getInputStream();
        byte[]        buf = new byte[4096];
        StringBuilder sb  = new StringBuilder();
        int len;
        while ((len = is.read(buf)) != -1) {
            sb.append(new String(buf, 0, len, "UTF-8"));
        }
        return sb.toString();
    }
}
