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

import com.alibaba.fastjson.JSONObject;
import ink.wgink.exceptions.base.SystemException;
import ink.wgink.module.wechat.enums.PayAuthorizationTypeEnum;
import ink.wgink.module.wechat.pojo.pay.v3.PayErrorResponse;
import ink.wgink.module.wechat.utils.pay.PaySignAuthorizationUtil;
import ink.wgink.properties.wechat.pay.v3.PayProperties;
import ink.wgink.util.UUIDUtil;
import ink.wgink.util.string.WStringUtil;
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.SimpleClientHttpRequestFactory;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.RequestMethod;
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.FileNotFoundException;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;

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

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

    private RestTemplate restTemplate;

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

    /**
     * bodys实体转json字符串
     *
     * @param requestBody
     * @return
     */
    public abstract String bodyJsonString(RequestBody requestBody);

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

    /**
     * GET 请求
     *
     * @param url           请求地址
     * @param authorization 授权信息
     * @param serialNumber  公钥证书序列号
     */
    public Response get(String url, String authorization, String serialNumber) {
        HttpEntity<String> httpEntity = new HttpEntity(getHttpHeaders(authorization, serialNumber));
        ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.GET, httpEntity, String.class);
        checkResponseCode(responseEntity);
        return response(responseEntity);
    }

    /**
     * POST 请求
     *
     * @param url           请求地址
     * @param authorization 授权信息
     * @param serialNumber  公钥证书序列号
     * @param jsonBody      请求参数
     */
    public Response post(String url, String authorization, String serialNumber, String jsonBody) {
        HttpEntity<String> httpEntity = new HttpEntity(jsonBody, getHttpHeaders(authorization, serialNumber));
        ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.POST, httpEntity, String.class);
        checkResponseCode(responseEntity);
        return response(responseEntity);
    }

    /**
     * Put 请求
     *
     * @param url           请求地址
     * @param authorization 授权信息
     * @param serialNumber  公钥证书序列号
     * @param jsonBody      请求参数
     */
    public Response put(String url, String authorization, String serialNumber, String jsonBody) {
        HttpEntity<String> httpEntity = new HttpEntity(jsonBody, getHttpHeaders(authorization, serialNumber));
        ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.PUT, httpEntity, String.class);
        checkResponseCode(responseEntity);
        return response(responseEntity);
    }

    /**
     * Delete 请求
     *
     * @param url           请求地址
     * @param authorization 授权信息
     * @param serialNumber  公钥证书序列号
     */
    public Response delete(String url, String authorization, String serialNumber) {
        HttpEntity<String> httpEntity = new HttpEntity(getHttpHeaders(authorization, serialNumber));
        ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.DELETE, httpEntity, String.class);
        checkResponseCode(responseEntity);
        return response(responseEntity);
    }

    /**
     * 上传文件
     *
     * @param url           请求地址
     * @param authorization 授权信息
     * @param serialNumber  公钥证书序列号
     * @param jsonData      请求参数
     * @param uploadFile    上传文件
     */
    public Response upload(String url, String authorization, String serialNumber, String jsonData, File uploadFile) {
        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);
        checkResponseCode(responseEntity);
        return response(responseEntity);
    }

    /**
     * 认证头
     *
     * @param requestMethod
     * @param urlSuffix
     * @param payProperties
     * @param jsonBody
     * @param payAuthorizationTypeEnum
     * @return
     * @throws FileNotFoundException
     */
    public String getAuthorization(RequestMethod requestMethod, String urlSuffix, String serialNumber, PayProperties payProperties, String jsonBody, PayAuthorizationTypeEnum payAuthorizationTypeEnum) throws Exception {
        String randomSubStr = WStringUtil.randomSubStr(UUIDUtil.get32UUID(), 10).toUpperCase();
        return PaySignAuthorizationUtil.buildAuthorization(requestMethod, urlSuffix,
                payProperties.getMchid(), serialNumber,
                payProperties.getKeyPath(), jsonBody, randomSubStr, System.currentTimeMillis(),
                payAuthorizationTypeEnum.getValue());
    }

    /**
     * 请求头
     *
     * @param authorization 授权信息
     * @return
     */
    private HttpHeaders getHttpBaseHeaders(String authorization) {
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.add("Accept", MediaType.APPLICATION_JSON_UTF8_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_UTF8_VALUE);
        if (!StringUtils.isBlank(serialNumber)) {
            httpHeaders.add(PAY_SERIAL, serialNumber);
        }
        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);
        }
        return httpHeaders;
    }

    /**
     * 检查返回状态码
     *
     * @param responseEntity
     */
    private void checkResponseCode(ResponseEntity<String> responseEntity) {
        if (responseEntity.getStatusCode().value() == HttpStatus.OK.value()) {
            return;
        }
        LOG.error(responseEntity.getBody());
        PayErrorResponse payErrorResponse = JSONObject.parseObject(responseEntity.getBody(), PayErrorResponse.class);
        throw new SystemException(payErrorResponse.getMessage());
    }

    /**
     * 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);
            }
        }
    }

}
