package com.izouma.nineth.utils.lianlianpay;

import com.alibaba.fastjson.JSONObject;
import com.izouma.nineth.config.LianlianpayProperties;
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.StringUtils;
import org.apache.http.Header;
import org.apache.http.client.config.RequestConfig;
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.util.EntityUtils;

import javax.net.ssl.SSLContext;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.nio.charset.StandardCharsets;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

@Slf4j
public class LianlianpayClient {

    private LianlianpayProperties lianlianpayProperties;

    private CloseableHttpClient httpClient;

    private CryptoUtil cryptoUtil = new CryptoUtil();


    public LianlianpayClient(LianlianpayProperties lianlianpayProperties) {
        this.lianlianpayProperties = lianlianpayProperties;
        try {
            init();
        } catch (Exception e) {
            throw new RuntimeException("初始化连连支付客户端失败", e);
        }
    }

    public void init() throws Exception {
        //初始化
        cryptoUtil.init(lianlianpayProperties.getPublicKey(),lianlianpayProperties.getPrivateKey(), null);
        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();

    }



    /**
     * 获取签名数据
     */
    private String getSignData(String requestData) throws Exception{
        byte[] signSourceBytes = requestData.getBytes(StandardCharsets.UTF_8);
        String signStr = DigestUtils.md5Hex(signSourceBytes);
        //log.info("接口拼接签名原始字符串：{}, MD5:{} " , requestData, signStr);
        byte[] encryptedSignBytes = cryptoUtil.digitalSign(signStr.getBytes(StandardCharsets.UTF_8), "MD5withRSA");

        String signData = Base64.encodeBase64String(encryptedSignBytes);
        return signData;
    }


    /**
     * 解析响应结果
     * @return
     */
    public JSONObject checkSuccessBody(String  resultStr, boolean isForce) throws Exception{
        JSONObject resObj = JSONObject.parseObject(resultStr);
        //判断是否出错
        String retCode = resObj.getString("ret_code");
        String retMsg = resObj.getString("ret_msg");
        if(isForce || StringUtils.isNotEmpty(retCode)){
            //检查响应结果
            if(!("0000".equals(retCode) || "8888".equals(retCode))){
                if(StringUtils.isEmpty(retMsg)){
                    retMsg = "支付通道未响应消息";
                }
                throw new BusinessException(retMsg + "("+retCode+")");
            }
        }

        return resObj;

    }

    /**
     * 校验签名
     * @param dataStr
     * @param fromSign
     */
    public void verifySign(String dataStr, String fromSign) throws Exception{

        if(StringUtils.isEmpty(fromSign)){
            throw new BusinessException("缺失签名信息");
        }

        //log.info("校验签名：{}, 数据：{}" , fromSign, dataStr);
        byte[] fromSignBytes = Base64.decodeBase64(fromSign);
        //获取本地签名
        byte[] signSourceBytes = dataStr.getBytes("UTF-8");
        String signStr = DigestUtils.md5Hex(signSourceBytes);
        boolean verify = cryptoUtil.verifyDigitalSign(signStr.getBytes(StandardCharsets.UTF_8),
                fromSignBytes, "MD5withRSA");
        if(!verify){
            throw new BusinessException("签名校验失败");
        }

    }


    /**
     * 获取请求配置
     * @param httppost
     * @return
     */
    public  void setRequestConfig(HttpPost httppost) {
        Integer socketTimeout = lianlianpayProperties.getSocketTimeout();
        Integer connectTimeout = lianlianpayProperties.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 postJson(String tradePath, JSONObject data){
        String url;
        if(tradePath.matches("^http(s?)://.+")){
            url  = tradePath;
        }else{
            url = StringUtils.stripEnd(lianlianpayProperties.getGateWay(), "/") + tradePath;
        }
        //添加公用参数
        String timestamp = DateTimeFormatter.ofPattern("yyyyMMddHHmmss").format(LocalDateTime.now());
        data.put("timestamp", timestamp);
        data.put("oid_partner", lianlianpayProperties.getMid());

        CloseableHttpResponse response = null;
        try {
            String requestData = data.toJSONString();
            String signData =  getSignData(requestData);
            long startTime = System.currentTimeMillis();
            log.info("接口请求数据，地址：{}， 数据：{}, 签名：{} ", url, requestData , signData);
            HttpPost httppost = new HttpPost(url);
            setRequestConfig(httppost);

            StringEntity stringEntity = new StringEntity(requestData, ContentType.APPLICATION_JSON);
            httppost.setHeader(new BasicHeader("Accept", "application/json, text/plain, */*"));
            httppost.setHeader(new BasicHeader("Signature-Type", "RSA"));
            httppost.setHeader(new BasicHeader("Signature-Data", signData));

            httppost.setEntity(stringEntity);

            response = httpClient.execute(httppost);

            Header resSignType = response.getFirstHeader("Signature-Type");
            Header resSignData = response.getFirstHeader("Signature-Data");
            String resultStr = EntityUtils.toString(response.getEntity());
            long endTime = System.currentTimeMillis();
            long duration = endTime - startTime;
            log.info("接口请求响应结果：{}, {}, {}, {}ms" , resultStr , resSignType, resSignData , duration);
            int statusCode = response.getStatusLine().getStatusCode();
            if(statusCode != HttpURLConnection.HTTP_OK){
                throw new IllegalStateException("接口响应失败(" + statusCode + ")");
            }

            String resSignDataStr = null;
            if(resSignData != null){
                resSignDataStr = resSignData.getValue();
            }

            JSONObject resBodyJson = checkSuccessBody(resultStr, true);

            //校验签名
            if(resSignDataStr != null){
                verifySign(resultStr, resSignDataStr);
            }

            return resBodyJson;

        }catch(BusinessException e){
            throw e;
        }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();
                }
            }
        }

    }

}
