package com.younike.wxpay.service.impl;

import com.alibaba.fastjson.JSON;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.*;
import com.wechat.pay.contrib.apache.httpclient.cert.CertificatesManager;
import com.wechat.pay.contrib.apache.httpclient.notification.Notification;
import com.wechat.pay.contrib.apache.httpclient.notification.NotificationHandler;
import com.wechat.pay.contrib.apache.httpclient.notification.NotificationRequest;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import com.younike.common.exception.ServiceException;
import com.younike.common.utils.StringUtils;
import com.younike.wxpay.enums.TransactionType;
import com.younike.wxpay.params.*;
import com.younike.wxpay.responses.*;
import com.younike.wxpay.service.IBasePayService;
import lombok.Builder;
import lombok.Data;
import lombok.extern.log4j.Log4j2;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.springframework.util.Base64Utils;

import javax.annotation.PreDestroy;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
import java.security.Signature;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

import static com.wechat.pay.contrib.apache.httpclient.constant.WechatPayHttpHeaders.*;

@Log4j2
public class BasePayServiceImpl implements IBasePayService {

    @Data
    @Builder
    protected static class PaymentConfig {
        Boolean isSupplier;
        String mchId; // 商户号
        String appId;
        String mchSerialNo; // 商户证书序列号
        String apiV3Key; // API V3密钥
        String certificatesPath; // 商户证书路径
    }

    protected final static String V3_PATH = "https://api.mch.weixin.qq.com/v3/";
    protected CloseableHttpClient httpClient;
    protected CertificatesManager certificatesManager;
    protected PrivateKey merchantPrivateKey;
    protected Verifier verifier;
    protected PaymentConfig config;

    protected void setup(PaymentConfig config) {
        this.config = config;
        merchantPrivateKey = PemUtil.loadPrivateKey(this.getClass().getClassLoader().getResourceAsStream(config.certificatesPath + "/apiclient_key.pem"));
        // 获取证书管理器实例
        certificatesManager = CertificatesManager.getInstance();
        try {
            // 向证书管理器增加需要自动更新平台证书的商户信息
            certificatesManager.putMerchant(config.mchId, new WechatPay2Credentials(config.mchId,
                    new PrivateKeySigner(config.mchSerialNo, merchantPrivateKey)), config.apiV3Key.getBytes(StandardCharsets.UTF_8));
            // 从证书管理器中获取verifier
            verifier = certificatesManager.getVerifier(config.mchId);
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("证书配置失败");
        }
        // 构造httpclient
        httpClient = WechatPayHttpClientBuilder.create()
                .withMerchant(config.mchId, config.mchSerialNo, merchantPrivateKey)
                .withValidator(new WechatPay2Validator(verifier))
                .build();
    }

    @PreDestroy
    public void after() throws IOException {
        httpClient.close();
    }

    @Override
    public TransactionResponse transaction(TransactionType type, TransactionParams params) {
        String response = post(V3_PATH +
                (config.isSupplier ? "pay/partner/transactions/" : "pay/transactions/") +
                type.getValue(), params);
        return JSON.parseObject(response, TransactionResponse.class);
    }

    @Override
    public RefundResponse refund(RefundParams params) {
        String response =  post(V3_PATH + "refund/domestic/refunds", params);
        return JSON.parseObject(response, RefundResponse.class);
    }

    public String sign(List<String> values) {
        String signatureStr = values.stream()
                .collect(Collectors.joining("\n", "", "\n"));
        try {
            Signature sign = Signature.getInstance("SHA256withRSA");
            sign.initSign(merchantPrivateKey);
            sign.update(signatureStr.getBytes(StandardCharsets.UTF_8));
            return Base64Utils.encodeToString(sign.sign());
        } catch (Exception e) {
            e.printStackTrace();
        }
        throw new RuntimeException("签名失败");
    }

    @Override
    public String decrypt(HttpServletRequest request) throws Exception {
        // 1. 验签和解析请求体
        NotificationRequest data = new NotificationRequest.Builder()
                .withSerialNumber(request.getHeader(WECHAT_PAY_SERIAL))
                .withNonce(request.getHeader(WECHAT_PAY_NONCE))
                .withTimestamp(request.getHeader(WECHAT_PAY_TIMESTAMP))
                .withSignature(request.getHeader(WECHAT_PAY_SIGNATURE))
                .withBody(request.getReader().lines().collect(Collectors.joining(System.lineSeparator())))
                .build();
        NotificationHandler handler = new NotificationHandler(verifier, config.apiV3Key.getBytes(StandardCharsets.UTF_8));
        Notification notification = handler.parse(data);

        // 2. 解析具体参数
        Notification.Resource resource = notification.getResource();
        return new AesUtil(config.apiV3Key.getBytes())
                .decryptToString(
                        resource.getAssociatedData().getBytes(),
                        resource.getNonce().getBytes(),
                        resource.getCiphertext()
                );
    }

    @Override
    public QueryResponse query(String outTradeNo, QueryParams params) {
        String response =  post(V3_PATH +
                (config.isSupplier ? "pay/partner/transactions/id/" : "pay/transactions/id/") +
                outTradeNo, params);
        return JSON.parseObject(response, QueryResponse.class);
    }

    @Override
    public CloseResponse close(String outTradeNo, CloseParams params) {
        String response =  post(V3_PATH +
                (config.isSupplier ? "pay/partner/transactions/out-trade-no/" : "pay/transactions/out-trade-no/") +
                outTradeNo + "/close", params);
        return JSON.parseObject(response, CloseResponse.class);
    }

    @Override
    public ProfitSharingResponse profitSharing(ProfitSharingParams params) {
        String response =  post(V3_PATH + "profitsharing/orders", params);
        return JSON.parseObject(response, ProfitSharingResponse.class);
    }

    @Override
    public QuerySharingResponse querySharing(String outTradeNo, String transactionId, String subMchid) {
        String response =  get(V3_PATH+ "profitsharing/orders/" + outTradeNo + "?sub_mchid=" + subMchid + "&transaction_id=" + transactionId);
        return JSON.parseObject(response, QuerySharingResponse.class);
    }

    protected String post(String url, Object data) {
        log.info("------------调用微信接口------------" + data);
        HttpPost httpPost = new HttpPost(url);
        httpPost.addHeader("Accept", "application/json");
        httpPost.addHeader("Content-type","application/json; charset=utf-8");
        httpPost.setEntity(new StringEntity(JSON.toJSONString(data), "UTF-8"));
        String result;
        try {
            CloseableHttpResponse response  = httpClient.execute(httpPost);
            result = Objects.nonNull(response.getEntity()) ? EntityUtils.toString(response.getEntity()) : "{}";
        } catch (IOException e) {
            e.printStackTrace();
            throw new ServiceException("支付接口调用失败");
        }
        ErrorResponse error = JSON.parseObject(result, ErrorResponse.class);
        log.info("------------微信接口返回------------" + result);
        if (StringUtils.isNoneBlank(error.getCode())) {
            log.warn(error.getMessage());
            throw new ServiceException("支付失败，请刷新后重试");
        }
        return result;
    }

    protected String get(String url) {
        HttpGet httpGet = new HttpGet(url);
        httpGet.addHeader("Accept", "application/json");
        httpGet.addHeader("Content-type","application/json; charset=utf-8");
        String result = "";
        try {
            CloseableHttpResponse response  = httpClient.execute(httpGet);
            result = Objects.nonNull(response.getEntity()) ? EntityUtils.toString(response.getEntity()) : "{}";
        } catch (IOException e) {
            e.printStackTrace();
            throw new ServiceException("查询接口调用失败");
        }
        ErrorResponse error = JSON.parseObject(result, ErrorResponse.class);
        log.info(result);
        if (StringUtils.isNoneBlank(error.getCode())) {
            log.warn(error.getMessage());
            throw new ServiceException("支付失败，请刷新后重试");
        }
        return result;
    }
}
