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 javax.net.ssl.SSLContext;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * 账户交易专用
 */
@Slf4j
public class SandpayAccountClient {

    /**
     * 账户的用户订单参数
     */
    public static final String ACCOUNT_CUSTOMER_ORDER_NO = "customerOrderNo";

    private SandpayProperties sandpayProperties;

    private CloseableHttpClient httpClient;

    private CryptoUtil accountCryptoUtil = new CryptoUtil();

    private SnowflakeIdWorker snowflakeIdWorker = new SnowflakeIdWorker(0, 0);

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

    public void init() throws Exception {
        //初始化账户加密工具
        accountCryptoUtil.init(sandpayProperties.getCfcaPublicKey(),sandpayProperties.getCfcaPrivateKey(),
                sandpayProperties.getCfcaPrivateKeyPwd());
        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
     * @throws Exception
     */
    private JSONObject createAccountRequestData(JSONObject data, String customerOrderNo) throws Exception{
        if(StringUtils.isBlank(customerOrderNo)){
            customerOrderNo =  String.valueOf(snowflakeIdWorker.nextId());
        }

        String signType = "SHA1WithRSA";
        JSONObject wrappedData = new JSONObject();
        String dataStr = data.toString();
        byte[] dataBytes = dataStr.getBytes("UTF-8");
        String signData = Base64.encodeBase64String(accountCryptoUtil.digitalSign(dataBytes,  signType));

        String timestamp = LocalDateTimeUtil.format(LocalDateTime.now(), "yyyy-MM-dd HH:mm:ss");
        wrappedData.put("mid",sandpayProperties.getMid());
        wrappedData.put("sign",signData);
        wrappedData.put("timestamp",timestamp);
        wrappedData.put("version","1.0.0");
        wrappedData.put("customerOrderNo", customerOrderNo);//唯一流水
        wrappedData.put("signType", signType);
        wrappedData.put("encryptType", "AES");

        String aesKey = RandomStringUtils.randomAlphabetic(16);
        byte[] keyDataByte = accountCryptoUtil.RSAEncrypt(aesKey.getBytes(), 2048, 11, "RSA/ECB/PKCS1Padding");
        byte[] encryptDataByte = accountCryptoUtil.AESEncrypt(dataBytes, aesKey.getBytes(), "AES", "AES/ECB/PKCS5Padding", null);
        wrappedData.put("encryptKey", Base64.encodeBase64String(keyDataByte));
        wrappedData.put("data",  Base64.encodeBase64String(encryptDataByte));


        return wrappedData;
    }


    /**
     * 获取错误信息
     * @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 = accountCryptoUtil.RSADecrypt(encryptedKeyByte, 2048, 11, "RSA/ECB/PKCS1Padding");
                byte[] encryptedDataByte = Base64.decodeBase64(encryptData);

                byte[] dataBytes = accountCryptoUtil.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 accountVerifySign(String respSign, String verifyData, String signType) throws Exception{
        byte[] respDataBytes = verifyData.getBytes("UTF-8");

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

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

    /**
     * 解析账户交易的结果内容
     * @param wrappedData
     * @return
     */
    public JSONObject checkAccountSuccessBody(String wrappedData) throws Exception{
        JSONObject wrappedObject = parseWrappedData(wrappedData);

        //检查响应字段response
        JSONObject responseObj = wrappedObject.getJSONObject("response");

        if(responseObj != null){
            String responseTime = responseObj.getString("responseTime");
            String respCode = responseObj.getString("responseCode");
            String respMsg = responseObj.getString("responseDesc");
            if(!"00000".equals(respCode)){
                throw new BusinessException(respMsg + "("+respCode+")");
            }
        }

        JSONObject bizData = wrappedObject;

        //签名校验
        if(wrappedObject.containsKey("data")){
            String respSign = Objects.toString(wrappedObject.remove("sign"), "");
            String signType = Objects.toString(wrappedObject.remove("signType"), "");

            String data = wrappedObject.getString("data");

            //账户公钥校验
            accountVerifySign(respSign, data, signType);

            //解密数据
            String respData = checkBizData(wrappedObject);
            //解密加密数据
            bizData = JSONObject.parseObject(respData);

        }

        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 accountPostJson(JSONObject bodyJson, SandEnum sandEnum){
        //提取用户订单号
        String customerOrderNo = Objects.toString(bodyJson.remove(ACCOUNT_CUSTOMER_ORDER_NO), "");

        String method = sandEnum.getBusinessType() + "/" + sandEnum.getMethodCode();
        String tradePath = "/" +  sandpayProperties.getVersion() + "/" + method;
        String url = StringUtils.stripEnd(sandpayProperties.getAccountGateWay(), "/") + tradePath;
        CloseableHttpResponse response = null;
        try {
            JSONObject accountData  = createAccountRequestData(bodyJson, customerOrderNo);
            log.info("接口请求数据，地址：{}， 数据：{}", url, accountData);
            HttpPost httppost = new HttpPost(url);

            setRequestConfig(httppost);

            StringEntity jsonEntity = new StringEntity(accountData.toString(), ContentType.APPLICATION_JSON);
            httppost.setHeader(new BasicHeader("Accept", "application/json, text/plain, */*"));
            httppost.setEntity(jsonEntity);

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

    }





}
