package cn.quant.baa.pay.rest;

import cn.quant.baa.pay.acquirer.AcquirerConfiguration;
import cn.quant.baa.pay.acquirer.AcquirerProperties;
import cn.quant.baa.pay.dict.AccessCode;
import cn.quant.baa.pay.jpa.entity.PayHistoryEntity;
import cn.quant.baa.pay.model.BusinessRequest;
import cn.quant.baa.pay.model.web.PayRequestData;
import cn.quant.baa.pay.service.TransactionService;
import cn.quant.spring.rest.AbstractController;
import com.alibaba.fastjson.JSON;
import com.alipay.api.AlipayClient;
import com.alipay.api.CertAlipayRequest;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.domain.AlipayTradeAppPayModel;
import com.alipay.api.domain.AlipayTradePagePayModel;
import com.alipay.api.domain.AlipayTradeWapPayModel;
import com.alipay.api.request.AlipayTradeAppPayRequest;
import com.alipay.api.request.AlipayTradePagePayRequest;
import com.alipay.api.request.AlipayTradeWapPayRequest;
import com.alipay.api.response.AlipayTradePagePayResponse;
import com.alipay.api.response.AlipayTradeWapPayResponse;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
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.PemUtil;
import org.apache.http.HttpEntity;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.message.BasicHeader;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.*;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
import sun.misc.BASE64Encoder;

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.*;
import java.security.cert.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static org.apache.http.entity.ContentType.APPLICATION_JSON;

/**
 * Created with IntelliJ IDEA.
 * Author: Lipeng Liu
 * Date: 2021/8/31
 * Time: 上午10:36
 * Description: No Description
 */
@RestController
@RequestMapping("wxpay")
public class WxpayController extends AbstractController {

    @Autowired
    private TransactionService transactionService;

    @Autowired
    private AcquirerConfiguration acquirerConfiguration;

    String TOKEN_PATTERN = "mchid=\"%s\",nonce_str=\"%s\",timestamp=\"%d\",serial_no=\"%s\",signature=\"%s\"";
    String SCHEMA = "WECHATPAY2-SHA256-RSA2048 ";

    WebClient webClient = WebClient.builder()
            .defaultHeader("Accept", "application/json")
            .defaultHeader("User-Agent", "QuantGroup Java Api")
            .build();

//    public HttpEntity buildHttpEntity(String url, String body, String method) {
//        String nonceStr = SignTextUtils.randomStr();
//        long timestamp = DateUtils.toEpochSecond();
//        String canonicalUrl = UriVariables.getCanonicalUrl(url);
//        //签名信息
//        String signText = StringUtils.joining("\n", method, canonicalUrl, String.valueOf(timestamp), nonceStr, body);
//        String sign = wxPayService.createSign(signText, payConfigStorage.getInputCharset());
//        String serialNumber = payConfigStorage.getCertEnvironment().getSerialNumber();
//        // 生成token
//        String token = String.format(WxConst.TOKEN_PATTERN, payConfigStorage.getMchId(), nonceStr, timestamp, serialNumber, sign);
//        HttpStringEntity entity = new HttpStringEntity(body, ContentType.APPLICATION_JSON);
//        entity.addHeader(new BasicHeader("Authorization", WxConst.SCHEMA.concat(token)));
//        entity.addHeader(new BasicHeader("User-Agent", "Pay-Java-Parent"));
//        entity.addHeader(new BasicHeader("Accept", APPLICATION_JSON.getMimeType()));
//        return entity;
//    }


    @ResponseBody
    @GetMapping("/webClient")
    public String webClient() throws Exception {
        String chanId = "75772285618946307";
        AcquirerProperties profile = acquirerConfiguration.get(Long.valueOf(chanId));

        ObjectMapper objectMapper = new ObjectMapper();
        String outTradeNo = UUID.randomUUID().toString().replace("-", "");
        ObjectNode rootNode = new ObjectMapper().createObjectNode();
        rootNode.put("mchid", profile.getPayAcctId())
                .put("appid", profile.getPayAppId())
                .put("description", "Image形象店-深圳腾大-QQ公仔")
                .put("notify_url", "https://www.weixin.qq.com/wxpay/pay.php")
                .put("out_trade_no", outTradeNo);
        rootNode.putObject("amount")
                .put("total", 1);
        if (profile.getAccessCode().equals(AccessCode.JS)) {
            rootNode.putObject("payer")
                    .put("openid", "oUpF8uMuAJO_M2pxb1Q9zNjWeS6o");
        } else if (profile.getAccessCode().equals(AccessCode.H5)) {
            rootNode.putObject("scene_info")
                    .put("payer_client_ip", "127.0.0.1")
                    .putObject("h5_info").put("type", "Wap");
        }

        String body = rootNode.toString();
        long timestamp = System.currentTimeMillis() / 1000;
        String nonceStr = UUID.randomUUID().toString().replace("-", "");
        String signText = joining("\n", "POST", profile.getPayAccess(), String.valueOf(timestamp), nonceStr, body);
        String sign = sign(signText, getPrivateKey(profile.getPrivateKey()), profile.getSignType());
        String token = String.format(TOKEN_PATTERN, profile.getPayAcctId(), nonceStr, timestamp, profile.getPayCertNo(), sign);
        String uri = String.format("%s%s", profile.getDomain(), profile.getPayAccess());
        WebClient.RequestBodySpec requestHeadersSpec = webClient.method(HttpMethod.POST).uri(uri)
                .header("Authorization", SCHEMA.concat(token))
                ;
//        Mono<String> mono = requestHeadersSpec.bodyValue(rootNode).retrieve().bodyToMono(String.class);
        Mono<ClientResponse> mono = requestHeadersSpec.bodyValue(rootNode).exchange();
        ClientResponse response = mono.block();
        mono.subscribe(System.out::println);
        if (response.statusCode() == HttpStatus.OK) {
            //当前使用的微信平台证书序列号
            String serial = response.headers().asHttpHeaders().getFirst("Wechatpay-Serial");
            //微信服务器的时间戳
            String wxTimestamp = response.headers().asHttpHeaders().getFirst("Wechatpay-Timestamp");
            //微信服务器提供的随机串
            String nonce = response.headers().asHttpHeaders().getFirst("Wechatpay-Nonce");
            //微信平台签名
            String signature = response.headers().asHttpHeaders().getFirst("Wechatpay-Signature");

            Mono<String> resultMono = response.bodyToMono(String.class);

            //签名信息
            String wsSignText = joining("\n", wxTimestamp, nonce, resultMono.block());
            X509Certificate x509Certificate = loadCertificate(profile.getPayCertKey());
            if (verify(wsSignText, signature, x509Certificate, profile.getSignType())) {
                return resultMono.block();
            }
            throw new Exception("");
        }
        return "";
    }

    public static void main(String[] args) {
        String str = "{\"alipay_trade_query_response\":{\"code\":\"40004\",\"msg\":\"Business Failed\",\"sub_code\":\"ACQ.TRADE_NOT_EXIST\",\"sub_msg\":\"交易不存在\",\"buyer_pay_amount\":\"0.00\",\"invoice_amount\":\"0.00\",\"out_trade_no\":\"123123123\",\"point_amount\":\"0.00\",\"receipt_amount\":\"0.00\"},\"alipay_cert_sn\":\"dbffe2eea8c336d4bdfece3e78b295f2\",\"sign\":\"HOJvXeUMzuMAAcV2SEtOHC1/8CG2ycbytAQyIoqGwSlKnNIwmGVna2gGWAQskNrufm8nhO5y9oCrC8biwVL2rGdkxTTYCNiSV9uKdGYVZhTutxaKgrJH9VQ5oV5ur24mZQO4rG5xAI6hyExr0vZeDOe7L5Io7xs3YajdcJOvdQVCqdqCyMY7ZDhoptRMxtbpBJwy28KjTRwAEpW687+4Xd2zmCm1HLcFrcO34inMip6jQlkD9a8l5ehjIlidfLGDCRXveIAHJ6RzhfGNdHYauuHP1os66eH3J7IoqJCRsUtNQYoXQWhzR3YzPv8k7l71IOPNrxVuuweM64hGZB27Iw==\"}";
        Pattern pattern = Pattern.compile("[a-zA-Z_0-9]*_response"); //去掉空格符合换行符
        Matcher matcher = pattern.matcher(str);
        String result = matcher.replaceAll("response");
        System.out.println(result);
    }

    @ResponseBody
    @GetMapping("/h5")
    public String h5() throws Exception {

        String chanId = "75772285618946307";
        AcquirerProperties profile = acquirerConfiguration.get(Long.valueOf(chanId));
//        PayHistoryEntity pay = transactionService.pay(data);

//        // 加载平台证书（mchId：商户号,mchSerialNo：商户证书序列号,apiV3Key：V3密钥）
//        AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(
//                new WechatPay2Credentials(profile.getPayAcctId(), new PrivateKeySigner(certEnvironment.getSerialNumber(), certEnvironment.getPrivateKey())),
//                profile.getSecretKey().getBytes("utf-8")
//        );
        List<X509Certificate> certificates = new ArrayList<>();
        X509Certificate x509Certificate = loadCertificate(profile.getPayCertKey());
//        PublicKey publicKey = getPublicKey(profile.getPayPublicKey());
        certificates.add(x509Certificate);
        WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
                .withMerchant(profile.getPayAcctId(), profile.getPayCertNo(), getPrivateKey(profile.getPrivateKey()))
                .withWechatpay(certificates);
//
//        // ... 接下来，你仍然可以通过builder设置各种参数，来配置你的HttpClient
//        // 通过WechatPayHttpClientBuilder构造的HttpClient，会自动的处理签名和验签
        CloseableHttpClient httpClient = builder.build();
        // 初始化httpClient
//        CloseableHttpClient httpClient = WechatPayHttpClientBuilder.create()
//                .withMerchant(profile.getPayAcctId(), certEnvironment.getSerialNumber(), certEnvironment.getPrivateKey())
//                .withValidator(new WechatPay2Validator(verifier)).build();
        String uri = String.format("%s%s", profile.getDomain(), profile.getPayAccess());
        HttpPost httpPost = new HttpPost(uri);
        httpPost.addHeader("Accept", "application/json");
        httpPost.addHeader("Content-type", "application/json; charset=utf-8");

        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectMapper objectMapper = new ObjectMapper();

        String outTradeNo = UUID.randomUUID().toString().replace("-", "");
        ObjectNode rootNode = objectMapper.createObjectNode();
        rootNode.put("mchid", profile.getPayAcctId())
                .put("appid", profile.getPayAppId())
                .put("description", "Image形象店-深圳腾大-QQ公仔")
                .put("notify_url", "https://www.weixin.qq.com/wxpay/pay.php")
                .put("out_trade_no", outTradeNo);
        rootNode.putObject("amount")
                .put("total", 1);
        if (profile.getAccessCode().equals(AccessCode.JS)) {
            rootNode.putObject("payer")
                    .put("openid", "oUpF8uMuAJO_M2pxb1Q9zNjWeS6o");
        } else if (profile.getAccessCode().equals(AccessCode.H5)) {
            rootNode.putObject("scene_info")
                    .put("payer_client_ip", "127.0.0.1")
                    .putObject("h5_info").put("type", "Wap");
        }

        objectMapper.writeValue(bos, rootNode);

        httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));
        CloseableHttpResponse response = httpClient.execute(httpPost);

        String bodyAsString = EntityUtils.toString(response.getEntity());
        System.out.println(bos.toString("UTF-8"));
        response.close();
        httpClient.close();
        if (profile.getAccessCode().equals(AccessCode.H5)) {
            return "<html><head><meta name=\"referrer\" content=\"always\"></dead><body>"
                    + "订单号：" + outTradeNo + "<br />"
                    + "<a href=\"" + JSON.parseObject(bodyAsString).getString("h5_url") + "\">去支付</a></body>";
        }
        return bodyAsString;
    }


    @ResponseBody
    @PostMapping("/app")
    public String app(@RequestBody BusinessRequest<PayRequestData> request) throws Exception {

        PayRequestData data = request.getData();
        String chanId = data.getChanId();
        AcquirerProperties profile = acquirerConfiguration.get(Long.valueOf(chanId));
//        PayHistoryEntity pay = transactionService.pay(data);

        FileInputStream inputStream = new FileInputStream("/Users/liulipeng/Web/lianghuapai/baa-pay/baa-pay-server/src/main/resources/wxpay/apiclient_cert_1604055791.p12");
        String keyAlias = "Tenpay Certificate";
        CertEnvironment certEnvironment = initCertification(inputStream, keyAlias, profile.getPayAcctId());
//        // 加载平台证书（mchId：商户号,mchSerialNo：商户证书序列号,apiV3Key：V3密钥）
        AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(
                new WechatPay2Credentials(profile.getPayAcctId(), new PrivateKeySigner(certEnvironment.getSerialNumber(), certEnvironment.getPrivateKey())),
                profile.getSecretKey().getBytes("utf-8")
        );
//        List<X509Certificate> certificates = new ArrayList<>();
//        X509Certificate certificate = getCertificate("/Users/liulipeng/Web/lianghuapai/baa-pay/baa-pay-server/src/main/resources/wxpay/apiclient_cert.p12");
//        certificates.add(certificate);
//        WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
//                .withMerchant(profile.getPayAcctId(), certEnvironment.getSerialNumber(), certEnvironment.getPrivateKey())
//                .withWechatpay(certificates);
//
//        // ... 接下来，你仍然可以通过builder设置各种参数，来配置你的HttpClient
//        // 通过WechatPayHttpClientBuilder构造的HttpClient，会自动的处理签名和验签
//        CloseableHttpClient httpClient = builder.build();
        // 初始化httpClient
        CloseableHttpClient httpClient = WechatPayHttpClientBuilder.create()
                .withMerchant(profile.getPayAcctId(), certEnvironment.getSerialNumber(), certEnvironment.getPrivateKey())
                .withValidator(new WechatPay2Validator(verifier)).build();
        String uri = String.format("%s%s", profile.getDomain(), profile.getPayAccess());
        HttpPost httpPost = new HttpPost(uri);
        httpPost.addHeader("Accept", "application/json");
        httpPost.addHeader("Content-type", "application/json; charset=utf-8");

        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectMapper objectMapper = new ObjectMapper();

        String outTradeNo = UUID.randomUUID().toString().replace("-", "");
        ObjectNode rootNode = objectMapper.createObjectNode();
        rootNode.put("mchid", profile.getPayAcctId())
                .put("appid", profile.getPayAppId())
                .put("description", "Image形象店-深圳腾大-QQ公仔")
                .put("notify_url", "https://www.weixin.qq.com/wxpay/pay.php")
                .put("out_trade_no", outTradeNo);
        rootNode.putObject("amount")
                .put("total", 1);
        if (profile.getAccessCode().equals(AccessCode.JS)) {
            rootNode.putObject("payer")
                    .put("openid", "oUpF8uMuAJO_M2pxb1Q9zNjWeS6o");
        } else if (profile.getAccessCode().equals(AccessCode.H5)) {
            rootNode.putObject("scene_info")
                    .put("payer_client_ip", "127.0.0.1")
                    .putObject("h5_info").put("type", "Wap");
        }

        objectMapper.writeValue(bos, rootNode);

        httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));
        CloseableHttpResponse response = httpClient.execute(httpPost);

        String bodyAsString = EntityUtils.toString(response.getEntity());
        System.out.println(bos.toString("UTF-8"));
        response.close();
        httpClient.close();
        if (profile.getAccessCode().equals(AccessCode.H5)) {
            return "<html><head><meta name=\"referrer\" content=\"always\"></dead><body><a href=\"" + JSON.parseObject(bodyAsString).getString("h5_url") + "\"></a></body>";
        }
        return bodyAsString;
    }

    /**
     * 获取公私钥.
     *
     * @param keyCertStream 商户API证书
     * @param keyAlias      证书别名
     * @param keyPass       证书对应的密码
     * @return 证书信息集合
     */
    public static CertEnvironment initCertification(InputStream keyCertStream, String keyAlias, String keyPass) {

        char[] pem = keyPass.toCharArray();
        try {
            KeyStore keyStore = KeyStore.getInstance("PKCS12");
            keyStore.load(keyCertStream, pem);
            X509Certificate certificate = (X509Certificate) keyStore.getCertificate(keyAlias);
            certificate.checkValidity();
            String serialNumber = certificate.getSerialNumber().toString(16).toUpperCase();
            PublicKey publicKey = certificate.getPublicKey();
            PrivateKey privateKey = (PrivateKey) keyStore.getKey(keyAlias, pem);
            BASE64Encoder base64Encoder = new BASE64Encoder();
            String privateKeyString = base64Encoder.encode(privateKey.getEncoded());
            String publicKeyString = base64Encoder.encode(publicKey.getEncoded());
            System.out.println(publicKeyString);
            return new CertEnvironment(privateKey, publicKey, serialNumber);
        } catch (GeneralSecurityException e) {
//            throw new PayErrorException(new WxPayError(WxConst.FAILURE, "获取公私钥失败"), e);
        } catch (IOException e) {
//            throw new PayErrorException(new WxPayError(WxConst.FAILURE, "私钥证书流加载失败"), e);
        }
        return null;
    }

    /**
     * 字符串数组拼接为字符串
     *
     * @param separator 分隔符
     * @param str       字符数组
     * @return 字符串
     */
    public static String joining(String separator, String... str) {
        StringBuilder builder = new StringBuilder();
        for (String s : str) {
            if (s == null || s.length() == 0) {
                continue;
            }

            builder.append(s).append(separator);
        }
        return builder.toString();
    }

    /**
     * RSA验签名检查
     *
     * @param content           待签名数据
     * @param signature              签名值
     * @param signAlgorithms    签名算法
     * @return 布尔值
     */
    public static boolean verify(String content, String signature, X509Certificate certificate, String signAlgorithms) {
        try {
            Signature sign = Signature.getInstance("SHA256withRSA");
            sign.initVerify(certificate);
            sign.update(content.getBytes());
            return sign.verify(Base64.getDecoder().decode(signature));
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("当前Java环境不支持SHA256withRSA", e);
        } catch (SignatureException e) {
            throw new RuntimeException("签名验证过程发生了错误", e);
        } catch (InvalidKeyException e) {
            throw new RuntimeException("无效的证书", e);
        }
    }



    public static String sign(String content, PrivateKey privateKey, String signAlgorithms) {
        try {
            java.security.Signature signature = java.security.Signature.getInstance(signAlgorithms);
            signature.initSign(privateKey);
            signature.update(content.getBytes());
            byte[] signed = signature.sign();
            return Base64.getEncoder().encodeToString(signed);
        }
        catch (GeneralSecurityException e) {
        }

        return null;
    }



    /**
     * 获取证书。
     *
     * @param certString 证书内容
     * @return X509证书
     */
    public static X509Certificate loadCertificate(String certString) {
        try {
            ByteArrayInputStream fis = new ByteArrayInputStream(certString.getBytes());
            CertificateFactory cf = CertificateFactory.getInstance("X509");
            X509Certificate cert = (X509Certificate) cf.generateCertificate(fis);
            cert.checkValidity();
            return cert;
        } catch (CertificateExpiredException var3) {
            throw new RuntimeException("证书已过期", var3);
        } catch (CertificateNotYetValidException var4) {
            throw new RuntimeException("证书尚未生效", var4);
        } catch (CertificateException var5) {
            throw new RuntimeException("无效的证书", var5);
        }
    }

    /**
     * 获取私钥。
     *
     * @param certString 公钥文件内容(required)
     * @return 私钥对象
     */
    public static PublicKey getPublicKey(String certString) throws IOException {
        try {
            String publicKey = certString
                    .replace("-----BEGIN PUBLIC KEY-----", "")
                    .replace("-----END PUBLIC KEY-----", "")
                    .replaceAll("\\s+", "");

            KeyFactory kf = KeyFactory.getInstance("RSA");
            return kf.generatePublic(
                    new X509EncodedKeySpec(Base64.getDecoder().decode(publicKey)));
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("当前Java环境不支持RSA", e);
        } catch (InvalidKeySpecException e) {
            throw new RuntimeException("无效的密钥格式");
        }
    }


    /**
     * 获取私钥。
     *
     * @param certString 私钥文件内容(required)
     * @return 私钥对象
     */
    public static PrivateKey getPrivateKey(String certString) throws IOException {
        try {
            String privateKey = certString
                    .replace("-----BEGIN PRIVATE KEY-----", "")
                    .replace("-----END PRIVATE KEY-----", "")
                    .replaceAll("\\s+", "");

            KeyFactory kf = KeyFactory.getInstance("RSA");
            return kf.generatePrivate(
                    new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey)));
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("当前Java环境不支持RSA", e);
        } catch (InvalidKeySpecException e) {
            throw new RuntimeException("无效的密钥格式");
        }
    }

}

class CertEnvironment {
    /**
     * 存放私钥
     */
    private PrivateKey privateKey;

    /**
     * 存放公钥
     */
    private PublicKey publicKey;

    /**
     * 公钥序列
     */
    private String serialNumber;


    public CertEnvironment() {
    }

    public CertEnvironment(PrivateKey privateKey, PublicKey publicKey, String serialNumber) {
        this.privateKey = privateKey;
        this.publicKey = publicKey;
        this.serialNumber = serialNumber;
    }

    public PrivateKey getPrivateKey() {
        return privateKey;
    }

    public void setPrivateKey(PrivateKey privateKey) {
        this.privateKey = privateKey;
    }

    public PublicKey getPublicKey() {
        return publicKey;
    }

    public void setPublicKey(PublicKey publicKey) {
        this.publicKey = publicKey;
    }

    public String getSerialNumber() {
        return serialNumber;
    }

    public void setSerialNumber(String serialNumber) {
        this.serialNumber = serialNumber;
    }

}
