package com.ycwl.basic.pay.adapter;

import cn.hutool.core.codec.Base64;
import com.wechat.pay.java.core.Config;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import com.wechat.pay.java.core.RSAPublicKeyConfig;
import com.wechat.pay.java.core.notification.NotificationConfig;
import com.wechat.pay.java.core.notification.NotificationParser;
import com.wechat.pay.java.core.notification.RequestParam;
import com.wechat.pay.java.core.util.PemUtil;
import com.wechat.pay.java.service.payments.jsapi.JsapiService;
import com.wechat.pay.java.service.payments.jsapi.model.Amount;
import com.wechat.pay.java.service.payments.jsapi.model.CloseOrderRequest;
import com.wechat.pay.java.service.payments.jsapi.model.Payer;
import com.wechat.pay.java.service.payments.jsapi.model.PrepayRequest;
import com.wechat.pay.java.service.payments.jsapi.model.PrepayResponse;
import com.wechat.pay.java.service.payments.jsapi.model.QueryOrderByOutTradeNoRequest;
import com.wechat.pay.java.service.payments.jsapi.model.SettleInfo;
import com.wechat.pay.java.service.payments.model.Transaction;
import com.wechat.pay.java.service.refund.RefundService;
import com.wechat.pay.java.service.refund.model.AmountReq;
import com.wechat.pay.java.service.refund.model.CreateRequest;
import com.wechat.pay.java.service.refund.model.QueryByOutRefundNoRequest;
import com.wechat.pay.java.service.refund.model.Refund;
import com.wechat.pay.java.service.refund.model.RefundNotification;
import com.ycwl.basic.constant.NumberConstant;
import com.ycwl.basic.pay.entity.CancelOrderRequest;
import com.ycwl.basic.pay.entity.CreateOrderRequest;
import com.ycwl.basic.pay.entity.CreateOrderResponse;
import com.ycwl.basic.pay.entity.PayResponse;
import com.ycwl.basic.pay.entity.RefundResponse;
import com.ycwl.basic.pay.entity.RefundOrderRequest;
import com.ycwl.basic.pay.entity.RefundOrderResponse;
import com.ycwl.basic.pay.entity.WxMpPayConfig;
import com.ycwl.basic.pay.exceptions.PayWrongConfigException;
import org.apache.commons.lang3.StringUtils;

import jakarta.servlet.ServletInputStream;
import jakarta.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.SignatureException;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static com.wechat.pay.java.core.http.Constant.WECHAT_PAY_NONCE;
import static com.wechat.pay.java.core.http.Constant.WECHAT_PAY_SERIAL;
import static com.wechat.pay.java.core.http.Constant.WECHAT_PAY_SIGNATURE;
import static com.wechat.pay.java.core.http.Constant.WECHAT_PAY_TIMESTAMP;
import static com.wechat.pay.java.service.refund.model.Status.PROCESSING;
import static com.wechat.pay.java.service.refund.model.Status.SUCCESS;

public class WxMpPayAdapter implements IPayAdapter {
    private WxMpPayConfig config;
    public static final String WECHAT_PAY_SIGNATURE_TYPE = "Wechatpay-Signature-Type";
    public WxMpPayAdapter() {

    }
    public WxMpPayAdapter(WxMpPayConfig config) {
        this.config = config;
    }
    public WxMpPayConfig _config() {
        return config;
    }
    @Override
    public void loadConfig(Map<String, String> _config) {
        this.config = new WxMpPayConfig();
        if (_config != null) {
            this.config.setMerchantId(_config.get("merchantId"));
            this.config.setAppId(_config.get("appId"));
            this.config.setPrivateKey(_config.get("privateKey"));
            this.config.setSerialNumber(_config.get("serialNumber"));
            this.config.setWxPublicKey(_config.get("wxPublicKey"));
            this.config.setWxPublicKeyId(_config.get("wxPublicKeyId"));
            this.config.setApiV3Key(_config.get("apiV3Key"));
            if (_config.containsKey("enableProfitSharing")) {
                this.config.setEnableProfitSharing(_config.get("enableProfitSharing").equals("true"));
            }
        }
    }

    private Config clientConfig;

    public Config getConfig() {
        if (clientConfig == null) {
            if (StringUtils.isNotBlank(config.getWxPublicKeyId())) {
                clientConfig = new RSAPublicKeyConfig.Builder()
                        .merchantId(config.getMerchantId())
                        .privateKey(config.getPrivateKey())
                        .merchantSerialNumber(config.getSerialNumber())
                        .publicKey(config.getWxPublicKey())
                        .publicKeyId(config.getWxPublicKeyId())
                        .apiV3Key(config.getApiV3Key())
                        .build();
            } else {
                clientConfig = new RSAAutoCertificateConfig.Builder()
                        .merchantId(config.getMerchantId())
                        .privateKey(config.getPrivateKey())
                        .merchantSerialNumber(config.getSerialNumber())
                        .apiV3Key(config.getApiV3Key())
                        .build();
            }
        }
        return clientConfig;
    }

    @Override
    public CreateOrderResponse createOrder(CreateOrderRequest request) {
        CreateOrderResponse resp = new CreateOrderResponse();
        if (request.getPrice() <= 0) {
            resp.setSkipPay(true);
            return resp;
        }
        Config wxConfig = getConfig();
        JsapiService service = new JsapiService.Builder().config(wxConfig).build();
        PrepayRequest prepayRequest = new PrepayRequest();
        Amount amount = new Amount();
        amount.setTotal(request.getPrice());
        prepayRequest.setAmount(amount);
        prepayRequest.setAppid(config.getAppId());
        prepayRequest.setMchid(config.getMerchantId());
        prepayRequest.setDescription(request.getDescription());
        prepayRequest.setNotifyUrl(request.getNotifyUrl());
        prepayRequest.setOutTradeNo(request.getOrderNo());
        Payer payer = new Payer();
        payer.setOpenid(request.getUserIdentify());
        prepayRequest.setPayer(payer);
        SettleInfo settleInfo = new SettleInfo();
        settleInfo.setProfitSharing(config.isEnableProfitSharing());
        prepayRequest.setSettleInfo(settleInfo);
        PrepayResponse response = service.prepay(prepayRequest);
        resp.setSuccess(true);
        resp.setSkipPay(false);
        resp.setOrderNo(response.getPrepayId());
        return resp;
    }

    @Override
    public Map<String, Object> getPaymentParams(CreateOrderResponse response) {
        Map<String, Object> params = new HashMap<>();
        if (response.isSkipPay()) {
            return params;
        }
        Long timeStamp = System.currentTimeMillis() / NumberConstant.THOUSAND;
        params.put("appId", config.getAppId());
        params.put("timeStamp", timeStamp);
        String nonce = UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32);
        params.put("nonceStr", nonce);
        String signStr = Stream.of(config.getAppId(), String.valueOf(timeStamp), nonce, "prepay_id=" + response.getOrderNo())
                .collect(Collectors.joining("\n", "", "\n"));
        String sign;
        try {
            sign = getSign(signStr, config.getPrivateKey());
        } catch (InvalidKeyException | SignatureException | NoSuchAlgorithmException e) {
            throw new PayWrongConfigException("配置错误");
        }
        params.put("paySign", sign);
        params.put("signType", "RSA-SHA256");
        params.put("prepayId", "prepay_id=" + response.getOrderNo());
        return params;
    }

    @Override
    public PayResponse handleCallback(HttpServletRequest request) throws IOException {
        ServletInputStream inputStream = request.getInputStream();
        StringBuilder body = new StringBuilder();
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
        String s;
        // 读取回调请求体
        while ((s = bufferedReader.readLine()) != null) {
            body.append(s);
        }
        PayResponse resp = new PayResponse();

        String timestamp = request.getHeader(WECHAT_PAY_TIMESTAMP);
        String nonce = request.getHeader(WECHAT_PAY_NONCE);
        String signType = request.getHeader(WECHAT_PAY_SIGNATURE_TYPE);
        String serialNo = request.getHeader(WECHAT_PAY_SERIAL);
        String signature = request.getHeader(WECHAT_PAY_SIGNATURE);
        NotificationConfig config = (NotificationConfig) getConfig();
        NotificationParser parser = new NotificationParser(config);
        RequestParam requestParam = new RequestParam.Builder()
                .serialNumber(serialNo)
                .nonce(nonce)
                .signature(signature)
                .timestamp(timestamp)
                // 若未设置signType，默认值为 WECHATPAY2-SHA256-RSA2048
                .signType(signType)
                .body(body.toString())
                .build();
        Transaction parse = parser.parse(requestParam, Transaction.class);
        resp.setValid(true);
        resp.setOrderNo(parse.getOutTradeNo());
        resp.setTransactionId(parse.getTransactionId());
        if (parse.getAmount() != null) {
            resp.setOrderPrice(parse.getAmount().getTotal());
            resp.setPayPrice(parse.getAmount().getPayerTotal());
        }
        switch (parse.getTradeState()) {
            case SUCCESS:
                resp.setState(PayResponse.PAY_STATE.SUCCESS);
                break;
            case NOTPAY:
            case CLOSED:
            case REVOKED:
                resp.setState(PayResponse.PAY_STATE.FAIL);
                break;
            case REFUND:
                resp.setState(PayResponse.PAY_STATE.REFUND);
                break;
            default:
                resp.setState(PayResponse.PAY_STATE.CANCEL);
                break;
        }
        resp.setPayTime(parse.getSuccessTime());
        resp.setOriginalResponse(parse);
        return resp;
    }

    @Override
    public PayResponse queryOrder(String orderNo) {
        Config wxConfig = getConfig();
        JsapiService service = new JsapiService.Builder().config(wxConfig).build();
        QueryOrderByOutTradeNoRequest queryRequest = new QueryOrderByOutTradeNoRequest();
        queryRequest.setMchid(config.getMerchantId());
        queryRequest.setOutTradeNo(orderNo);
        PayResponse resp = new PayResponse();
        Transaction result = service.queryOrderByOutTradeNo(queryRequest);
        resp.setValid(true);
        resp.setOrderNo(result.getOutTradeNo());
        if (result.getAmount() != null) {
            resp.setOrderPrice(result.getAmount().getTotal());
            resp.setPayPrice(result.getAmount().getPayerTotal());
        }
        switch (result.getTradeState()) {
            case SUCCESS:
                resp.setState(PayResponse.PAY_STATE.SUCCESS);
                break;
            case NOTPAY:
            case CLOSED:
            case REVOKED:
                resp.setState(PayResponse.PAY_STATE.FAIL);
                break;
            case REFUND:
                resp.setState(PayResponse.PAY_STATE.REFUND);
                break;
            default:
                resp.setState(PayResponse.PAY_STATE.CANCEL);
                break;
        }
        resp.setPayTime(result.getSuccessTime());
        resp.setOriginalResponse(result);
        return resp;
    }

    @Override
    public RefundOrderResponse refund(RefundOrderRequest request) {
        RefundOrderResponse resp = new RefundOrderResponse();
        Config wxConfig = getConfig();
        RefundService service = new RefundService.Builder().config(wxConfig).build();
        CreateRequest createRequest = new CreateRequest();
        createRequest.setOutTradeNo(request.getOrderNo());
        createRequest.setOutRefundNo(request.getRefundNo());
        AmountReq amountReq = new AmountReq();
        amountReq.setTotal(Long.valueOf(request.getPrice()));
        amountReq.setRefund(Long.valueOf(request.getRefundPrice()));
        amountReq.setCurrency("CNY");
        createRequest.setAmount(amountReq);
        createRequest.setNotifyUrl(request.getNotifyUrl());
        Refund refund = service.create(createRequest);
        if (refund.getStatus() == SUCCESS || refund.getStatus() == PROCESSING) {
            resp.setSuccess(true);
            resp.setRefundNo(refund.getOutRefundNo());
        } else {
            resp.setSuccess(false);
            resp.setMessage(refund.getStatus().name());
        }
        return resp;
    }

    @Override
    public RefundResponse handleRefundCallback(HttpServletRequest request) throws IOException {
        ServletInputStream inputStream = request.getInputStream();
        StringBuilder body = new StringBuilder();
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
        String s;
        // 读取回调请求体
        while ((s = bufferedReader.readLine()) != null) {
            body.append(s);
        }
        RefundResponse resp = new RefundResponse();
        String timestamp = request.getHeader(WECHAT_PAY_TIMESTAMP);
        String nonce = request.getHeader(WECHAT_PAY_NONCE);
        String signType = request.getHeader(WECHAT_PAY_SIGNATURE_TYPE);
        String serialNo = request.getHeader(WECHAT_PAY_SERIAL);
        String signature = request.getHeader(WECHAT_PAY_SIGNATURE);
        NotificationConfig config = (NotificationConfig) getConfig();
        NotificationParser parser = new NotificationParser(config);RequestParam requestParam = new RequestParam.Builder()
                .serialNumber(serialNo)
                .nonce(nonce)
                .signature(signature)
                .timestamp(timestamp)
                // 若未设置signType，默认值为 WECHATPAY2-SHA256-RSA2048
                .signType(signType)
                .body(body.toString())
                .build();
        RefundNotification parse = parser.parse(requestParam, RefundNotification.class);
        resp.setValid(true);
        resp.setRefundTransactionId(parse.getRefundId());
        resp.setOriginalResponse(parse);
        if (parse.getRefundStatus() == SUCCESS) {
            //退款成功
            resp.setSuccess();
            resp.setRefundTime(parse.getSuccessTime());
            resp.setOrderNo(parse.getOutTradeNo());
            resp.setRefundNo(parse.getRefundId());
            if (parse.getAmount() != null) {
                resp.setRefundPrice(Math.toIntExact(parse.getAmount().getPayerRefund()));
                resp.setOrderPrice(Math.toIntExact(parse.getAmount().getTotal()));
            }
        } else {
            //退款失败
            resp.setFail();
        }
        return resp;
    }

    @Override
    public RefundResponse checkRefundStatus(String refundNo) {
        Config wxConfig = getConfig();
        RefundService service = new RefundService.Builder().config(wxConfig).build();
        QueryByOutRefundNoRequest request = new QueryByOutRefundNoRequest();
        request.setOutRefundNo(refundNo);
        RefundResponse resp = new RefundResponse();
        Refund result = service.queryByOutRefundNo(request);
        resp.setValid(true);
        resp.setOriginalResponse(result);
        if (result.getStatus() == SUCCESS) {
            //退款成功
            resp.setSuccess();
            resp.setRefundTime(result.getSuccessTime());
            resp.setOrderNo(result.getOutTradeNo());
            resp.setRefundNo(result.getRefundId());
            if (result.getAmount() != null) {
                resp.setRefundPrice(Math.toIntExact(result.getAmount().getPayerRefund()));
                resp.setOrderPrice(Math.toIntExact(result.getAmount().getTotal()));
            }
        } else {
            //退款失败
            resp.setFail();
        }
        return resp;
    }

    @Override
    public void cancelOrder(CancelOrderRequest request) {
        CloseOrderRequest closeOrderRequest = new CloseOrderRequest();
        closeOrderRequest.setOutTradeNo(request.getOrderNo());
        closeOrderRequest.setMchid(config.getMerchantId());
        Config config = getConfig();
        JsapiService service = new JsapiService.Builder().config(config).build();
        service.closeOrder(closeOrderRequest);
    }

    public static String getSign(String signatureStr,String privateKey) throws InvalidKeyException, SignatureException, NoSuchAlgorithmException {
        String replace = privateKey.replace("\\n", "\n");
        PrivateKey merchantPrivateKey = PemUtil.loadPrivateKeyFromString(replace);
        Signature sign = Signature.getInstance("SHA256withRSA");
        sign.initSign(merchantPrivateKey);
        sign.update(signatureStr.getBytes(StandardCharsets.UTF_8));
        return Base64.encode(sign.sign());
    }
}
