package com.izouma.nineth.utils.payeasenet;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.github.javafaker.Crypto;
import com.izouma.nineth.config.PayeasenetProperties;
import com.izouma.nineth.exception.BusinessException;
import com.izouma.nineth.utils.CryptoUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.NameValuePair;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.DefaultHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContextBuilder;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.springframework.web.util.HtmlUtils;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;

import javax.net.ssl.SSLContext;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.TreeMap;

@Slf4j
public class PayeasenetClient {

    private PayeasenetProperties payeasenetProperties;

    private CloseableHttpClient httpClient;

    private CryptoUtil cryptoUtil = new CryptoUtil();

    /**
     * 请求类型
     */
    private String defaultContentType = "application/vnd.5upay-v3.0+json";


    public PayeasenetClient(PayeasenetProperties payeasenetProperties) {
        this.payeasenetProperties = payeasenetProperties;
        try {
            init();
        } catch (Exception e) {
            throw new RuntimeException("初始化首信易支付客户端失败", e);
        }
    }

    public void init() throws Exception {
        //初始化
        cryptoUtil.init(payeasenetProperties.getPublicKey(),payeasenetProperties.getPrivateKey(),
                payeasenetProperties.getPrivateKeyPwd());
        SSLContext sslcontext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
            public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                return true;
            }
        }).build();
        SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext, new DefaultHostnameVerifier());

        httpClient = HttpClients.custom().setSSLSocketFactory(sslsf).build();

    }


    /**
     * 生成待签名数据
     * @param data
     * @return
     */
    private void generateHmacSource(JSONObject data, StringBuilder signSource){
        TreeMap<String,Object> sortedMap = new TreeMap<>();
        sortedMap.putAll(data);
        sortedMap.forEach((key, value) -> {
            if(value instanceof JSONArray){
                JSONArray itemArray = (JSONArray)value;
                for(int i = 0; i < itemArray.size(); i++){
                    JSONObject item = itemArray.getJSONObject(i);
                    generateHmacSource(item, signSource);
                }
            }else if(value instanceof  JSONObject){
                generateHmacSource((JSONObject)value, signSource);
            }else {
                String realValue = Objects.toString(value, "");
                if (StringUtils.isNotEmpty(realValue)) {
                    signSource.append(realValue).append("#");
                }
            }
        });

    }

    /**
     * 封装交易报文参数
     */
    private JSONObject wrapRequestData(JSONObject data) throws Exception{
        StringBuilder signSource = new StringBuilder();
        generateHmacSource(data, signSource);
        log.info("接口拼接签名原始字符串：" + signSource);
        byte[] signSourceBytes = signSource.toString().getBytes("UTF-8");
        byte[] signSha1Bytes = DigestUtils.sha1(signSourceBytes);
        byte[] encryptedSignBytes = cryptoUtil.digitalSign(signSha1Bytes, "MD5withRSA");

        String signData = Base64.encodeBase64String(encryptedSignBytes);

        data.put("hmac", signData);

        JSONObject wrapObj = new JSONObject();

        String aesKey = RandomStringUtils.randomAlphabetic(16);
        byte[] keyDataByte = cryptoUtil.RSAEncrypt(aesKey.getBytes(), 2048, 11, "RSA/ECB/PKCS1Padding");
        byte[] encryptDataByte = cryptoUtil.AESEncrypt(data.toString().getBytes(StandardCharsets.UTF_8),
                aesKey.getBytes(), "AES", "AES/ECB/PKCS5Padding", null);
        wrapObj.put("encryptKey", Base64.encodeBase64String(keyDataByte));
        wrapObj.put("data", Base64.encodeBase64String(encryptDataByte));
        wrapObj.put("merchantId", payeasenetProperties.getMerchantId());
        //商户订单号
        wrapObj.put("requestId", data.getString("requestId"));

        //服务商ID
        if(StringUtils.isNotBlank(payeasenetProperties.getPartnerId())){
            wrapObj.put("partnerId", payeasenetProperties.getPartnerId());
        }

        return wrapObj;
    }


    /**
     * 解析响应结果
     * @return
     */
    public JSONObject checkSuccessBody(JSONObject wrappedData, String encryptKey) throws Exception{
        String encryptData = wrappedData.getString("data");

        if(StringUtils.isBlank(encryptKey)){
            throw new BusinessException("解密密钥不能为空");
        }

        //解密数据
        byte[] encryptedKeyByte = Base64.decodeBase64(encryptKey);
        byte[] keyBytes = cryptoUtil.RSADecrypt(encryptedKeyByte, 2048, 11, "RSA/ECB/PKCS1Padding");
        byte[] encryptedDataByte = Base64.decodeBase64(encryptData);

        byte[] dataBytes = cryptoUtil.AESDecrypt(encryptedDataByte, keyBytes, "AES", "AES/ECB/PKCS5Padding", null);

        //签名校验
        String respData = new String(dataBytes, StandardCharsets.UTF_8);
        log.info("接口响应解密后的数据：" + respData);
        JSONObject resObj = JSONObject.parseObject(respData);
        //判断是否出错
        String status = resObj.getString("status");
        String error = resObj.getString("error");
        String code = resObj.getString("code");
        if("ERROR".equals(status)){
            throw new BusinessException(error + "("+code+")");
        }

        //移出签名数据用于校验
        String fromSign = Objects.toString(resObj.remove("hmac"),"");
        byte[] fromSignBytes = Base64.decodeBase64(fromSign);
        //获取本地签名
        StringBuilder signSource = new StringBuilder();
        generateHmacSource(resObj, signSource);
        byte[] signSourceBytes = signSource.toString().getBytes("UTF-8");
        byte[] signSha1Bytes = DigestUtils.sha1(signSourceBytes);
        boolean verify = cryptoUtil.verifyDigitalSign(signSha1Bytes, fromSignBytes, "MD5withRSA");
        if(!verify){
            throw new BusinessException("签名校验失败");
        }

        return resObj;

    }

    /**
     * 获取请求配置
     * @param httppost
     * @return
     */
    public  void setRequestConfig(HttpPost httppost) {
        Integer socketTimeout = payeasenetProperties.getSocketTimeout();
        Integer connectTimeout = payeasenetProperties.getConnectTimeout();
        if ((socketTimeout != null) && (connectTimeout != null)) {
            RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(socketTimeout.intValue())
                    .setConnectTimeout(connectTimeout.intValue()).build();

            httppost.setConfig(requestConfig);
        }
    }


    /**
     *
     * 发送请求表单到接口
     * @return
     * @throws Exception
     */
    public JSONObject postForm(String tradePath, JSONObject data){
        String url;
        if(tradePath.matches("^http(s?)://.+")){
            url  = tradePath;
        }else{
            url = StringUtils.stripEnd(payeasenetProperties.getGateWay(), "/") + tradePath;
        }

        log.info("接口请求数据，地址：{}， 数据：{}", url, data);

        CloseableHttpResponse response = null;
        try {
            JSONObject wrapObj  = wrapRequestData(data);
            HttpPost httppost = new HttpPost(url);
            setRequestConfig(httppost);

            String encryptedData = wrapObj.getString("data");
            String encryptKey = wrapObj.getString("encryptKey");
            String merchantId = wrapObj.getString("merchantId");
            String partnerId = wrapObj.getString("partnerId");


            StringEntity stringEntity = new StringEntity(encryptedData, ContentType.create(defaultContentType));
            httppost.setHeader(new BasicHeader("Accept", "application/json, text/plain, */*"));
            httppost.setHeader(new BasicHeader("encryptKey", encryptKey));
            httppost.setHeader(new BasicHeader("merchantId", merchantId));
            if(StringUtils.isNotBlank(partnerId)){
                httppost.setHeader(new BasicHeader("partnerId", partnerId));
            }

            httppost.setEntity(stringEntity);
            response = httpClient.execute(httppost);
            String resultStr = EntityUtils.toString(response.getEntity());
            String fromEncryptKey = response.getFirstHeader("encryptKey").getValue();
            log.info("接口请求响应结果：" + resultStr + ",密钥：" + fromEncryptKey);
            JSONObject wrappedData = JSONObject.parseObject(resultStr);
            JSONObject resBodyJson = checkSuccessBody(wrappedData, fromEncryptKey);
            return resBodyJson;

        }catch(Exception e){
            String errorInfo = e.getMessage();
            if(StringUtils.isEmpty(errorInfo)){
                errorInfo = "接口未知错误";
            }
            throw new RuntimeException(errorInfo, e);
        } finally {
            if(response != null){
                EntityUtils.consumeQuietly(response.getEntity());
                try {
                    response.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }

}
