package com.fzm.mall.server.admin.util;

import com.alibaba.fastjson.JSONObject;
import com.fzm.mall.server.admin.config.WeChatPayConfig;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.AutoUpdateCertificatesVerifier;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import com.wechat.pay.contrib.apache.httpclient.util.RsaCryptoUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

/**
 * @author wangp
 * @date 2021/4/15 15:37
 * @description 微信支付http工具类
 * @since JDK 1.8
 */
@Slf4j
@Component
public class WxPayUtil {

    @Autowired
    private WeChatPayConfig config;

    private static CloseableHttpClient httpClient = null;
    private static RequestConfig requestConfig = null;
    private static AutoUpdateCertificatesVerifier verifier = null;
    private static PrivateKey merchantPrivateKey = null;
    private static AesUtil aesUtil = null;

    private static String transaction = "TRANSACTION.SUCCESS";
    public static final String REFUND_SUCCESS = "REFUND.SUCCESS";
    public static final String REFUND_ABNORMAL = "REFUND.ABNORMAL";
    public static final String REFUND_CLOSED = "REFUND.CLOSED";
    private static String description = "充值";
    private static String paySign = "%s\n%s\n%s\n%s\n";
    private static String signType = "RSA";
    private static String charSet = "UTF-8";
    private static String contentType = "application/json";
    private static String algorithm = "SHA256withRSA";

    private static Map<String, X509Certificate> certSet = new HashMap<>();

    @PostConstruct
    public void setup() {
        // 加载商户私钥（privateKey：私钥字符串）
        merchantPrivateKey = PemUtil
                .loadPrivateKey(new ByteArrayInputStream(config.getPrivateKey().getBytes(StandardCharsets.UTF_8)));

        // 加载平台证书（mchId：商户号,mchSerialNo：商户证书序列号,apiV3Key：V3秘钥）
        verifier = new AutoUpdateCertificatesVerifier(
                new WechatPay2Credentials(config.getMchId(), new PrivateKeySigner(config.getMchSerialNo(), merchantPrivateKey)),
                config.getApiV3Key().getBytes(StandardCharsets.UTF_8));

        // 初始化httpClient，通过WechatPayHttpClientBuilder构造的HttpClient，会自动的处理签名和验签，并进行证书自动更新
        httpClient = WechatPayHttpClientBuilder.create()
                .withMerchant(config.getMchId(), config.getMchSerialNo(), merchantPrivateKey)
                .withValidator(new WechatPay2Validator(verifier)).build();

        requestConfig = RequestConfig.custom()
                //设置连接超时
                .setConnectTimeout(35000)
                //设置从连接池获取连接实例的超时
                .setConnectionRequestTimeout(35000)
                //设置读取超时
                .setSocketTimeout(60000)
                .build();

        aesUtil = new AesUtil(config.getApiV3Key().getBytes(StandardCharsets.UTF_8));
    }

    public String weChatPayHttpRequest(HttpRequestBase base) {
        String result;
        base.setConfig(requestConfig);
        JSONObject json = new JSONObject();

        try (CloseableHttpResponse response = httpClient.execute(base)) {
            result = EntityUtils.toString(response.getEntity());
            int statusCode = response.getStatusLine().getStatusCode();
            json.put("code", statusCode);
            json.put("body", result);
            if (statusCode == 200) { //处理成功
                log.info("success,return body = " + result);
            } else if (statusCode == 204) { //处理成功，无返回Body
                log.info("success");
            } else {
                throw new RuntimeException("failed,resp code = " + statusCode + ",return body = " + result);
            }
        } catch (IOException e) {
            log.info(e.getMessage());
        }

        return json.toString();
    }

    /**
     * RSA加密
     *
     * @param message
     * @return
     * @throws IllegalBlockSizeException
     * @throws IOException
     */
    public String rsaEncryptOAEP(String message) {
        String result;
        // 建议从Verifier中获得微信支付平台证书，或使用预先下载到本地的平台证书文件中
        X509Certificate wechatpayCertificate = verifier.getValidCertificate();
        try {
            result = RsaCryptoUtil.encryptOAEP(message, wechatpayCertificate);
        } catch (IllegalBlockSizeException e) {
            throw new RuntimeException(e);
        }
        return result;
    }

    /**
     * 商户私钥解密
     *
     * @param ciphertext
     * @return
     * @throws BadPaddingException
     * @throws IOException
     */
    public String rsaDecryptOAEP(String ciphertext) {
        String result;
        // 使用商户私钥解密
        try {
            result = RsaCryptoUtil.decryptOAEP(ciphertext, merchantPrivateKey);
        } catch (BadPaddingException e) {
            throw new RuntimeException(e);
        }
        return result;
    }

    public String getBodytxt(HttpServletRequest request) {
        String str, wholeStr = "";
        try {
            BufferedReader br = request.getReader();
            while ((str = br.readLine()) != null) {
                wholeStr += str;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return wholeStr;
    }

    /**
     * 验签
     *
     * @param serialNo
     * @param verStr
     * @param signStr
     * @throws Exception
     */
    public void verify(String serialNo, String verStr, String signStr) {
        try {
            Certificate cerCert = certSet.get(serialNo);
            if (null == cerCert) {
                this.freshCert();
                cerCert = certSet.get(serialNo);
            }
            PublicKey publicKey = cerCert.getPublicKey();
            byte[] signed = Base64.getDecoder().decode(signStr);
            Signature sign = Signature.getInstance(algorithm);
            sign.initVerify(publicKey);
            sign.update(verStr.getBytes(charSet));
            if (!sign.verify(signed)) {
                throw new RuntimeException("签名验证失败");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取微信支付平台证书
     *
     * @throws Exception
     */
    private void freshCert() throws Exception {
        HttpGet httpGet = new HttpGet(config.getCertUrl());
        httpGet.setHeader("Accept", contentType);
        httpGet.setHeader("User-Agent", "https://zh.wikipedia.org/wiki/User_agent");
        JSONObject json = JSONObject.parseObject(weChatPayHttpRequest(httpGet));
        for (Object o : json.getJSONArray("data")) {
            JSONObject certificate = JSONObject.parseObject(o.toString());
            JSONObject encryptCertificate = certificate.getJSONObject("encrypt_certificate");
            String cert = aesUtil.decryptToString(encryptCertificate.getString("associated_data").getBytes(charSet), encryptCertificate.getString("nonce").getBytes(charSet), encryptCertificate.getString("ciphertext"));
            X509Certificate weixinCert = PemUtil.loadCertificate(new ByteArrayInputStream(cert.getBytes(charSet)));
            certSet.put(certificate.getString("serial_no"), weixinCert);
        }
    }

    public JSONObject notify(JSONObject data) {
        try {
            if (transaction.equals(data.get("event_type"))) {
                JSONObject resource = data.getJSONObject("resource");
                String notifyData = aesUtil.decryptToString(resource.getString("associated_data").getBytes(StandardCharsets.UTF_8), resource.getString("nonce").getBytes(StandardCharsets.UTF_8), resource.getString("ciphertext"));
                JSONObject notifyJsonData = JSONObject.parseObject(notifyData);
                notifyJsonData.getString("mchid");
                notifyJsonData.getString("appid");
                notifyJsonData.getString("out_trade_no");
                notifyJsonData.getString("trade_state");
                notifyJsonData.getString("success_time");
                notifyJsonData.getJSONObject("payer").getString("openid");
                notifyJsonData.getJSONObject("amount");
                return notifyJsonData;
            } else {
                return null;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public JSONObject refundNotify(JSONObject data) {
        JSONObject result = new JSONObject();
        try {
            if (REFUND_SUCCESS.equals(data.get("event_type"))) {
                JSONObject resource = data.getJSONObject("resource");
                String notifyData = aesUtil.decryptToString(resource.getString("associated_data").getBytes(StandardCharsets.UTF_8), resource.getString("nonce").getBytes(StandardCharsets.UTF_8), resource.getString("ciphertext"));
                result = JSONObject.parseObject(notifyData);
                result.put("event_type", REFUND_SUCCESS);
            } else {
                result.put("event_type", data.get("event_type"));
                result.put("summary", data.get("summary"));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    public String doHttpPost(String url, String reqdata) {
        HttpPost httpPost = new HttpPost(url);
        // 请求body参数
        StringEntity entity = new StringEntity(reqdata, "UTF-8");
        entity.setContentType("application/json");
        httpPost.setEntity(entity);
        httpPost.setHeader("Accept", "application/json");
        //完成签名并执行请求
        return weChatPayHttpRequest(httpPost);
    }
}
