package com.eactive.eai.common.hsm;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.security.KeyStore;
import java.security.Provider;
import java.security.Security;

import org.springframework.stereotype.Component;

import com.eactive.eai.common.exception.ExceptionUtil;
import com.eactive.eai.common.lifecycle.Lifecycle;
import com.eactive.eai.common.lifecycle.LifecycleException;
import com.eactive.eai.common.lifecycle.LifecycleListener;
import com.eactive.eai.common.lifecycle.LifecycleSupport;
import com.eactive.eai.common.property.PropManager;
import com.eactive.eai.common.util.ApplicationContextProvider;
import com.eactive.eai.common.util.Logger;

/**
 * SafeNet ProtectServer HSM 연동 관리자 (JDK 8 / SunPKCS11)
 *
 * PropManager 그룹 "HSM" 에서 읽는 키:
 *   PKCS11_CONFIG  - pkcs11.cfg 파일 내용 (name/library/slot 설정을 DB에 직접 저장)
 *   PIN            - HSM 슬롯 PIN
 *
 * PKCS11_CONFIG 값 예시 (개행은 \n 으로 입력):
 *   name = ProtectServer
 *   library = /opt/safenet/protecttoolkit5/ptk/lib/libcryptoki.so
 *   slot = 0
 */
@Component
public class HsmManager implements Lifecycle {

    static Logger logger = Logger.getLogger(Logger.LOGGER_DEFAULT);

    private static final String GROUP_NAME = "HSM";
    private static final String PROP_CONFIG = "PKCS11_CONFIG";
    private static final String PROP_PIN = "PIN";

    private Provider pkcs11Provider;
    private KeyStore keyStore;
    private boolean started;

    private final LifecycleSupport lifecycle = new LifecycleSupport(this);

    private HsmManager() {
    }

    public static HsmManager getInstance() {
        return ApplicationContextProvider.getContext().getBean(HsmManager.class);
    }

    @Override
    public void start() throws LifecycleException {
        if (started) {
            throw new LifecycleException("RECEAIHSM001");
        }
        lifecycle.fireLifecycleEvent(STARTING_EVENT, this);

        try {
            init();
        } catch (Exception e) {
            throw new LifecycleException(ExceptionUtil.getErrorCode(e, "RECEAIHSM002"));
        }

        started = true;
        lifecycle.fireLifecycleEvent(STARTED_EVENT, this);
    }

    private void init() throws Exception {
        String configContent = PropManager.getInstance().getProperty(GROUP_NAME, PROP_CONFIG);
        String pin = PropManager.getInstance().getProperty(GROUP_NAME, PROP_PIN);

        if (configContent == null || configContent.trim().isEmpty()) {
            logger.warn("HsmManager] PKCS11_CONFIG 가 설정되지 않았습니다. HSM 초기화를 건너뜁니다.");
            return;
        }

        pkcs11Provider = createProvider(configContent.trim().replace("\\n", "\n"));

        // 이미 등록된 Provider 가 있으면 제거 후 재등록
        Provider existing = Security.getProvider(pkcs11Provider.getName());
        if (existing != null) {
            Security.removeProvider(existing.getName());
        }
        Security.addProvider(pkcs11Provider);

        keyStore = KeyStore.getInstance("PKCS11", pkcs11Provider);
        char[] pinChars = (pin != null) ? pin.toCharArray() : null;
        keyStore.load(null, pinChars);

        logger.warn("HsmManager] 초기화 완료. Provider=" + pkcs11Provider.getName());

        java.util.Enumeration<String> aliases = keyStore.aliases();
        StringBuilder aliasList = new StringBuilder();
        while (aliases.hasMoreElements()) {
            if (aliasList.length() > 0) aliasList.append(", ");
            aliasList.append(aliases.nextElement());
        }
        logger.warn("HsmManager] HSM 키 목록: [" + aliasList + "]");
    }

    /**
     * JDK 버전에 따라 SunPKCS11 Provider 를 생성한다.
     *
     * JDK 8 : SunPKCS11(InputStream) 생성자를 리플렉션으로 호출
     * JDK 9+: Provider.configure(configFilePath) 를 리플렉션으로 호출
     *
     * 두 경로 모두 리플렉션을 사용하므로 컴파일 타임에 JDK 버전 의존성이 없다.
     */
    static Provider createProvider(String cfgContent) throws Exception {
        String javaVersion = System.getProperty("java.version");
        if (javaVersion.startsWith("1.")) {
            // JDK 8: new SunPKCS11(InputStream)
            InputStream is = new ByteArrayInputStream(cfgContent.getBytes("UTF-8"));
            return (Provider) Class.forName("sun.security.pkcs11.SunPKCS11")
                    .getConstructor(InputStream.class)
                    .newInstance(is);
        } else {
            // JDK 9+: Security.getProvider("SunPKCS11").configure(configFilePath)
            // configure() 는 JDK 9 에서 추가된 메서드이므로 리플렉션으로 호출
            File tmp = File.createTempFile("pkcs11-hsm-", ".cfg");
            tmp.deleteOnExit();
            Files.write(tmp.toPath(), cfgContent.getBytes("UTF-8"));
            Provider base = Security.getProvider("SunPKCS11");
            Method configure = Provider.class.getMethod("configure", String.class);
            return (Provider) configure.invoke(base, tmp.getAbsolutePath());
        }
    }

    @Override
    public void stop() throws LifecycleException {
        if (!started) {
            throw new LifecycleException("RECEAIHSM003");
        }
        lifecycle.fireLifecycleEvent(STOPING_EVENT, this);

        if (pkcs11Provider != null) {
            Security.removeProvider(pkcs11Provider.getName());
        }
        pkcs11Provider = null;
        keyStore = null;
        started = false;

        lifecycle.fireLifecycleEvent(STOPPED_EVENT, this);
    }

    @Override
    public void addLifecycleListener(LifecycleListener listener) {
        lifecycle.addLifecycleListener(listener);
    }

    @Override
    public LifecycleListener[] findLifecycleListeners() {
        return lifecycle.findLifecycleListeners();
    }

    @Override
    public void removeLifecycleListener(LifecycleListener listener) {
        lifecycle.removeLifecycleListener(listener);
    }

    @Override
    public boolean isStarted() {
        return started;
    }

    public Provider getPkcs11Provider() {
        return pkcs11Provider;
    }

    public KeyStore getKeyStore() {
        return keyStore;
    }

    public boolean isReady() {
        return started && pkcs11Provider != null && keyStore != null;
    }
}
