package com.izouma.nineth.utils.sandpay;

import cn.hutool.core.date.LocalDateTimeUtil;
import com.alibaba.fastjson.JSONObject;
import com.izouma.nineth.config.SandpayProperties;
import com.izouma.nineth.exception.BusinessException;
import com.izouma.nineth.utils.CryptoUtil;
import com.izouma.nineth.utils.SnowflakeIdWorker;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
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.*;
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.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

@Slf4j
public class SandpayClient{

    private SandpayProperties sandpayProperties;

    private CloseableHttpClient httpClient;

    private CryptoUtil cryptoUtil = new CryptoUtil();

    public SandpayClient(SandpayProperties sandpayProperties) {
        this.sandpayProperties = sandpayProperties;
        try {
            init();
        } catch (Exception e) {
            throw new RuntimeException("初始化杉德支付客户端失败", e);
        }
    }

    public void init() throws Exception {
        //初始化支付加密工具
        cryptoUtil.init(sandpayProperties.getPublicKey(),sandpayProperties.getPrivateKey(),
                sandpayProperties.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
     * @param extend
     * @return
     */
    public List<NameValuePair> createRequestParam(JSONObject data, String extend) throws Exception{
        List<NameValuePair> formParams = new ArrayList<>();
        String dataStr = data.toString();
        byte[] dataBytes = dataStr.getBytes("UTF-8");
        String signData = new String(
                Base64.encodeBase64(cryptoUtil.digitalSign(dataBytes,  "SHA1WithRSA")),
                "UTF-8");

        formParams.add(new BasicNameValuePair("charset", "UTF-8"));
        formParams.add(new BasicNameValuePair("data", dataStr));
        formParams.add(new BasicNameValuePair("signType", "01"));
        formParams.add(new BasicNameValuePair("sign", signData));
        formParams.add(new BasicNameValuePair("extend", extend));

        log.info("杉德签名 =>>sign:{}", signData);
        return formParams;
    }


    /**
     * 封装交易报文参数
     */
    private List<NameValuePair> createFundRequestParam(String transCode, JSONObject data) throws Exception{
        List<NameValuePair> formParams = new ArrayList<>();
        String dataStr = data.toString();
        byte[] dataBytes = dataStr.getBytes("UTF-8");
        String signData = Base64.encodeBase64String(cryptoUtil.digitalSign(dataBytes,  "SHA1WithRSA"));

        String accessType = "0";
        formParams.add(new BasicNameValuePair("transCode", transCode));
        //0-商户接入，默认   1-平台接入
        formParams.add(new BasicNameValuePair("accessType", accessType));
        formParams.add(new BasicNameValuePair("transCode", transCode));
        formParams.add(new BasicNameValuePair("merId", sandpayProperties.getMid()));
        if("1".equals(accessType)){
            formParams.add(new BasicNameValuePair("plId", sandpayProperties.getPlMid()));//对应核心企业ID
        }

        String aesKey = RandomStringUtils.randomAlphabetic(16);
        byte[] keyDataByte = cryptoUtil.RSAEncrypt(aesKey.getBytes(), 2048, 11, "RSA/ECB/PKCS1Padding");
        byte[] encryptDataByte = cryptoUtil.AESEncrypt(dataBytes, aesKey.getBytes(), "AES", "AES/ECB/PKCS5Padding", null);
        formParams.add(new BasicNameValuePair("encryptKey", Base64.encodeBase64String(keyDataByte)));
        formParams.add(new BasicNameValuePair("encryptData", Base64.encodeBase64String(encryptDataByte)));
        formParams.add(new BasicNameValuePair("sign", signData));
//        formParams.add(new BasicNameValuePair("extend", ""));


        return formParams;
    }


    /**
     * 组报文
     */
    private JSONObject createData(String method, String productCode, JSONObject bodyJson){
        // 数据域
        JSONObject dataJson = new JSONObject();

        String reqTime =  LocalDateTimeUtil.format(LocalDateTime.now(), "yyyyMMddHHmmss");
        // 报文头
        JSONObject headJson = new JSONObject();
        headJson.put("version", "1.0");
        headJson.put("method", method);
        headJson.put("productId", productCode);
        //1-普通商户接入, 2-平台商户接入
        headJson.put("accessType", "1");//商户接入类型
        headJson.put("mid", sandpayProperties.getMid());
        if("2".equals(headJson.get("accessType"))){
            headJson.put("plMid", sandpayProperties.getPlMid());//对应核心企业ID
        }
        //07-互联网, 08-移动端
        headJson.put("channelType", "08");
        headJson.put("reqTime", reqTime);
        dataJson.put("head", headJson);
        dataJson.put("body", bodyJson);

        return dataJson;
    }



    /**
     * 获取错误信息
     * @param wrappedData
     * @return
     */
    public JSONObject parseWrappedData(String wrappedData) throws UnsupportedEncodingException {
        JSONObject wrappedObject;
        if(wrappedData.startsWith("{")){
            wrappedObject = JSONObject.parseObject(wrappedData);
        }else{
            wrappedObject = new JSONObject();
            String originalStr = URLDecoder.decode(wrappedData, StandardCharsets.UTF_8.name());
            String[] pairAry = originalStr.split("&");
            for(String pair : pairAry){
                String[] mp = pair.split("=");
                wrappedObject.put(mp[0], mp.length > 1 ? mp[1] : "");
            }
        }

        return wrappedObject;

    }

    /**
     * 解析响应内容
     * @return
     * @throws Exception
     */
    private  String checkBizData(JSONObject wrappedObject) throws Exception {

        String encryptKey = wrappedObject.getString("encryptKey");
        //data字段：支付请求返回的是明文，资金交易不返回，账户交易返回密文
        String respData =  wrappedObject.getString("data");
        if(encryptKey != null){
            //加密数据解密,默认AES解密
            String encryptData  = respData;
            if(encryptData == null){
                //资金交易返回密文字段
                encryptData = wrappedObject.getString("encryptData");
            }

            if(encryptData != null){
                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);

                respData = new String(dataBytes, StandardCharsets.UTF_8);
            }

        }

        if(respData == null){
            throw new BusinessException("未读取到响应数据");
        }

        return respData;
    }

    /**
     * 校验签名
     * @param respSign
     * @param verifyData
     */
    private void verifySign(String respSign, String verifyData, String signType) throws Exception{
        byte[] respDataBytes = verifyData.getBytes("UTF-8");

        byte[] signDataBytes = Base64.decodeBase64(respSign);

        boolean isValid = cryptoUtil.verifyDigitalSign(respDataBytes, signDataBytes, signType);
        if (!isValid) {
            throw new BusinessException("报文验签不通过");
        }
    }


    /**
     * 解析结果内容
     * @param wrappedData
     * @return
     */
    public JSONObject checkSuccessBody(String wrappedData) throws Exception{
        JSONObject wrappedObject = parseWrappedData(wrappedData);
        String respData = checkBizData(wrappedObject);

        //签名校验
        String respSign =  wrappedObject.getString("sign");
        verifySign(respSign, respData, "SHA1WithRSA");

        JSONObject bizData = JSONObject.parseObject(respData);
        JSONObject resHeadJson = bizData.getJSONObject("head");
        String version = resHeadJson.getString("version");
        String respTime = resHeadJson.getString("respTime");
        String respCode = resHeadJson.getString("respCode");
        String respMsg = resHeadJson.getString("respMsg");
        if(!"000000".equals(respCode)){
            throw new BusinessException(respMsg + "("+respCode+")");
        }


        JSONObject resBodyJson = bizData.getJSONObject("body");
        return resBodyJson;
    }

    /**
     * 解析资金交易的结果内容
     * @param wrappedData
     * @return
     */
    public JSONObject checkFundSuccessBody(String wrappedData) throws Exception{
        JSONObject wrappedObject = parseWrappedData(wrappedData);
        String respData = checkBizData(wrappedObject);

        //签名校验
        String respSign =  wrappedObject.getString("sign");
        verifySign(respSign, respData, "SHA1WithRSA");

        JSONObject bizData = JSONObject.parseObject(respData);
        String respCode = bizData.getString("respCode");
        String respMsg = bizData.getString("respDesc");
        if(!"0000".equals(respCode)){
            throw new BusinessException(respMsg + "("+respCode+")");
        }

        return bizData;
    }


    /**
     * 获取请求配置
     * @param httppost
     * @return
     */
    public  void setRequestConfig(HttpPost httppost) {
        Integer socketTimeout = sandpayProperties.getSocketTimeout();
        Integer connectTimeout = sandpayProperties.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, String method, String productCode,
                               JSONObject bodyJson, String extend){
        String url;
        if(tradePath.matches("^http(s?)://.+")){
            url  = tradePath;
        }else{
            url = StringUtils.stripEnd(sandpayProperties.getGateWay(), "/") + tradePath;
        }

        CloseableHttpResponse response = null;
        try {
            JSONObject data = createData(method, productCode, bodyJson);
            List<NameValuePair> formParams = createRequestParam(data, extend);
            log.info("接口请求数据，地址：{}， 数据：{}", url, formParams);
            HttpPost httppost = new HttpPost(url);

            setRequestConfig(httppost);

            UrlEncodedFormEntity uefEntity = new UrlEncodedFormEntity(formParams, "UTF-8");
            httppost.setHeader(new BasicHeader("Accept", "application/json, text/plain, */*"));
            httppost.setEntity(uefEntity);

            response = httpClient.execute(httppost);
            String resultStr = EntityUtils.toString(response.getEntity());
            log.info("接口请求响应结果：" + resultStr);
            JSONObject resBodyJson = checkSuccessBody(resultStr);
            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();
                }
            }
        }

    }

    /**
     * 拼接请求地址
     * @param tradePath
     * @param method
     * @param productCode
     * @param bodyJson
     * @param extend
     * @return
     * @throws Exception
     */
    public String generateForm(String tradePath, String method, String productCode,
                               JSONObject bodyJson, String extend) throws Exception {
        String url;
        if(tradePath.matches("^http(s?)://.+")){
            url  = tradePath;
        }else{
            url = StringUtils.stripEnd(sandpayProperties.getGateWay(), "/") + tradePath;
        }

        JSONObject data = createData(method, productCode, bodyJson);
        List<NameValuePair> formParams = createRequestParam(data, extend);
        log.info("接口请求数据，地址：{}， 数据：{}", url, formParams);

        StringBuilder formStr = new StringBuilder();
        formStr.append("<form  action=\""+url+"\"  method=\"post\">");
        formParams.forEach( item -> {
            String name = item.getName();
            String value = HtmlUtils.htmlEscape(item.getValue());
            formStr.append("<input type=\"hidden\" name=\""+name+"\"  value=\""+ value+"\"/>");
        });

        formStr.append("</form>");

        //自动提交
        formStr.append("<script>document.forms[0].submit();</script>");
        return formStr.toString();
    }


    /**
     * 资金交易处理
     * @return
     * @throws Exception
     */
    public JSONObject fundPostForm(String tradePath, String tradeCode, JSONObject bodyJson){
        String url;
        if(tradePath.matches("^http(s?)://.+")){
            url  = tradePath;
        }else{
            url = StringUtils.stripEnd(sandpayProperties.getFundGateWay(), "/") + tradePath;
        }

        CloseableHttpResponse response = null;
        try {
            List<NameValuePair> formParams = createFundRequestParam(tradeCode, bodyJson);
            log.info("接口请求数据，地址：{}， 数据：{}", url, formParams);
            HttpPost httppost = new HttpPost(url);

            setRequestConfig(httppost);

            UrlEncodedFormEntity uefEntity = new UrlEncodedFormEntity(formParams, "UTF-8");
            httppost.setHeader(new BasicHeader("Accept", "application/json, text/plain, */*"));
            httppost.setEntity(uefEntity);
            response = httpClient.execute(httppost);
            String resultStr = EntityUtils.toString(response.getEntity());
            log.info("接口请求响应结果：" + resultStr);
            JSONObject resBodyJson = checkFundSuccessBody(resultStr);
            return resBodyJson;

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

    }




}
