package com.ylx.web.controller.massage;

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.file.FileWriter;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.ContentType;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.ijpay.core.IJPayHttpResponse;
import com.ijpay.core.enums.AuthTypeEnum;
import com.ijpay.core.enums.RequestMethodEnum;
import com.ijpay.core.kit.AesUtil;
import com.ijpay.core.kit.HttpKit;
import com.ijpay.core.kit.PayKit;
import com.ijpay.core.kit.WxPayKit;
import com.ijpay.core.utils.DateTimeZoneUtil;
import com.ijpay.wxpay.WxPayApi;
import com.ijpay.wxpay.WxPayApiConfigKit;
import com.ijpay.wxpay.enums.WxDomainEnum;
import com.ijpay.wxpay.enums.v3.BasePayApiEnum;
import com.ijpay.wxpay.enums.v3.CertAlgorithmTypeEnum;
import com.ijpay.wxpay.enums.v3.TransferApiEnum;
import com.ijpay.wxpay.model.v3.*;
import com.wechat.pay.java.service.partnerpayments.jsapi.JsapiServiceExtension;
import com.ylx.common.config.WxPayConfig;
import com.ylx.common.core.domain.R;
import com.ylx.massage.domain.TRecharge;
import com.ylx.massage.enums.BillTypeEnum;
import com.ylx.massage.enums.PayTypeEnum;
import com.ylx.massage.domain.ProductOrderInfo;
import com.ylx.massage.domain.TConsumptionLog;
import com.ylx.massage.domain.TWxUser;
import com.ylx.massage.enums.BillTypeEnum;
import com.ylx.massage.enums.ProductOrderStatusEnum;
import com.ylx.massage.service.IProductOrderInfoService;
import com.ylx.massage.service.RefundVoucherService;
import com.ylx.massage.service.TOrderService;
import com.ylx.massage.service.TRechargeService;
import com.ylx.massage.service.TWxUserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayInputStream;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.security.cert.X509Certificate;
import java.util.*;

import static com.ylx.common.constant.HttpStatus.SUCCESS;

/**
 * @author jianlong
 * @date 2024-04-03 15:27
 */

@RestController
@Slf4j
@RequestMapping("/wx/pay")
@Api(tags = {"微信支付"})
public class PayController {


    @Autowired
    private WxPayConfig wxPayProperties;

    @Resource
    private TRechargeService rechargeService;

    @Resource
    private TOrderService orderService;

    @Resource
    private RefundVoucherService refundVoucherService;

    @Resource
    private IProductOrderInfoService productOrderInfoService;

    @Resource
    private TWxUserService wxUserService;

    //    @Resource
//    private JsapiServiceExtension service;
    String serialNo;
    String platSerialNo;

    /**
     * 小程序微信支付的第一步,统一下单
     */
    @PostMapping("/pay")
    @ApiOperation("AIPV3微信支付充值")
    public R createUnifiedOrder(@RequestBody TRecharge recharge) throws Exception {
        TRecharge rechargeResp = rechargeService.recharge(recharge);
        return rechargeService.getPay(rechargeResp.getRechargeNo(), recharge.getdMoney(), recharge.getcOpenId(), BillTypeEnum.RECHARGE.getInfo(), BillTypeEnum.RECHARGE.getCode().toString());
    }

    /**
     * 支付
     *
     * @param setOutTradeNo
     * @param amount
     * @param openId
     * @param description
     * @return
     * @throws Exception
     */
    public R<String> getPay(String setOutTradeNo, BigDecimal amount, String openId, String description) throws Exception {
        String timeExpire = DateTimeZoneUtil.dateToTimeZone(System.currentTimeMillis() + 1000 * 60 * 3);
        UnifiedOrderModel unifiedOrderModel = new UnifiedOrderModel()
                .setAppid(wxPayProperties.getAppId())
                .setMchid(wxPayProperties.getMchId())
                //商品描述
                .setDescription(description)
                //订单号
                .setOut_trade_no(setOutTradeNo)
                //交易结束时间
                .setTime_expire(timeExpire)
                //附加数据
                .setAttach("夜来香")
                //通知地址异步接收微信支付结果通知的回调地址，通知url必须为外网可访问的url，不能携带参数。 公网域名必须为https，如果是走专线接入，使用专线NAT IP或者私有回调域名可使用http
                //示例值：https://www.weixin.qq.com/wxpay/pay.php
                .setNotify_url(wxPayProperties.getNotifyUrl())
                //支付金额以分为单位
                .setAmount(new Amount().setTotal(amount.multiply(new BigDecimal(100)).intValue()))
                //交易人
                .setPayer(new Payer().setOpenid(openId));

        log.info("统一下单参数 {}", JSONUtil.toJsonStr(unifiedOrderModel));
        IJPayHttpResponse response = WxPayApi.v3(
                RequestMethodEnum.POST,
                WxDomainEnum.CHINA.toString(),
                BasePayApiEnum.JS_API_PAY.toString(),
                wxPayProperties.getMchId(),
                getSerialNumber(),
                null,
                wxPayProperties.getCertKeyPath(),
                JSONUtil.toJsonStr(unifiedOrderModel)
        );

        log.info("统一下单响应 {}", response);
        // 根据证书序列号查询对应的证书来验证签名结果
        boolean verifySignature = WxPayKit.verifySignature(response, wxPayProperties.getPlatFormPath());
        log.info("verifySignature: {}", verifySignature);
        if (response.getStatus() == SUCCESS && verifySignature) {
            String body = response.getBody();
            JSONObject jsonObject = JSONUtil.parseObj(body);
            String prepayId = jsonObject.getStr("prepay_id");
            Map<String, String> map = WxPayKit.jsApiCreateSign(wxPayProperties.getAppId(), prepayId, wxPayProperties.getCertKeyPath());
            log.info("唤起支付参数:{}", map);
            return R.ok(JSONUtil.toJsonStr(map));
        }
        return R.ok(JSONUtil.toJsonStr(response));
    }

    //服务商模式下单
    public R<String> getPay1(String setOutTradeNo, BigDecimal amount, String openId, String description) throws Exception {
        String timeExpire = DateTimeZoneUtil.dateToTimeZone(System.currentTimeMillis() + 1000 * 60 * 3);
        UnifiedOrderModel unifiedOrderModel = new UnifiedOrderModel()
                .setSp_appid(wxPayProperties.getAppId())
                .setSp_mchid(wxPayProperties.getMchId())
                .setSub_mchid("123")//子商户号
                //商品描述
                .setDescription(description)
                //订单号
                .setOut_trade_no(setOutTradeNo)
                //交易结束时间
                .setTime_expire(timeExpire)
                //附加数据
                .setAttach("夜来香")
                //通知地址异步接收微信支付结果通知的回调地址，通知url必须为外网可访问的url，不能携带参数。 公网域名必须为https，如果是走专线接入，使用专线NAT IP或者私有回调域名可使用http
                //示例值：https://www.weixin.qq.com/wxpay/pay.php
                .setNotify_url(wxPayProperties.getNotifyUrl())
                //分账标识
                .setSettle_info(new SettleInfo().setProfit_sharing(true))
                //支付金额以分为单位
                .setAmount(new Amount().setTotal(amount.multiply(new BigDecimal(100)).intValue()))
                //交易人
                .setPayer(new Payer().setSp_openid(openId));

        log.info("统一下单参数 {}", JSONUtil.toJsonStr(unifiedOrderModel));
        IJPayHttpResponse response = WxPayApi.v3(
                RequestMethodEnum.POST,
                WxDomainEnum.CHINA.toString(),
                BasePayApiEnum.PARTNER_JS_API_PAY.toString(),
                wxPayProperties.getMchId(),
                getSerialNumber(),
                null,
                wxPayProperties.getCertKeyPath(),
                JSONUtil.toJsonStr(unifiedOrderModel)
        );

        log.info("统一下单响应 {}", response);
        // 根据证书序列号查询对应的证书来验证签名结果
        boolean verifySignature = WxPayKit.verifySignature(response, wxPayProperties.getPlatFormPath());
        log.info("verifySignature: {}", verifySignature);
        if (response.getStatus() == SUCCESS && verifySignature) {
            String body = response.getBody();
            JSONObject jsonObject = JSONUtil.parseObj(body);
            String prepayId = jsonObject.getStr("prepay_id");
            Map<String, String> map = WxPayKit.jsApiCreateSign(wxPayProperties.getAppId(), prepayId, wxPayProperties.getCertKeyPath());
            log.info("唤起支付参数:{}", map);
            return R.ok(JSONUtil.toJsonStr(map));
        }
        return R.ok(JSONUtil.toJsonStr(response));
    }

    /**
     * 获取商户API证书序列号
     * @return String 证书序列号
     */
    private String getSerialNumber() {
        if (StrUtil.isEmpty(serialNo)) {
            // 获取证书序列号
            X509Certificate certificate = PayKit.getCertificate(wxPayProperties.getCertPath());
            if (null != certificate) {
                serialNo = certificate.getSerialNumber().toString(16).toUpperCase();
                // 提前两天检查证书是否有效
                boolean isValid = PayKit.checkCertificateIsValid(certificate, wxPayProperties.getMchId(), -2);
                log.info("证书是否可用 {} 证书有效期为 {}", isValid, DateUtil.format(certificate.getNotAfter(), DatePattern.NORM_DATETIME_PATTERN));
            }
//            System.out.println("输出证书信息:\n" + certificate.toString());
//            // 输出关键信息，截取部分并进行标记
//            System.out.println("证书序列号:" + certificate.getSerialNumber().toString(16));
//            System.out.println("版本号:" + certificate.getVersion());
//            System.out.println("签发者：" + certificate.getIssuerDN());
//            System.out.println("有效起始日期：" + certificate.getNotBefore());
//            System.out.println("有效终止日期：" + certificate.getNotAfter());
//            System.out.println("主体名：" + certificate.getSubjectDN());
//            System.out.println("签名算法：" + certificate.getSigAlgName());
//            System.out.println("签名：" + certificate.getSignature().toString());
        }
        System.out.println("serialNo:" + serialNo);
        return serialNo;
    }

    /**
     * 微信支付回调接口
     *
     * @param request
     * @param response
     */
    @RequestMapping(value = "/payNotify", method = {org.springframework.web.bind.annotation.RequestMethod.POST, org.springframework.web.bind.annotation.RequestMethod.GET})
    @ResponseBody
    @ApiOperation("微信支付回调接口")
    public void payNotify(HttpServletRequest request, HttpServletResponse response) {
        log.info("微信支付回调接口====================================>>>>微信支付回调接口");
        Map<String, String> map = new HashMap<>(12);
        try {
            String timestamp = request.getHeader("Wechatpay-Timestamp");
            String nonce = request.getHeader("Wechatpay-Nonce");
            String serialNo = request.getHeader("Wechatpay-Serial");
            String signature = request.getHeader("Wechatpay-Signature");

            log.info("timestamp:{} nonce:{} serialNo:{} signature:{}", timestamp, nonce, serialNo, signature);
            String result = HttpKit.readData(request);
            log.info("支付通知密文 {}", result);

            // 需要通过证书序列号查找对应的证书，verifyNotify 中有验证证书的序列号
            String plainText = WxPayKit.verifyNotify(serialNo, result, signature, nonce, timestamp,
                    wxPayProperties.getMchKey(), wxPayProperties.getPlatFormPath());

            log.info("支付通知明文 {}", plainText);

            if (StrUtil.isNotEmpty(plainText)) {
                response.setStatus(200);
                map.put("code", "SUCCESS");
                map.put("message", "SUCCESS");
                // 处理业务逻辑
                JSONObject jsonObject = new JSONObject(plainText);
                if (jsonObject.get("attach").equals(BillTypeEnum.WX_PAY.getCode().toString())) {
                    // 服务订单支付成功
                    orderService.payNotifyOrder(jsonObject.get("out_trade_no").toString());
                } else if (jsonObject.get("attach").equals(PayTypeEnum.WX_PAY.getCode().toString())) {
                    // 商品订单支付成功
                    String productOrderNo = jsonObject.get("out_trade_no").toString();
                    log.info("商品订单支付回调开始处理，订单号：{}", productOrderNo);
                    productOrderInfoService.handleWxPayCallback(productOrderNo);
                    log.info("商品订单支付回调处理完成，订单号：{}", productOrderNo);
                } else {
                    TRecharge outTradeNo = rechargeService.increaseAmount(jsonObject.get("out_trade_no").toString());
                }
            } else {
                response.setStatus(500);
                map.put("code", "ERROR");
                map.put("message", "签名错误");
            }
            response.setHeader("Content-type", ContentType.JSON.toString());
            response.getOutputStream().write(JSONUtil.toJsonStr(map).getBytes(StandardCharsets.UTF_8));
            response.flushBuffer();
        } catch (Exception e) {
            log.error("系统异常", e);
        }
    }


    @RequestMapping(value = "/test", method = {org.springframework.web.bind.annotation.RequestMethod.POST, org.springframework.web.bind.annotation.RequestMethod.GET})
    @ResponseBody
    @ApiOperation("测试")
    public void test(HttpServletRequest request, HttpServletResponse response) {
        System.out.println("test=======================>");
    }

    @RequestMapping("/get")
    @ResponseBody
    public String v3Get() throws Exception {
        // 获取平台证书列表
        try {
            IJPayHttpResponse response = WxPayApi.v3(
                    RequestMethodEnum.GET,
                    WxDomainEnum.CHINA.toString(),
                    CertAlgorithmTypeEnum.getCertSuffixUrl(CertAlgorithmTypeEnum.RSA.getCode()),
                    wxPayProperties.getMchId(),
                    getSerialNumber(),
                    null,
                    wxPayProperties.getCertKeyPath(),
                    "",
                    AuthTypeEnum.RSA.getCode()
            );
            Map<String, List<String>> headers = response.getHeaders();
            log.info("请求头: {}", headers);
            String timestamp = response.getHeader("Wechatpay-Timestamp");
            String nonceStr = response.getHeader("Wechatpay-Nonce");
            String serialNumber = response.getHeader("Wechatpay-Serial");
            String signature = response.getHeader("Wechatpay-Signature");

            String body = response.getBody();
            int status = response.getStatus();

            log.info("serialNumber: {}", serialNumber);
            log.info("status: {}", status);
            log.info("body: {}", body);
            int isOk = 200;
            if (status == isOk) {
                JSONObject jsonObject = JSONUtil.parseObj(body);
                JSONArray dataArray = jsonObject.getJSONArray("data");
                // 默认认为只有一个平台证书
                JSONObject encryptObject = dataArray.getJSONObject(0);
                JSONObject encryptCertificate = encryptObject.getJSONObject("encrypt_certificate");
                String associatedData = encryptCertificate.getStr("associated_data");
                String cipherText = encryptCertificate.getStr("ciphertext");
                String nonce = encryptCertificate.getStr("nonce");
                String algorithm = encryptCertificate.getStr("algorithm");
                String serialNo = encryptObject.getStr("serial_no");
                final String platSerialNo = savePlatformCert(associatedData, nonce, cipherText, algorithm, wxPayProperties.getPlatFormPath());
                log.info("平台证书序列号: {} serialNo: {}", platSerialNo, serialNo);
                // 根据证书序列号查询对应的证书来验证签名结果
                boolean verifySignature = WxPayKit.verifySignature(response, wxPayProperties.getPlatFormPath());
                log.info("verifySignature:{}", verifySignature);
            }
            return body;
        } catch (Exception e) {
            log.error("获取平台证书列表异常", e);
            return null;
        }
    }

    private String savePlatformCert(String associatedData, String nonce, String cipherText, String algorithm, String certPath) {
        try {
            String key3 = wxPayProperties.getMchKey();
            String publicKey;
            if (StrUtil.equals(algorithm, AuthTypeEnum.SM2.getPlatformCertAlgorithm())) {
                publicKey = PayKit.sm4DecryptToString(key3, cipherText, nonce, associatedData);
            } else {
                AesUtil aesUtil = new AesUtil(wxPayProperties.getMchKey().getBytes(StandardCharsets.UTF_8));
                // 平台证书密文解密
                // encrypt_certificate 中的  associated_data nonce  ciphertext
                publicKey = aesUtil.decryptToString(
                        associatedData.getBytes(StandardCharsets.UTF_8),
                        nonce.getBytes(StandardCharsets.UTF_8),
                        cipherText
                );
            }
            if (StrUtil.isNotEmpty(publicKey)) {
                // 保存证书
                FileWriter writer = new FileWriter(certPath);
                writer.write(publicKey);
                // 获取平台证书序列号
                X509Certificate certificate = PayKit.getCertificate(new ByteArrayInputStream(publicKey.getBytes()));
                return certificate.getSerialNumber().toString(16).toUpperCase();
            }
            return "";
        } catch (Exception e) {
            log.error("保存平台证书异常", e);
            return e.getMessage();
        }
    }

    /**
     * 微信批量提现
     * @param openId
     * @return String
     */
    @RequestMapping("/batchTransfer")
    @ApiOperation("微信批量提现")
    @ResponseBody
    public String batchTransfer(@RequestParam(value = "openId", required = false, defaultValue = "o-_-itxuXeGW3O1cxJ7FXNmq8Wf8") String openId) {
        try {
            BatchTransferModel batchTransferModel = new BatchTransferModel()
                    .setAppid(wxPayProperties.getAppId())
                    .setOut_batch_no(PayKit.generateStr())
                    .setBatch_name("IJPay 测试微信转账到零钱")
                    .setBatch_remark("IJPay 测试微信转账到零钱")
                    .setTotal_amount(1)
                    .setTotal_num(1)
                    .setTransfer_detail_list(Collections.singletonList(
                            new TransferDetailInput()
                                    .setOut_detail_no(PayKit.generateStr())
                                    .setTransfer_amount(1)
                                    .setTransfer_remark("IJPay 测试微信转账到零钱")
                                    .setOpenid(openId)));

            log.info("发起商家转账请求参数 {}", JSONUtil.toJsonStr(batchTransferModel));
            IJPayHttpResponse response = WxPayApi.v3(
                    RequestMethodEnum.POST,
                    WxDomainEnum.CHINA.toString(),
                    TransferApiEnum.TRANSFER_BATCHES.toString(),
                    wxPayProperties.getMchId(),
                    getSerialNumber(),
                    null,
                    wxPayProperties.getCertKeyPath(),
                    JSONUtil.toJsonStr(batchTransferModel)
            );
            log.info("发起商家转账响应 {}", response);
            // 根据证书序列号查询对应的证书来验证签名结果
            boolean verifySignature = WxPayKit.verifySignature(response, wxPayProperties.getPlatFormPath());
            log.info("verifySignature: {}", verifySignature);
            if (response.getStatus() == SUCCESS && verifySignature) {
                return response.getBody();
            }
            return JSONUtil.toJsonStr(response);
        } catch (Exception e) {
            log.error("系统异常", e);
            return e.getMessage();
        }
    }

    /**
     * 退款
     *
     * @param outRefundNo   退款订单号
     * @param amount        退款金额
     * @param transactionId 微信支付订单号
     * @param outTradeNo    商户订单号
     * @return String 退款结果
     */
    @RequestMapping("/refund")
    @ResponseBody
    public String refund(@RequestParam(required = false) String outRefundNo, @RequestParam(required = false) BigDecimal amount, @RequestParam(required = false) String transactionId, @RequestParam(required = false) String outTradeNo) {
        try {
            return rechargeService.refund(outRefundNo, transactionId, outTradeNo, amount);
        } catch (Exception e) {
            log.error("退款异常", e);
            throw new RuntimeException(e);
        }
    }

    /**
     * 微信退款回调接口
     * @param request
     * @param response
     */
    @ApiOperation("微信退款回调接口")
    @RequestMapping(value = "/refundNotify", method = {org.springframework.web.bind.annotation.RequestMethod.POST, org.springframework.web.bind.annotation.RequestMethod.GET})
    public void refundWechatCallback(HttpServletRequest request, HttpServletResponse response) {
        log.info("微信退款回调接口====================================>>>>微信退款回调接口");
        Map<String, String> map = new HashMap<>(12);
        try {
            String timestamp = request.getHeader("Wechatpay-Timestamp");
            String nonce = request.getHeader("Wechatpay-Nonce");
            String serialNo = request.getHeader("Wechatpay-Serial");
            String signature = request.getHeader("Wechatpay-Signature");

            log.info("timestamp:{} nonce:{} serialNo:{} signature:{}", timestamp, nonce, serialNo, signature);
            String result = HttpKit.readData(request);
            log.info("退款通知密文 {}", result);

            // 需要通过证书序列号查找对应的证书，verifyNotify 中有验证证书的序列号
            String plainText = WxPayKit.verifyNotify(serialNo, result, signature, nonce, timestamp,
                    wxPayProperties.getMchKey(), wxPayProperties.getPlatFormPath());

            log.info("退款通知明文 {}", plainText);

            if (StrUtil.isNotEmpty(plainText)) {
                response.setStatus(200);
                map.put("code", "SUCCESS");
                map.put("message", "SUCCESS");
                // 处理业务逻辑
                JSONObject jsonObject = new JSONObject(plainText);
                //退款单号
                String refundNo = jsonObject.get("out_refund_no").toString();
                refundVoucherService.refundWechatCallback(refundNo);
            } else {
                response.setStatus(500);
                map.put("code", "ERROR");
                map.put("message", "签名错误");
            }
            response.setHeader("Content-type", ContentType.JSON.toString());
            response.getOutputStream().write(JSONUtil.toJsonStr(map).getBytes(StandardCharsets.UTF_8));
            response.flushBuffer();
        } catch (Exception e) {
            log.error("系统异常", e);
        }
    }


    /**
     * 根据商户订单号查询微信支付订单
     *
     * @param outTradeNo 商户订单号
     * @return R 订单详情
     */
    @GetMapping("/query/order/{outTradeNo}")
    @ApiOperation("根据商户订单号查询微信支付订单")
    public R queryOrderByOutTradeNo(@PathVariable("outTradeNo") String outTradeNo) {
        try {
            log.info("查询微信支付订单，商户订单号：{}", outTradeNo);

            // V3 API：根据商户订单号查询订单接口路径
            // GET /v3/pay/transactions/out-trade-no/{out_trade_no}
            String queryUrl = String.format("/v3/pay/transactions/out-trade-no/%s", outTradeNo);
            log.info("查询订单URL：{}", queryUrl);
            Map<String, String> queryParams = new HashMap<>();
            queryParams.put("mchid", wxPayProperties.getMchId());
            // 调用微信支付V3接口查询订单
            IJPayHttpResponse response = WxPayApi.v3(
                    RequestMethodEnum.GET,
                    WxDomainEnum.CHINA.toString(),
                    queryUrl,
                    wxPayProperties.getMchId(),
                    getSerialNumber(),
                    null,
                    wxPayProperties.getCertKeyPath(),
                    queryParams
            );
            log.info("查询订单响应状态：{}，响应体：{}", response.getStatus(), response.getBody());
            // 处理响应
            if (response.getStatus() == SUCCESS) {
                // 验证响应签名
                boolean verifySignature = WxPayKit.verifySignature(response, wxPayProperties.getPlatFormPath());
                log.info("响应签名验证结果：{}", verifySignature);

                if (verifySignature) {
                    log.info("商户订单号 {} 查询成功，订单信息：{}", outTradeNo, response.getBody());
                    JSONObject result = JSONUtil.parseObj(response.getBody());
                    return R.ok(result);
                } else {
                    log.error("商户订单号 {} 查询响应签名验证失败", outTradeNo);
                    return R.fail("查询订单响应签名验证失败");
                }
            } else {
                log.error("商户订单号 {} 查询失败，状态码：{}，响应：{}", outTradeNo, response.getStatus(), response.getBody());
                return R.fail("查询订单失败：" + response.getBody());
            }
        } catch (Exception e) {
            log.error("查询微信支付订单异常，商户订单号：{}，错误：{}", outTradeNo, e.getMessage(), e);
            return R.fail("查询订单异常：" + e.getMessage());
        }
    }

}
