package com.sunyard.chsm.sdf.adapter;

import com.sun.jna.Pointer;
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.EccKey;
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.Arrays;
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);

    @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(sdfInfo.IssuerName));
        deviceInfo.setDeviceName(new String(sdfInfo.DeviceName));
        deviceInfo.setDeviceSerial(new String(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 byte[] exportEncPublicKeyECC(String sessionHandle, int uiKeyIndex) {
        byte[] pubKey = new byte[132];
        Pointer hSessionHandle = getSessionHandle(sessionHandle);
        sdfLibrary.SDF_ExportEncPublicKey_ECC(hSessionHandle, uiKeyIndex, pubKey);
        return LangUtils.merge(Arrays.copyOfRange(pubKey, 36, 68), Arrays.copyOfRange(pubKey, 100, 132));
    }

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

    @Override
    public byte[] exchangeDigitEnvelopeBaseOnECC(String sessionHandle, int uiKeyIndex, byte[] pubKey, byte[] pucEncDateIn) {
        return new byte[0];
    }

    @Override
    public byte[] externalEncryptECC(String sessionHandle, byte[] 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, pucData, pucData.length, encData);
        return encData;
    }
}
