package me.chanjar.weixin.cp.util.crypto;

import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.common.util.crypto.WxCryptUtil;
import me.chanjar.weixin.cp.config.WxCpConfigStorage;
import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.asn1.pkcs.RSAPrivateKey;

import javax.crypto.Cipher;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.RSAPrivateCrtKeySpec;
import java.util.Base64;
import java.util.Objects;

/**
 * The type Wx cp crypt util.
 *
 * @author qian
 */
public class WxCpCryptUtil extends WxCryptUtil {
  /**
   * Instantiates a new Wx cp crypt util.
   *
   * @param wxCpConfigStorage the wx cp config storage
   */
  public WxCpCryptUtil(WxCpConfigStorage wxCpConfigStorage) {
    /*
     * @param token          公众平台上，开发者设置的token
     * @param encodingAesKey 公众平台上，开发者设置的EncodingAESKey
     * @param appidOrCorpid          公众平台appid
     */
    String encodingAesKey = wxCpConfigStorage.getAesKey();
    String token = wxCpConfigStorage.getToken();
    String corpId = wxCpConfigStorage.getCorpId();

    this.token = token;
    this.appidOrCorpid = corpId;
    this.aesKey = Base64.getDecoder().decode(StringUtils.remove(encodingAesKey, " "));
  }

  /**
   * 判断使用PKCS8或者PKCS1进行解密
   *
   * @param encryptRandomKey 使用PUBLICKEY_VER指定版本的公钥进行非对称加密后base64加密的内容
   * @param msgAuditPriKey   会话存档私钥
   * @param pkcs1            使用什么方式进行解密，1代表使用PKCS1进行解密，2代表PKCS8进行解密 ...
   * @return string
   * @throws Exception the exception
   */
  public static String decryptPriKey(String encryptRandomKey, String msgAuditPriKey, Integer pkcs1) throws Exception {
    if (Objects.isNull(pkcs1)) {
      throw new WxErrorException("请配置会话存档解密方式");
    }

    if (Objects.equals(pkcs1, 1)) {
      return decryptPriKeyByPKCS1(encryptRandomKey, msgAuditPriKey);
    }

    return decryptPriKeyByPKCS8(encryptRandomKey, msgAuditPriKey);
  }

  /**
   * PKCS8 解密私钥
   *
   * @param encryptRandomKey the encrypt random key
   * @param msgAuditPriKey   the msg audit pri key
   * @return string
   * @throws Exception the exception
   */
  public static String decryptPriKeyByPKCS8(String encryptRandomKey, String msgAuditPriKey) throws Exception {
    String privateKey = msgAuditPriKey.replaceAll("(\r\n|\r|\n|\n\r)", "")
      .replace("-----BEGIN PRIVATE KEY-----", "")
      .replace("-----END PRIVATE KEY-----", "")
      .replace(" ", "");

    byte[] keyBytes = Base64.getDecoder().decode(privateKey);
    PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
    KeyFactory keyFactory = KeyFactory.getInstance("RSA");
    PrivateKey priKey = keyFactory.generatePrivate(pkcs8KeySpec);

    Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
    cipher.init(Cipher.DECRYPT_MODE, priKey);
    byte[] utf8 = cipher.doFinal(Base64.getDecoder().decode(encryptRandomKey));
    return new String(utf8, StandardCharsets.UTF_8);
  }

  /**
   * 会话存档，PKCS1 解密私钥
   * 企业获取的会话内容将用公钥加密，企业用自行保存的私钥解开会话内容数据
   *
   * @param encryptRandomKey 使用PUBLICKEY_VER指定版本的公钥进行非对称加密后base64加密的内容，需要业务方先base64
   *                         decode处理后，再使用指定版本的私钥进行解密，得出内容。String类型
   * @param msgAuditPriKey   会话存档私钥
   * @return string
   * @throws Exception the exception
   */
  public static String decryptPriKeyByPKCS1(String encryptRandomKey, String msgAuditPriKey) throws Exception {
    String privateKey = msgAuditPriKey.replaceAll("(\r\n|\r|\n|\n\r)", "")
      .replace("-----BEGIN RSA PRIVATE KEY-----", "")
      .replace("-----END RSA PRIVATE KEY-----", "")
      .replace(" ", "");

    byte[] keyBytes = Base64.getDecoder().decode(privateKey);
    // Java 8 以后 sun.security.util.DerInputStream 和 sun.security.util.DerValue 无法使用
    // 因此改为通过 org.bouncycastle:bcprov-jdk18on 来完成 ASN1 编码数据解析
    final RSAPrivateKey key = RSAPrivateKey.getInstance(keyBytes);
    final RSAPrivateCrtKeySpec keySpec = new RSAPrivateCrtKeySpec(
      key.getModulus(),
      key.getPublicExponent(),
      key.getPrivateExponent(),
      key.getPrime1(),
      key.getPrime2(),
      key.getExponent1(),
      key.getExponent2(),
      key.getCoefficient());

    PrivateKey priKey = KeyFactory.getInstance("RSA").generatePrivate(keySpec);
    Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
    cipher.init(Cipher.DECRYPT_MODE, priKey);
    byte[] utf8 = cipher.doFinal(Base64.getDecoder().decode(encryptRandomKey));
    return new String(utf8, StandardCharsets.UTF_8);
  }

}
