package com.sunyard.chsm.sdf.adapter;

import com.sun.jna.Pointer;
import com.sun.jna.ptr.IntByReference;
import com.sun.jna.ptr.PointerByReference;
import com.sunyard.chsm.sdf.context.AlgId;
import com.sunyard.chsm.sdf.lib.SdfLibrary;
import com.sunyard.chsm.sdf.model.DeviceInfo;
import com.sunyard.chsm.sdf.model.EccCipher;
import com.sunyard.chsm.sdf.model.EccKey;
import com.sunyard.chsm.sdf.model.EccPubKey;
import com.sunyard.chsm.sdf.model.SDF_DeviceInfo;
import com.sunyard.chsm.sdf.util.LangUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.Assert;

import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author liulu
 * @since 2024/11/4
 */
@Slf4j
@RequiredArgsConstructor
public abstract class JnaSdfAdaptor implements SdfApiAdapter {

    protected static final Map<String, Pointer> DEVICE_HANDLE_CONTEXT = new ConcurrentHashMap<>();
    protected static final Map<String, Pointer> SESSION_HANDLE_CONTEXT = new ConcurrentHashMap<>();
    protected static final Map<String, Pointer> KEY_HANDLE_CONTEXT = new ConcurrentHashMap<>();

    protected final SdfLibrary sdfLibrary;

    protected abstract int getAlgId(AlgId alg);
    protected abstract void checkRes(int res);

    @Override
    public String openDevice() {
        PointerByReference phDeviceHandle = new PointerByReference();
        sdfLibrary.SDF_OpenDevice(phDeviceHandle);
        String key = UUID.randomUUID().toString();
        DEVICE_HANDLE_CONTEXT.put(key, phDeviceHandle.getValue());
        return key;
    }

    @Override
    public boolean closeDevice(String deviceHandle) {
        Pointer pointer = DEVICE_HANDLE_CONTEXT.remove(deviceHandle);
        if (pointer != null) {
            int res = sdfLibrary.SDF_CloseDevice(pointer);
            return res == 0;
        }
        return true;
    }

    @Override
    public String openSession(String deviceHandle) {
        Pointer device = getDeviceHandle(deviceHandle);

        PointerByReference phSessionHandle = new PointerByReference();
        sdfLibrary.SDF_OpenSession(device, phSessionHandle);
        Assert.notNull(phSessionHandle.getValue(), "SDF_OpenSession failed");

        String key = UUID.randomUUID().toString();
        SESSION_HANDLE_CONTEXT.put(key, phSessionHandle.getValue());
        return key;
    }

    @Override
    public void closeSession(String sessionHandle) {
        Pointer pointer = SESSION_HANDLE_CONTEXT.remove(sessionHandle);
        if (pointer != null) {
            sdfLibrary.SDF_CloseSession(pointer);
        }
    }

    protected Pointer getDeviceHandle(String deviceHandle) {
        Pointer pointer = DEVICE_HANDLE_CONTEXT.get(deviceHandle);
        Assert.notNull(pointer, "设备句柄不存在");
        return pointer;
    }

    protected Pointer getSessionHandle(String sessionHandle) {
        Pointer pointer = SESSION_HANDLE_CONTEXT.get(sessionHandle);
        Assert.notNull(pointer, "会话句柄不存在");
        return pointer;
    }

    protected Pointer getKeyHandle(String keyHandle) {
        Pointer pointer = KEY_HANDLE_CONTEXT.get(keyHandle);
        Assert.notNull(pointer, "密钥句柄不存在");
        return pointer;
    }

    @Override
    public DeviceInfo getDeviceInfo(String sessionHandle) {

        SDF_DeviceInfo sdfInfo = new SDF_DeviceInfo();
        sdfLibrary.SDF_GetDeviceInfo(getSessionHandle(sessionHandle), sdfInfo);

        DeviceInfo deviceInfo = new DeviceInfo();
        deviceInfo.setIssuerName(new String(LangUtils.removeLastZero(sdfInfo.IssuerName)));
        deviceInfo.setDeviceName(new String(LangUtils.removeLastZero(sdfInfo.DeviceName)));
        deviceInfo.setDeviceSerial(new String(LangUtils.removeLastZero(sdfInfo.DeviceSerial)));
        deviceInfo.setDeviceVersion(sdfInfo.DeviceVersion);
        deviceInfo.setStandardVersion(sdfInfo.StandardVersion);
        deviceInfo.setAsymAlgAbility(new long[]{sdfInfo.AsymAlgAbility[0], sdfInfo.AsymAlgAbility[1]});
        deviceInfo.setSymAlgAbility(sdfInfo.SymAlgAbility);
        deviceInfo.setHashAlgAbility(sdfInfo.HashAlgAbility);
        deviceInfo.setBufferSize(sdfInfo.BufferSize);
        return deviceInfo;
    }

    @Override
    public byte[] generateRandom(String sessionHandle, int uiLength) {
        byte[] pucRandom = new byte[uiLength];
        Pointer hSessionHandle = getSessionHandle(sessionHandle);
        sdfLibrary.SDF_GenerateRandom(hSessionHandle, uiLength, pucRandom);
        return pucRandom;
    }

    @Override
    public EccPubKey exportEncPublicKeyECC(String sessionHandle, int uiKeyIndex) {
        byte[] pubKey = new byte[132];
        Pointer hSessionHandle = getSessionHandle(sessionHandle);
        sdfLibrary.SDF_ExportEncPublicKey_ECC(hSessionHandle, uiKeyIndex, pubKey);
        return EccPubKey.fromBytes(pubKey);
    }

    @Override
    public EccKey generateKeyPairECC(String sessionHandle, String alg, int uiKeyBits) {
        return null;
    }

    @Override
    public boolean getPrivateKeyAccessRight(String sessionHandle, int uiKeyIndex, byte[] pucPassword) {
        Pointer hSessionHandle = getSessionHandle(sessionHandle);
        int res = sdfLibrary.SDF_GetPrivateKeyAccessRight(hSessionHandle, uiKeyIndex, pucPassword, pucPassword.length);
        return res == 0;
    }

    @Override
    public String importKeyWithISKECC(String sessionHandle, int uiIskIndex, EccCipher eccCipher) {
        Pointer hSessionHandle = getSessionHandle(sessionHandle);
        PointerByReference phKeyHandle = new PointerByReference();

        sdfLibrary.SDF_ImportKeyWithISK_ECC(hSessionHandle, uiIskIndex, eccCipher.toSdfData(), phKeyHandle);
        String key = UUID.randomUUID().toString();
        KEY_HANDLE_CONTEXT.put(key, phKeyHandle.getValue());
        return key;
    }

    @Override
    public String importKey(String sessionHandle, byte[] pucKey) {
        Pointer hSessionHandle = getSessionHandle(sessionHandle);
        PointerByReference phKeyHandle = new PointerByReference();
        sdfLibrary.SDF_ImportKey(hSessionHandle, pucKey, pucKey.length, phKeyHandle);
        String key = UUID.randomUUID().toString();
        KEY_HANDLE_CONTEXT.put(key, phKeyHandle.getValue());
        return key;
    }

    @Override
    public int destroyKey(String sessionHandle, String hKeyHandle) {
        return 0;
    }


    @Override
    public EccCipher exchangeDigitEnvelopeBaseOnECC(String sessionHandle, int uiKeyIndex, EccPubKey pubKey, EccCipher pucEncDateIn) {
        Pointer hSessionHandle = getSessionHandle(sessionHandle);

        byte[] pucEncDateOut = new byte[164 + pucEncDateIn.getL()];
        sdfLibrary.SDF_ExchangeDigitEnvelopeBaseOnECC(hSessionHandle, uiKeyIndex, getAlgId(AlgId.SGD_SM2_1),
                pubKey.toSdfData(), pucEncDateIn.toSdfData(), pucEncDateOut);
        return EccCipher.fromBytes(pucEncDateOut);
    }

    @Override
    public EccCipher externalEncryptECC(String sessionHandle, EccPubKey pubKey, byte[] pucData) {
        byte[] encData = new byte[128 + 32 + 4 + pucData.length];
        Pointer hSessionHandle = getSessionHandle(sessionHandle);
        sdfLibrary.SDF_ExternalEncrypt_ECC(hSessionHandle, getAlgId(AlgId.SGD_SM2_3), pubKey.toSdfData(), pucData, pucData.length, encData);
        return EccCipher.fromBytes(encData);
    }

    @Override
    public byte[] symEncrypt(String sessionHandle, String keyHandle, AlgId alg, byte[] pucIv, byte[] pucData) {

        Pointer hSessionHandle = getSessionHandle(sessionHandle);
        Pointer hKeyHandle = getKeyHandle(keyHandle);
        int uiDataLength = pucData.length;
        byte[] pucEncData = new byte[uiDataLength];
        IntByReference puiLength = new IntByReference();
        //加密
        sdfLibrary.SDF_Encrypt(hSessionHandle, hKeyHandle, getAlgId(alg),
                pucIv, pucData, uiDataLength, pucEncData, puiLength);

        return pucEncData;
    }

    @Override
    public byte[] symDecrypt(String sessionHandle, String keyHandle, AlgId alg, byte[] pucIv, byte[] pucEncData) {
        Pointer hSessionHandle = getSessionHandle(sessionHandle);
        Pointer hKeyHandle = getKeyHandle(keyHandle);
        int uiEncDataLength = pucEncData.length;
        byte[] pucData = new byte[uiEncDataLength];
        //解密
        IntByReference puiLength = new IntByReference();
        sdfLibrary.SDF_Decrypt(hSessionHandle, hKeyHandle, getAlgId(alg),
                pucIv, pucEncData, uiEncDataLength, pucData, puiLength);
        return pucData;
    }
}
