package ink.wgink.module.wechat.pay.request.v3;

import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson.support.config.FastJsonConfig;
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import ink.wgink.exceptions.base.SystemException;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.*;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestTemplate;

import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;

/**
 * @ClassName: PayRequestUtil
 * @Description: 支付请求
 * @Author: wanggeng
 * @Date: 2021/8/18 9:41 下午
 * @Version: 1.0
 */
public abstract class AbstractPayRequest<RequestBean, BodyType, ResponseType> {

    protected static final Logger LOG = LoggerFactory.getLogger(AbstractPayRequest.class);
    public static final String PAY_SERIAL = "Wechatpay-Serial";

    private RestTemplate restTemplate;

    public AbstractPayRequest() {
        HttpComponentsClientHttpRequestFactory httpComponentsClientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory();
        httpComponentsClientHttpRequestFactory.setConnectTimeout(20 * 1000);
        httpComponentsClientHttpRequestFactory.setReadTimeout(60 * 1000);
        this.restTemplate = new RestTemplate(httpComponentsClientHttpRequestFactory);
    }

    /**
     * bodys实体转json字符串
     *
     * @param requestBean
     * @return
     */
    public abstract BodyType bodyJson(RequestBean requestBean);

    /**
     * 响应结果处理
     *
     * @param responseEntity
     */
    public abstract ResponseType response(ResponseEntity<String> responseEntity);

    public abstract void error(HttpClientErrorException e);

    /**
     * GET 请求
     *
     * @param url           请求地址
     * @param authorization 授权信息
     * @param serialNumber  公钥证书序列号
     */
    public ResponseType get(String url, String authorization, String serialNumber) {
        try {
            HttpEntity<String> httpEntity = new HttpEntity(getHttpHeaders(authorization, serialNumber));
            ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.GET, httpEntity, String.class);
            return response(responseEntity);
        } catch (Exception e) {
            if (e instanceof HttpClientErrorException) {
                HttpClientErrorException exception = (HttpClientErrorException) e;
                error(exception);
            }
            throw new SystemException(e.getMessage(), e);
        }
    }

    /**
     * POST 请求
     *
     * @param url           请求地址
     * @param authorization 授权信息
     * @param serialNumber  公钥证书序列号
     * @param jsonBody      请求参数
     */
    public ResponseType post(String url, String authorization, String serialNumber, BodyType jsonBody) {
        try {
            HttpEntity<BodyType> httpEntity = new HttpEntity(jsonBody, getHttpHeaders(authorization, serialNumber));
            ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.POST, httpEntity, String.class);
            return response(responseEntity);
        } catch (Exception e) {
            if (e instanceof HttpClientErrorException) {
                HttpClientErrorException exception = (HttpClientErrorException) e;
                error(exception);
            }
            throw new SystemException(e.getMessage(), e);
        }
    }

    /**
     * Put 请求
     *
     * @param url           请求地址
     * @param authorization 授权信息
     * @param serialNumber  公钥证书序列号
     * @param jsonBody      请求参数
     */
    public ResponseType put(String url, String authorization, String serialNumber, BodyType jsonBody) {
        try {
            HttpEntity<BodyType> httpEntity = new HttpEntity(jsonBody, getHttpHeaders(authorization, serialNumber));
            ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.PUT, httpEntity, String.class);
            return response(responseEntity);
        } catch (Exception e) {
            if (e instanceof HttpClientErrorException) {
                HttpClientErrorException exception = (HttpClientErrorException) e;
                error(exception);
            }
            throw new SystemException(e.getMessage(), e);
        }
    }

    /**
     * Delete 请求
     *
     * @param url           请求地址
     * @param authorization 授权信息
     * @param serialNumber  公钥证书序列号
     */
    public ResponseType delete(String url, String authorization, String serialNumber) {
        try {
            HttpEntity<String> httpEntity = new HttpEntity(getHttpHeaders(authorization, serialNumber));
            ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.DELETE, httpEntity, String.class);
            return response(responseEntity);
        } catch (Exception e) {
            if (e instanceof HttpClientErrorException) {
                HttpClientErrorException exception = (HttpClientErrorException) e;
                error(exception);
            }
            throw new SystemException(e.getMessage(), e);
        }
    }

    /**
     * 上传文件
     *
     * @param url           请求地址
     * @param authorization 授权信息
     * @param serialNumber  公钥证书序列号
     * @param jsonData      请求参数
     * @param uploadFile    上传文件
     */
    public ResponseType upload(String url, String authorization, String serialNumber, String jsonData, File uploadFile) {
        try {
            FileSystemResource fileSystemResource = new FileSystemResource(uploadFile);
            MultiValueMap<String, Object> form = new LinkedMultiValueMap<>();
            form.add("file", fileSystemResource);
            form.add("meta", jsonData);
            HttpEntity<MultiValueMap<String, Object>> httpEntity = new HttpEntity<>(form, getUploadHeaders(authorization, serialNumber));
            ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.POST, httpEntity, String.class);
            return response(responseEntity);
        } catch (Exception e) {
            if (e instanceof HttpClientErrorException) {
                HttpClientErrorException exception = (HttpClientErrorException) e;
                error(exception);
            }
            throw new SystemException(e.getMessage(), e);
        }
    }

    /**
     * 请求头
     *
     * @param authorization 授权信息
     * @return
     */
    private HttpHeaders getHttpBaseHeaders(String authorization) {
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.add("Accept", MediaType.APPLICATION_JSON_VALUE);
        httpHeaders.add("Authorization", authorization);
        return httpHeaders;
    }

    /**
     * JSON请求头
     *
     * @param authorization 授权信息
     * @param serialNumber  公钥证书序列号
     * @return
     */
    private HttpHeaders getHttpHeaders(String authorization, String serialNumber) {
        HttpHeaders httpHeaders = getHttpBaseHeaders(authorization);
        httpHeaders.add("Content-Type", MediaType.APPLICATION_JSON_VALUE);
        if (!StringUtils.isBlank(serialNumber)) {
            httpHeaders.add(PAY_SERIAL, serialNumber);
        }
        LOG.debug("header: {}", httpHeaders);
        return httpHeaders;
    }

    /**
     * 上传文件请求头
     *
     * @param authorization 授权信息
     * @param serialNumber  公钥证书序列号
     * @return
     */
    private HttpHeaders getUploadHeaders(String authorization, String serialNumber) {
        HttpHeaders httpHeaders = getHttpBaseHeaders(authorization);
        httpHeaders.add("Content-Type", "multipart/form-data;boundary=\"boundary\"");
        if (!StringUtils.isBlank(serialNumber)) {
            httpHeaders.add(PAY_SERIAL, serialNumber);
        }
        LOG.debug(httpHeaders.toString());
        return httpHeaders;
    }

    @Deprecated
    private static class HttpMessageCoverBuilder {

        /**
         * string 消息转换
         *
         * @return
         */
        private static StringHttpMessageConverter getStringHttpMessageConverter() {
            StringHttpMessageConverter stringConvert = new StringHttpMessageConverter();
            List<MediaType> stringMediaTypes = new ArrayList<MediaType>() {{
                //添加响应数据格式，不匹配会报401
                add(MediaType.TEXT_PLAIN);
                add(MediaType.TEXT_HTML);
                add(MediaType.APPLICATION_JSON);
                add(MediaType.APPLICATION_JSON_UTF8);
            }};
            stringConvert.setSupportedMediaTypes(stringMediaTypes);
            return stringConvert;
        }

        /**
         * fastjson 消息转换
         *
         * @return
         */
        private static FastJsonHttpMessageConverter getFastJsonHttpMessageConverter() {
            FastJsonHttpMessageConverter fastJsonHttpMessageConverter = new FastJsonHttpMessageConverter();
            //2、添加fastjson的配置信息
            FastJsonConfig fastJsonConfig = new FastJsonConfig();

            SerializerFeature[] serializerFeatures = new SerializerFeature[]{
                    //    输出key是包含双引号
//                SerializerFeature.QuoteFieldNames,
                    //    是否输出为null的字段,若为null 则显示该字段
//                SerializerFeature.WriteMapNullValue,
                    //    数值字段如果为null，则输出为0
                    SerializerFeature.WriteNullNumberAsZero,
                    //     List字段如果为null,输出为[],而非null
                    SerializerFeature.WriteNullListAsEmpty,
                    //    字符类型字段如果为null,输出为"",而非null
                    SerializerFeature.WriteNullStringAsEmpty,
                    //    Boolean字段如果为null,输出为false,而非null
                    SerializerFeature.WriteNullBooleanAsFalse,
                    //    Date的日期转换器
                    SerializerFeature.WriteDateUseDateFormat,
                    //    循环引用
                    SerializerFeature.DisableCircularReferenceDetect,
            };

            fastJsonConfig.setSerializerFeatures(serializerFeatures);
            fastJsonConfig.setCharset(Charset.forName("UTF-8"));
            fastJsonHttpMessageConverter.setFastJsonConfig(fastJsonConfig);
            return fastJsonHttpMessageConverter;
        }

        /**
         * 返回格式解析
         *
         * @return
         */
        public static List<HttpMessageConverter<?>> getMessageConverts() {
            List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();
            messageConverters.add(getStringHttpMessageConverter());
            messageConverters.add(getFastJsonHttpMessageConverter());
            return messageConverters;
        }
    }

    /**
     * Aes 解密
     */
    public static class AesUtil {
        static final int KEY_LENGTH_BYTE = 32;
        static final int TAG_LENGTH_BIT = 128;
        private final byte[] aesKey;

        /**
         * @param key APIv3秘钥
         */
        public AesUtil(byte[] key) {
            if (key.length != KEY_LENGTH_BYTE) {
                throw new IllegalArgumentException("无效的ApiV3Key，长度必须为32个字节");
            }
            this.aesKey = key;
        }

        /**
         * 解密
         *
         * @param associatedData 关联数据
         * @param nonce          加密使用的随机串初始化向量
         * @param ciphertext     密文
         * @return
         * @throws GeneralSecurityException
         * @throws IOException
         */
        public String decryptToString(byte[] associatedData, byte[] nonce, String ciphertext) throws GeneralSecurityException, IOException {
            try {
                Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");

                SecretKeySpec key = new SecretKeySpec(aesKey, "AES");
                GCMParameterSpec spec = new GCMParameterSpec(TAG_LENGTH_BIT, nonce);

                cipher.init(Cipher.DECRYPT_MODE, key, spec);
                cipher.updateAAD(associatedData);

                return new String(cipher.doFinal(Base64.getDecoder().decode(ciphertext)), "utf-8");
            } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
                throw new IllegalStateException(e);
            } catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
                throw new IllegalArgumentException(e);
            }
        }
    }

}
