package com.sunyard.chsm.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.sunyard.chsm.enums.KeyCategory;
import com.sunyard.chsm.enums.KeyStatus;
import com.sunyard.chsm.enums.KeyUsage;
import com.sunyard.chsm.mapper.AppCertMapper;
import com.sunyard.chsm.mapper.ApplicationMapper;
import com.sunyard.chsm.mapper.KeyInfoMapper;
import com.sunyard.chsm.mapper.SpKeyRecordMapper;
import com.sunyard.chsm.model.dto.CertDTO;
import com.sunyard.chsm.model.entity.AppCert;
import com.sunyard.chsm.model.entity.Application;
import com.sunyard.chsm.model.entity.KeyInfo;
import com.sunyard.chsm.model.entity.KeyRecord;
import com.sunyard.chsm.sdf.SdfApiService;
import com.sunyard.chsm.service.AppCertService;
import com.sunyard.chsm.utils.gm.BCECUtils;
import com.sunyard.chsm.utils.gm.BCSM2Utils;
import com.sunyard.chsm.utils.gm.BCSM4Utils;
import com.sunyard.chsm.utils.gm.cert.BCSM2CertUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.tuple.Pair;
import org.bouncycastle.asn1.ASN1BitString;
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1OctetString;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DLSequence;
import org.bouncycastle.asn1.gm.GMObjectIdentifiers;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.util.BigIntegers;
import org.bouncycastle.util.encoders.Hex;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.security.PublicKey;
import java.security.cert.X509Certificate;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.Base64;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * @author liulu
 * @since 2024/11/6
 */
@Slf4j
@Service
@Transactional
public class AppCertServiceImpl implements AppCertService {

    @Resource
    private AppCertMapper appCertMapper;
    @Resource
    private ApplicationMapper applicationMapper;
    @Resource
    private SpKeyRecordMapper spKeyRecordMapper;
    @Resource
    private KeyInfoMapper keyInfoMapper;
    @Resource
    private SdfApiService sdfApiService;

    @Override
    public Page<CertDTO.ACView> selectPageList(CertDTO.Query query) {

        IPage<AppCert> page = appCertMapper.selectPage(
                new Page<>(query.getPageNumber(), query.getPageSize()),
                new LambdaQueryWrapper<AppCert>()
                        .eq(Objects.nonNull(query.getAppId()), AppCert::getApplicationId, query.getAppId())
                        .eq(StringUtils.hasText(query.getCertType()), AppCert::getCertType, query.getCertType())
                        .orderByDesc(AppCert::getCreateTime)
        );
        List<AppCert> records = page.getRecords();
        if (CollectionUtils.isEmpty(records)) {
            return new Page<>(page.getCurrent(), page.getSize(), page.getTotal());
        }
        List<Long> appIds = records.stream().map(AppCert::getApplicationId).collect(Collectors.toList());
        Map<Long, String> appNameMap = applicationMapper.selectBatchIds(appIds)
                .stream().collect(Collectors.toMap(Application::getId, Application::getName));

        List<CertDTO.ACView> viewList = records.stream()
                .map(it -> {
                    CertDTO.ACView view = new CertDTO.ACView();
                    BeanUtils.copyProperties(it, view);
                    Optional.ofNullable(it.getApplicationId()).map(appNameMap::get).ifPresent(view::setAppName);
                    KeyUsage keyUsage = KeyUsage.valueOf(it.getCertType().toUpperCase());
                    switch (keyUsage) {
                        case ENCRYPT_DECRYPT:
                            view.setCertTypeText("加密证书");
                            break;
                        case SIGN_VERIFY:
                            view.setCertTypeText("签名证书");
                            break;
                    }
                    return view;
                })
                .collect(Collectors.toList());

        return new Page<CertDTO.ACView>(page.getCurrent(), page.getSize(), page.getTotal()).setRecords(viewList);
    }

    @Override
    public void importCert(CertDTO.ImportCert importCert) {
        if (importCert.getSingle()) {
            importSingleCert(importCert);
            return;
        }
        importDoubleCert(importCert);
    }


    private void importSingleCert(CertDTO.ImportCert importCert) {

        Assert.hasText(importCert.getCertText(), "证书内容不能为空");
        Assert.hasText(importCert.getCertType(), "证书类型不能为空");


        X509Certificate x509Cert;
        try {
            x509Cert = BCSM2CertUtils.getX509Cert(importCert.getCertText());
        } catch (Exception ex) {
            throw new IllegalArgumentException("证书内容格式错误,无法解析");
        }
        PublicKey publicKey = x509Cert.getPublicKey();
        String hexPubKey = BCECUtils.getHexPubKey((BCECPublicKey) publicKey);

        KeyRecord record = spKeyRecordMapper.selectByPubKey(hexPubKey);
        Assert.notNull(record, "此证书不是本系统生成P10请求生成的, 无法导入");
        KeyInfo keyInfo = keyInfoMapper.selectById(record.getKeyId());
        Assert.notNull(keyInfo, "密钥信息为空, 数据异常");
        LocalDateTime now = LocalDateTime.now();
        Assert.isTrue(KeyCategory.ASYM_KEY.getCode().equals(keyInfo.getKeyType())
                        && KeyStatus.ENABLED.getCode().equals(keyInfo.getStatus())
                        && now.isAfter(keyInfo.getEffectiveTime())
                        && now.isBefore(keyInfo.getExpiredTime()),
                "此证书对应的密钥ID:" + keyInfo.getId() + "不是启用状态,无法导入");

        AppCert exist = appCertMapper.selectBySN(x509Cert.getSerialNumber().toString());
        Assert.isNull(exist, "此证书已经存在");

        AppCert cert = genCert(x509Cert, keyInfo.getApplicationId(), record, importCert);
        appCertMapper.insert(cert);
    }


    private void importDoubleCert(CertDTO.ImportCert importCert) {

        Assert.hasText(importCert.getSignCertText(), "签名证书内容不能为空");
        Assert.hasText(importCert.getEncCertText(), "加密证书不能为空");
        Assert.hasText(importCert.getEnvelopedKey(), "加密密钥信封不能为空");

        X509Certificate signCert, encCert;
        try {
            signCert = BCSM2CertUtils.getX509Cert(importCert.getSignCertText());
            encCert = BCSM2CertUtils.getX509Cert(importCert.getEncCertText());
        } catch (Exception ex) {
            throw new IllegalArgumentException("证书内容格式错误,无法解析");
        }
        Assert.isTrue(Objects.equals(signCert.getSubjectX500Principal().getName(), encCert.getSubjectX500Principal().getName()),
                "证书主题不一致");
        PublicKey signPk = signCert.getPublicKey();
        String signPkHex = BCECUtils.getHexPubKey((BCECPublicKey) signPk);
        String encPkHex = BCECUtils.getHexPubKey((BCECPublicKey) encCert.getPublicKey());

        KeyRecord record = spKeyRecordMapper.selectByPubKey(signPkHex);
        Assert.notNull(record, "签名证书不是本系统生成P10请求生成的, 无法导入");
        KeyInfo keyInfo = keyInfoMapper.selectById(record.getKeyId());
        Assert.notNull(keyInfo, "密钥信息为空, 数据异常");

        // 验证数字信封
        byte[] priKey = sdfApiService.decryptByTMK(Hex.decode(record.getKeyData()));
        byte[] enveloped = Base64.getDecoder().decode(importCert.getEnvelopedKey());
        Pair<String, byte[]> keys;
        try {
            keys = decryptEnvelopedKey(priKey, enveloped);
        } catch (Exception ex) {
            log.error("解密加密密钥信封异常", ex);
            throw new IllegalArgumentException("加密密钥信封格式错误,解密失败");
        }
        Assert.isTrue(Objects.equals(encPkHex, keys.getLeft()), "加密证书和私钥不匹配");

        AppCert exist = appCertMapper.selectBySN(signCert.getSerialNumber().toString());
        Assert.isNull(exist, "签名证书已经存在");
        exist = appCertMapper.selectBySN(encCert.getSerialNumber().toString());
        Assert.isNull(exist, "加密证书已经存在");

        importCert.setCertType(KeyUsage.SIGN_VERIFY.getCode());
        importCert.setCertText(importCert.getSignCertText());
        AppCert sign = genCert(signCert, keyInfo.getApplicationId(), record, importCert);
        appCertMapper.insert(sign);

        importCert.setCertType(KeyUsage.ENCRYPT_DECRYPT.getCode());
        importCert.setCertText(importCert.getEncCertText());
        AppCert enc = genCert(encCert, keyInfo.getApplicationId(), record, importCert);
        enc.setPubKey(keys.getLeft());
        byte[] encPri = sdfApiService.encryptByTMK(keys.getRight());
        enc.setEncPriKey(Hex.toHexString(encPri));
        appCertMapper.insert(enc);

    }

    private AppCert genCert(X509Certificate x509Cert, Long appId, KeyRecord record, CertDTO.ImportCert importCert) {
        AppCert cert = new AppCert();
        cert.setId(IdWorker.getId());
        cert.setApplicationId(appId);
        cert.setKeyId(record.getKeyId());
        cert.setKeyRecordId(record.getId());

        cert.setKeyAlg(importCert.getKeyAlg());
        cert.setCertType(importCert.getCertType());
        cert.setSingle(importCert.getSingle());
        cert.setCertText(importCert.getCertText());

        cert.setVersion(String.valueOf(x509Cert.getVersion()));
        cert.setSubject(x509Cert.getSubjectX500Principal().getName());
        cert.setSerialNumber(x509Cert.getSerialNumber().toString());
        cert.setIssuerDn(x509Cert.getIssuerX500Principal().getName());
        cert.setNotBefore(x509Cert.getNotBefore());
        cert.setNotAfter(x509Cert.getNotAfter());

        cert.setPubKey(record.getPubKey());
        cert.setEncPriKey(record.getKeyData());
        return cert;
    }


    private static Pair<String, byte[]> decryptEnvelopedKey(byte[] d, byte[] envelopedKey) throws Exception {
        ECPrivateKeyParameters pri = BCECUtils.createECPrivateKeyParameters(d);
        ASN1Sequence seq = DLSequence.getInstance(envelopedKey);

        AlgorithmIdentifier oid = AlgorithmIdentifier.getInstance(seq.getObjectAt(0));
        ASN1ObjectIdentifier sm4 = GMObjectIdentifiers.sm_scheme.branch("104");
        Assert.isTrue(oid.getAlgorithm().equals(sm4) || oid.getAlgorithm().on(sm4), "不支持的对称算法:" + oid.getAlgorithm());

        ASN1Sequence ccseq = DLSequence.getInstance(seq.getObjectAt(1));
        BigInteger x = ASN1Integer.getInstance(ccseq.getObjectAt(0)).getValue();
        BigInteger y = ASN1Integer.getInstance(ccseq.getObjectAt(1)).getValue();
        byte[] m = ASN1OctetString.getInstance(ccseq.getObjectAt(2)).getOctets();
        byte[] c = ASN1OctetString.getInstance(ccseq.getObjectAt(3)).getOctets();

        ByteBuffer buffer = ByteBuffer.allocate(65 + m.length + c.length);
        buffer.put((byte) 4);
        buffer.put(BigIntegers.asUnsignedByteArray(32, x));
        buffer.put(BigIntegers.asUnsignedByteArray(32, y));
        buffer.put(m);
        buffer.put(c);

        byte[] symk = BCSM2Utils.decrypt(pri, buffer.array());

        byte[] xy = ASN1BitString.getInstance(seq.getObjectAt(2)).getBytes();
        byte[] ed = ASN1BitString.getInstance(seq.getObjectAt(3)).getBytes();

        byte[] pd = BCSM4Utils.decrypt_ECB_NoPadding(symk, ed);

        if (xy.length == 65) {
            xy = Arrays.copyOfRange(xy, 1, xy.length);
        }

        return Pair.of(Hex.toHexString(xy), pd);
    }


    @Override
    public void delete(Long id) {

        AppCert appCert = appCertMapper.selectById(id);
        Assert.notNull(appCert, "证书不存在");

        if (appCert.getSingle()) {
            appCertMapper.deleteById(id);
            return;
        }

        List<AppCert> appCerts = appCertMapper.selectList(
                new LambdaQueryWrapper<AppCert>()
                        .eq(AppCert::getKeyId, appCert.getKeyId())
                        .eq(AppCert::getSubject, appCert.getSubject())

        );
        appCertMapper.deleteBatchIds(appCerts.stream().map(AppCert::getId).collect(Collectors.toList()));
    }

}
