package com.sunyard.chsm.sdf;

import com.sunyard.chsm.enums.DeviceTmkStatus;
import com.sunyard.chsm.enums.Padding;
import com.sunyard.chsm.mapper.SpDeviceMapper;
import com.sunyard.chsm.model.dto.DeviceCheckRes;
import com.sunyard.chsm.model.entity.Device;
import com.sunyard.chsm.sdf.adapter.SdfApiAdapter;
import com.sunyard.chsm.sdf.adapter.SdfApiAdapterFactory;
import com.sunyard.chsm.sdf.context.AlgId;
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.EccPriKey;
import com.sunyard.chsm.sdf.model.EccPubKey;
import com.sunyard.chsm.sdf.model.EccSignature;
import com.sunyard.chsm.sdf.util.PaddingUtil;
import com.sunyard.chsm.service.TmkService;
import com.sunyard.chsm.utils.gm.BCECUtils;
import com.sunyard.chsm.utils.gm.BCSM3Utils;
import com.sunyard.chsm.utils.gm.SM2PreprocessSigner;
import lombok.NoArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.params.ParametersWithID;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

import javax.annotation.Resource;
import java.util.Objects;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
 * @author liulu
 * @since 2024/12/11
 */
@Service
@NoArgsConstructor
public class SingleSdfApiService implements SdfApiService, InitializingBean {

    private String tmkHandle;
    private String deviceHandle;
    private String sessionHandle;
    private SdfApiAdapter sdfApiAdapter;

    @Resource
    public TmkService tmkService;
    @Resource
    private SpDeviceMapper spDeviceMapper;

    public SingleSdfApiService(SdfApiAdapter sdfApiAdapter) {
        this.sdfApiAdapter = sdfApiAdapter;
        this.deviceHandle = sdfApiAdapter.openDevice();
        this.sessionHandle = sdfApiAdapter.openSession(deviceHandle);
    }

    @Override
    public byte[] generateRandom(int len) {
        checkStatus();
        return sdfApiAdapter.generateRandom(sessionHandle, len);
    }

    @Override
    public byte[] symEncrypt(AlgId alg, Padding padding, byte[] key, byte[] iv, byte[] data) {
        checkStatus();
        byte[] pad = PaddingUtil.padding(padding, data);
        String hk = sdfApiAdapter.importKey(sessionHandle, key);
        byte[] encrypt = sdfApiAdapter.symEncrypt(sessionHandle, hk, alg, iv, pad);
        sdfApiAdapter.destroyKey(sessionHandle, hk);
        return encrypt;
    }

    @Override
    public byte[] symDecrypt(AlgId alg, Padding padding, byte[] key, byte[] iv, byte[] data) {
        checkStatus();
        String hk = sdfApiAdapter.importKey(sessionHandle, key);
        byte[] decrypt = sdfApiAdapter.symDecrypt(sessionHandle, hk, alg, iv, data);
        sdfApiAdapter.destroyKey(sessionHandle, hk);
        return PaddingUtil.unpadding(padding, decrypt);
    }

    @Override
    public EccKey genKeyPairEcc() {
        checkStatus();
        return sdfApiAdapter.generateKeyPairECC(sessionHandle, AlgId.SGD_SM2_1);
    }

    @Override
    public EccSignature externalSignECC(byte[] privateKey, byte[] pucData) {
        return externalSignWithIdECC(privateKey, pucData, new byte[0]);
    }

    @Override
    public EccSignature externalSignWithIdECC(byte[] privateKey, byte[] pucData, byte[] userId) {
        EccPriKey sdfEccPrivateKey = EccPriKey.fromBytes(privateKey);
        SM2PreprocessSigner signer = new SM2PreprocessSigner();
        CipherParameters keyParameters = BCECUtils.createECPrivateKeyParameters(sdfEccPrivateKey.getD());
        if (userId != null && userId.length > 0) {
            keyParameters = new ParametersWithID(keyParameters, userId);
        }
        signer.init(true, keyParameters);
        byte[] preHash = signer.preprocess(pucData);
        return sdfApiAdapter.externalSignECC(sessionHandle, sdfEccPrivateKey, preHash);
    }

    @Override
    public boolean externalVerifyWithIdECC(byte[] publicKey, byte[] pubData, byte[] signData, byte[] userId) {
        EccPubKey eccPublicKey = EccPubKey.fromBytes(publicKey);
        CipherParameters parameters = BCECUtils.createECPublicKeyParameters(eccPublicKey.getX(), eccPublicKey.getY());
        if (userId != null && userId.length > 0) {
            parameters = new ParametersWithID(parameters, userId);
        }
        SM2PreprocessSigner signer = new SM2PreprocessSigner();
        signer.init(false, parameters);
        byte[] preHash = signer.preprocess(pubData);
        EccSignature eccSignature = EccSignature.fromBytes(signData);

        return sdfApiAdapter.externalVerifyECC(sessionHandle, eccPublicKey, preHash, eccSignature);
    }

    @Override
    public boolean externalVerifyECC(byte[] publicKey, byte[] pubData, byte[] signData) {
        return externalVerifyWithIdECC(publicKey, pubData, signData, new byte[0]);
    }

    @Override
    public EccCipher externalEncryptECC(byte[] publicKey, byte[] pucData) {
        EccPubKey eccPublicKey = EccPubKey.fromBytes(publicKey);
        return sdfApiAdapter.externalEncryptECC(sessionHandle, eccPublicKey, pucData);
    }

    @Override
    public byte[] externalDecryptECC(byte[] privateKey, byte[] encData) {
        EccPriKey sdfEccPrivateKey = EccPriKey.fromBytes(privateKey);
        EccCipher eccCipher = EccCipher.fromBytes(encData);
        return sdfApiAdapter.externalDecryptECC(sessionHandle, sdfEccPrivateKey, eccCipher);
    }

    @Override
    public byte[] calculateMAC(AlgId algId, Padding padding, byte[] symKey, byte[] pucIv, byte[] pucData) {
        checkStatus();
        byte[] pad = PaddingUtil.padding(padding, pucData);
        String hk = sdfApiAdapter.importKey(sessionHandle, symKey);
        byte[] mac = sdfApiAdapter.calculateMAC(sessionHandle, hk, algId, pucIv, pad);
        sdfApiAdapter.destroyKey(sessionHandle, hk);
        return mac;
    }

    @Override
    public byte[] hmac(byte[] key, byte[] srcData) {
        return BCSM3Utils.hmac(key, srcData);
    }

    @Override
    public byte[] hash(byte[] pucData) {
        checkStatus();
        String newSession = sdfApiAdapter.openSession(deviceHandle);
        sdfApiAdapter.hashInit(newSession, AlgId.SGD_SM3, null, null);
        sdfApiAdapter.hashUpdate(newSession, pucData);
        byte[] hash = sdfApiAdapter.hashFinish(newSession);
        sdfApiAdapter.closeSession(newSession);
        return hash;
    }

    @Override
    public byte[] encryptByTMK(byte[] data) {
        checkKey();
        byte[] pad = PaddingUtil.PKCS7Padding(data);
        sdfApiAdapter.symEncrypt(sessionHandle, tmkHandle, AlgId.SGD_SM4_CBC, new byte[8], pad);
        return new byte[0];
    }

    @Override
    public byte[] decryptByTMK(byte[] data) {
        checkKey();
        byte[] decrypt = sdfApiAdapter.symDecrypt(sessionHandle, tmkHandle, AlgId.SGD_SM4_CBC, new byte[8], data);
        return PaddingUtil.PKCS7Unpadding(decrypt);
    }

    private void checkStatus() {
        Assert.notNull(sdfApiAdapter, "没有可用设备");
    }

    private void checkKey() {
        checkStatus();
        Assert.hasText(tmkHandle, "没有可用的主密钥设备");
    }

    private void changeDevice(boolean tmkInit) {
        DeviceTmkStatus status = tmkInit ? DeviceTmkStatus.finished : DeviceTmkStatus.available;
        Device device = spDeviceMapper.selectOneByStatus(status);
        if (Objects.nonNull(device)) {
            DeviceCheckRes checkRes = tmkService.checkDevice(device);
            if (!checkRes.isHasError()) {
                this.sdfApiAdapter = checkRes.getSdfApiAdapter();
                this.deviceHandle = sdfApiAdapter.openDevice();
                this.sessionHandle = sdfApiAdapter.openSession(deviceHandle);
                if (tmkInit) {
                    sdfApiAdapter.getPrivateKeyAccessRight(sessionHandle, device.getEncKeyIdx(), device.getAccessCredentials().getBytes());
                    this.tmkHandle = sdfApiAdapter.importKeyWithISKECC(sessionHandle, device.getEncKeyIdx(), EccCipher.fromHex(device.getEncTmk()));
                }
                return;
            }
        }
        if (tmkService.isEnableSoftDevice()) {
            this.sdfApiAdapter = SdfApiAdapterFactory.getBcAdapter();
            this.sessionHandle = sdfApiAdapter.openSession("");
            if (tmkInit) {
                byte[] encTmk = tmkService.getSoftDeviceEncTmk();
                this.tmkHandle = sdfApiAdapter.importKeyWithISKECC(sessionHandle, device.getEncKeyIdx(), EccCipher.fromBytes(encTmk));
            }
        }
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        Executors.newSingleThreadScheduledExecutor()
                .scheduleWithFixedDelay(() -> {
                    boolean tmkInit = tmkService.isTmkInit();
                    if (sdfApiAdapter == null) {
                        changeDevice(tmkInit);
                        return;
                    }
                    if (tmkInit && StringUtils.isEmpty(tmkHandle)) {
                        changeDevice(tmkInit);
                        return;
                    }
                    try {
                        DeviceInfo deviceInfo = sdfApiAdapter.getDeviceInfo(sessionHandle);
                    } catch (Exception ex) {
                        changeDevice(tmkInit);
                    }
                }, 0L, 5L, TimeUnit.MINUTES);
    }
}
