package com.sunyard.chsm.service;

import com.sunyard.chsm.constant.ParamConfKeyConstant;
import com.sunyard.chsm.enums.DeviceTmkStatus;
import com.sunyard.chsm.mapper.ParamConfMapper;
import com.sunyard.chsm.mapper.SpDeviceMapper;
import com.sunyard.chsm.model.dto.DeviceCheckRes;
import com.sunyard.chsm.model.dto.TmkStatus;
import com.sunyard.chsm.model.entity.Device;
import com.sunyard.chsm.model.entity.ParamConf;
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.EccPubKey;
import com.sunyard.chsm.utils.CodecUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.Objects;
import java.util.Optional;

/**
 * @author liulu
 * @since 2024/12/10
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class TmkService {

    private final SpDeviceMapper spDeviceMapper;
    private final ParamConfMapper paramConfMapper;

    public void initTmk() {
        boolean tmkInit = isTmkInit();
        Assert.isTrue(!tmkInit, "主密钥已经初始化");

        Device device = getOneByStatus(DeviceTmkStatus.available);
        Assert.notNull(device, "没有可以用于初始化主密钥的设备");
        SdfApiAdapter sdfApi = SdfApiAdapterFactory.newInstance(device.getManufacturerModel(), device.getServiceIp(), device.getServicePort());

        String hd = sdfApi.openDevice();
        String hs = sdfApi.openSession(hd);
        byte[] rk = sdfApi.generateRandom(hs, 16);
        EccPubKey pubKey = sdfApi.exportEncPublicKeyECC(hs, device.getEncKeyIdx());
        EccCipher cipher = sdfApi.externalEncryptECC(hs, pubKey, rk);
        sdfApi.getPrivateKeyAccessRight(hs, device.getEncKeyIdx(), device.getAccessCredentials().getBytes());
        String hk = sdfApi.importKeyWithISKECC(hs, device.getEncKeyIdx(), cipher);
        byte[] encrk = sdfApi.symEncrypt(hs, hk, AlgId.SGD_SM4_ECB, new byte[0], rk);
        byte[] prk = sdfApi.symDecrypt(hs, hk, AlgId.SGD_SM4_ECB, new byte[0], encrk);
        Assert.isTrue(Arrays.equals(rk, prk), "密码机加解密异常");

        sdfApi.destroyKey(hs, hk);
        sdfApi.closeSession(hs);
        sdfApi.closeDevice(hd);

        if (Objects.equals(device.getManufacturerModel(), BouncyCastleProvider.PROVIDER_NAME)) {
            updateSoftDeviceEncTmk(cipher.getC1C3C2Bytes());
        } else {
            Device up = new Device();
            up.setId(device.getId());
            up.setEncTmk(cipher.getC1C3C2Hex());
            up.setTmkStatus(DeviceTmkStatus.finished.name());
            spDeviceMapper.updateById(up);
        }
        updateTmkInit(true);

    }

    public TmkStatus getTMKStatus() {
        TmkStatus status = new TmkStatus();
        boolean init = isTmkInit();
        if (init) {
            status.setHasDevice(true);
            status.setTmkInit(true);
            return status;
        }
        status.setTmkInit(false);
        Device device = getOneByStatus(DeviceTmkStatus.available);
        status.setHasDevice(device != null);
        return status;
    }


    public DeviceCheckRes checkDevice(Device check) {

        log.debug("==========> begin check  device : {}", check);
        DeviceCheckRes res = new DeviceCheckRes();
        SdfApiAdapter sdfApi = SdfApiAdapterFactory.newInstance(check.getManufacturerModel(), check.getServiceIp(), check.getServicePort());
        String hd = sdfApi.openDevice();
        String hs = null;

        try {
            hs = sdfApi.openSession(hd);
            DeviceInfo info = sdfApi.getDeviceInfo(hs);
            log.debug("get DeviceInfo: {}", info);
            res.setDeviceSerial(info.getDeviceSerial());
            res.setStatus(DeviceTmkStatus.key_error);
        } catch (Exception e) {
            log.warn("check device connect error: {}:{}", check.getServiceIp(), check.getServicePort(), e);
            res.setHasError(true);
            res.setStatus(DeviceTmkStatus.device_error);
            res.setMessage(e.getMessage());
        }
        if (res.isHasError()) {
            Optional.ofNullable(hs).ifPresent(sdfApi::closeSession);
            Optional.ofNullable(hd).ifPresent(sdfApi::closeDevice);
            log.debug("==========> end check  device : {}", res);
            return res;
        }
        try {
            EccPubKey pubKey = sdfApi.exportEncPublicKeyECC(hs, check.getEncKeyIdx());
            if (Arrays.equals(pubKey.getPubKeyBytes(), new byte[64])) {
                throw new IllegalArgumentException("加密密钥索引不正确");
            }
            res.setPubKey(pubKey.getPubKeyHex());
            log.debug("export enc pubKey at index: {}, {}", check.getEncKeyIdx(), res.getPubKey());
            sdfApi.getPrivateKeyAccessRight(hs, check.getEncKeyIdx(), check.getAccessCredentials().getBytes());
            if (isTmkInit()) {
                log.debug("tmk is init...");
                EccCipher cipher;
                if (Objects.equals(check.getDeviceSerial(), res.getDeviceSerial())
                        && Objects.equals(check.getPubKey(), res.getPubKey())
                        && StringUtils.hasText(check.getEncTmk())) {
                    cipher = EccCipher.fromHex(check.getEncTmk());
                    log.debug("device serial, pubKey not changed, use origin device enc tmk");
                } else {
                    log.debug("device serial, pubKey is changed, or no tmk in origin device");
                    Device device = getOneByStatus(DeviceTmkStatus.finished);
                    Assert.notNull(device, "系统主密钥设备异常,请联系管理员排查");
                    log.debug("get device from finished: {}", device);
                    SdfApiAdapter tmkAdapter = SdfApiAdapterFactory.newInstance(device.getManufacturerModel(), device.getServiceIp(), device.getServicePort());
                    String tmkHd = tmkAdapter.openDevice();
                    String tmkHs = tmkAdapter.openSession(tmkHd);
                    tmkAdapter.getPrivateKeyAccessRight(tmkHs, device.getEncKeyIdx(), device.getAccessCredentials().getBytes());
                    cipher = tmkAdapter.exchangeDigitEnvelopeBaseOnECC(tmkHs, device.getEncKeyIdx(), pubKey, EccCipher.fromHex(device.getEncTmk()));
                    log.debug("exchanged envelope from finished success ...");
                }
                String hk = sdfApi.importKeyWithISKECC(hs, check.getEncKeyIdx(), cipher);
                sdfApi.destroyKey(hs, hk);
                if (!Objects.equals(check.getEncTmk(), cipher.getC1C3C2Hex())) {
                    res.setEncTmk(cipher.getC1C3C2Hex());
                }
                res.setStatus(DeviceTmkStatus.finished);
            } else {
                log.debug("tmk not init, start encrypt and import test ...");
                byte[] random = sdfApi.generateRandom(hs, 16);
                EccCipher eccCipher = sdfApi.externalEncryptECC(hs, pubKey, random);
                String hk = sdfApi.importKeyWithISKECC(hs, check.getEncKeyIdx(), eccCipher);
                sdfApi.destroyKey(hs, hk);
                res.setStatus(DeviceTmkStatus.available);
            }
        } catch (Exception e) {
            log.warn("check device encKey error: {}:{}", check.getServiceIp(), check.getServicePort(), e);
            res.setHasError(true);
            res.setStatus(DeviceTmkStatus.key_error);
            res.setMessage(e.getMessage());
        }
        Optional.ofNullable(hs).ifPresent(sdfApi::closeSession);
        Optional.ofNullable(hd).ifPresent(sdfApi::closeDevice);
        res.setSdfApiAdapter(sdfApi);
        log.debug("==========> end check  device : {}", res);
        return res;
    }


    public void checkSoftDeviceTmk() {
        if (Objects.nonNull(getSoftDeviceEncTmk())) {
            return;
        }
        if (!isTmkInit() || !isEnableSoftDevice()) {
            return;
        }
        log.warn("enabled soft device but no tmk in soft");
        Device device = getOneByStatus(DeviceTmkStatus.finished);
        if (device == null || Objects.equals(device.getManufacturerModel(), BouncyCastleProvider.PROVIDER_NAME)) {
            log.warn("data error, no tmk found in system");
            return;
        }
        SdfApiAdapter softAdapter = SdfApiAdapterFactory.getBcAdapter();
        EccPubKey pubKey = softAdapter.exportEncPublicKeyECC("", 1);

        SdfApiAdapter tmkAdapter = SdfApiAdapterFactory.newInstance(device.getManufacturerModel(), device.getServiceIp(), device.getServicePort());
        String tmkHd = tmkAdapter.openDevice();
        String tmkHs = tmkAdapter.openSession(tmkHd);
        tmkAdapter.getPrivateKeyAccessRight(tmkHs, device.getEncKeyIdx(), device.getAccessCredentials().getBytes());
        EccCipher cipher = tmkAdapter.exchangeDigitEnvelopeBaseOnECC(tmkHs, device.getEncKeyIdx(), pubKey, EccCipher.fromHex(device.getEncTmk()));

        updateSoftDeviceEncTmk(cipher.getC1C3C2Bytes());
    }

    private Device getOneByStatus(DeviceTmkStatus status) {
        Device device = spDeviceMapper.selectOneByStatus(status);
        if (Objects.nonNull(device)) {
            return device;
        }
        if (isEnableSoftDevice()) {
            device = new Device();
            device.setManufacturerModel(BouncyCastleProvider.PROVIDER_NAME);
            device.setEncKeyIdx(1);
            device.setServiceIp("127.0.0.1");
            device.setServicePort(8889);
            device.setAccessCredentials("543");
            if (isTmkInit()) {
                device.setEncTmk(CodecUtils.encodeHex(getSoftDeviceEncTmk()));
            }
            return device;
        }

        return null;
    }

    private boolean tmkInit;
    private boolean enableSoftDevice;
    private byte[] softEncTmk;

    public synchronized boolean isTmkInit() {
        if (tmkInit) {
            return true;
        }
        ParamConf conf = paramConfMapper.selectByKey(ParamConfKeyConstant.TMK_INIT);
        tmkInit = conf != null && String.valueOf(true).equals(conf.getValue());
        return tmkInit;
    }

    public boolean isEnableSoftDevice() {
        ParamConf conf = paramConfMapper.selectByKey(ParamConfKeyConstant.ENABLE_SOFT_DEVICE);
        return conf != null && String.valueOf(true).equals(conf.getValue());
    }

    private void updateTmkInit(boolean value) {
        ParamConf conf = paramConfMapper.selectByKey(ParamConfKeyConstant.TMK_INIT);
        if (conf == null) {
            conf = new ParamConf();
            conf.setValue(String.valueOf(value));
            conf.setKey(ParamConfKeyConstant.TMK_INIT);
            conf.setCreatTime(LocalDateTime.now());
            paramConfMapper.insert(conf);
        } else {
            conf.setValue(String.valueOf(value));
            paramConfMapper.updateById(conf);
        }
    }

    public synchronized byte[] getSoftDeviceEncTmk() {
        boolean tmkInit = isTmkInit();
        Assert.isTrue(tmkInit, "主密钥未初始化");
        boolean enabled = isEnableSoftDevice();
        Assert.isTrue(enabled, "未启用软设备");
        if (softEncTmk != null) {
            return softEncTmk;
        }
        ParamConf conf = paramConfMapper.selectByKey(ParamConfKeyConstant.SOFT_ENC_TMK);
        if (conf == null || ObjectUtils.isEmpty(conf.getValue())) {
            return null;
        }
        softEncTmk = CodecUtils.decodeBase64(conf.getValue());
        return softEncTmk;
    }

    private void updateSoftDeviceEncTmk(byte[] encTmk) {
        ParamConf conf = paramConfMapper.selectByKey(ParamConfKeyConstant.SOFT_ENC_TMK);
        if (conf == null) {
            conf = new ParamConf();
            conf.setValue(CodecUtils.encodeBase64(encTmk));
            conf.setKey(ParamConfKeyConstant.SOFT_ENC_TMK);
            conf.setCreatTime(LocalDateTime.now());
            paramConfMapper.insert(conf);
        } else {
            conf.setValue(CodecUtils.encodeBase64(encTmk));
            paramConfMapper.updateById(conf);
        }
    }


}
