package com.czg.service.market.service.impl;

import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson2.JSONObject;
import com.czg.exception.CzgException;
import com.czg.service.RedisService;
import com.ijpay.core.IJPayHttpResponse;
import com.ijpay.core.enums.RequestMethodEnum;
import com.ijpay.core.enums.SignType;
import com.ijpay.core.kit.AesUtil;
import com.ijpay.core.kit.PayKit;
import com.ijpay.core.kit.RsaKit;
import com.ijpay.core.kit.WxPayKit;
import com.ijpay.wxpay.WxPayApi;
import com.ijpay.wxpay.enums.WxDomainEnum;
import com.ijpay.wxpay.enums.v3.BasePayApiEnum;
import com.ijpay.wxpay.model.v3.*;
import jakarta.servlet.http.HttpServletRequest;
import lombok.Data;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.Cipher;
import java.io.ByteArrayInputStream;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.PrivateKey;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Base64;
import java.util.Locale;
import java.util.Map;

/**
 * 微信支付相关
 * @author Administrator
 */
public abstract class BaseWx {
    public Config config;
    public RedisService redisService;
    public Logger log = LoggerFactory.getLogger(BaseWx.class);
    @Data
    public static class Config {
        public String appId;
        public String appSecret;
        public String certPath;
        public String pubKey;
        public String apiCertKey;
        public PrivateKey privateKey;
        public String apiCert;
        public String platformCertPath;
        public String platformCertNo;
        public String mchId;
        public String apiV3Key;
        public String apiV2Key;
        public String notifyUrl;
        public String refundNotifyUrl;
        public String transferNotifyUrl;
    }


     String getPhone(String code) {
        String requestBody = JSONObject.toJSONString(Map.of("code", code));
        String response = HttpUtil.post("https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=" + getAccessToken(false), requestBody);
        log.info("获取手机号响应: {}", response);
        JSONObject jsonObject = JSONObject.parseObject(response);
        Integer errCode = jsonObject.getInteger("errcode");

        if (Integer.valueOf(0).equals(errCode)) {
            return jsonObject.getJSONObject("phone_info").getString("phoneNumber");
        } else if (Integer.valueOf(40001).equals(errCode)) {
            getAccessToken(true);
        }
        throw new RuntimeException("获取手机号失败");
    }

     String getAccessToken(boolean refresh) {
        Object token = redisService.get("access_token");
        if (!refresh && token instanceof String) {
            return (String) token;
        }

        String response = HttpUtil.get("https://api.weixin.qq.com/cgi-bin/token",
                Map.of("grant_type", "client_credential", "appid", config.appId, "secret", config.appSecret)
        );

        log.info("获取access_token响应: {}", response);
        JSONObject jsonObject = JSONObject.parseObject(response);
        String accessToken = jsonObject.getString("access_token");
        if (accessToken == null) {
            throw new RuntimeException("获取access_token失败");
        }
        Long expiresIn = jsonObject.getLong("expires_in");
        if (expiresIn == null) {
            expiresIn = 7200L;
        }
        redisService.set("access_token", accessToken, expiresIn - 200);
        return accessToken;
    }

    /**
     * 使用微信支付平台证书公钥加密敏感信息（OAEP）
     */
     String encryptByPlatformCert(String content) {
         return PayKit.encryptData(content, config.pubKey);
    }

     String getSerialNumberFromPem(String certContent) {
        try {
            // 去掉 PEM 头尾并清理空格换行
            String pem = certContent
                    .replace("-----BEGIN CERTIFICATE-----", "")
                    .replace("-----END CERTIFICATE-----", "")
                    .replaceAll("\\s+", "");

            // Base64 解码
            byte[] certBytes = Base64.getDecoder().decode(pem);

            try (ByteArrayInputStream bis = new ByteArrayInputStream(certBytes)) {
                X509Certificate certificate = PayKit.getCertificate(bis);

                if (certificate != null) {
                    String serialNo = certificate.getSerialNumber()
                            .toString(16)
                            .toUpperCase(Locale.getDefault());

                    System.out.println("证书序列号：" + serialNo);

                    return serialNo;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("无效的证书", e);
        }
        return null;
    }

    public String getSerialNumber() {

        try (var certStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(config.getCertPath())) {
            X509Certificate certificate = PayKit.getCertificate(certStream);
            if (certificate != null) {
                String serialNo = certificate.getSerialNumber().toString(16).toUpperCase(Locale.getDefault());
                boolean isValid = PayKit.checkCertificateIsValid(certificate, config.getMchId(), -2);
                log.info("证书是否可用 {} 证书有效期为 {}", isValid, certificate.getNotAfter());
                log.info("证书序列号: {}", serialNo);
                return serialNo;
            }
        } catch (Exception e) {
            log.error("读取证书失败", e);
        }
        return null;
    }

    public JSONObject verifySignature(HttpServletRequest request) {
        try {
            log.info("开始校验签名并解密");
            String timestamp = request.getHeader("Wechatpay-Timestamp");
            String nonce = request.getHeader("Wechatpay-Nonce");
            String serialNo = request.getHeader("Wechatpay-Serial");
            String signature = request.getHeader("Wechatpay-Signature");
            String result = request.getReader().lines().reduce((a, b) -> a + b).orElse("");
            log.info("参数信息: timestamp: {}, nonce: {}, serialNo: {}, signature: {}, result: {}",  timestamp, nonce, serialNo, signature, result);
            boolean b = WxPayKit.verifySignature(signature, result, nonce, timestamp, config.pubKey);
            if (!b) {
                throw new CzgException("验签失败");
            }
            JSONObject jsonObject = JSONObject.parseObject(result);
            JSONObject resource = jsonObject.getJSONObject("resource");
            String associatedData = resource.getString("associated_data");
            String ciphertext = resource.getString("ciphertext");
            String nonceStr = resource.getString("nonce");

            String plainText = decryptToString(associatedData, nonceStr, ciphertext);
            log.info("充值支付通知明文 {}", plainText);
            return JSONObject.parseObject(plainText);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) throws Exception {
         String key = "-----BEGIN PUBLIC KEY-----\n" +
                 "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA7sdF3xWrWPCYRjwulCpY\n" +
                 "KeER1+deVRLWqsMxibarVXt9+hJpMwZtqyEW5/KQrK8CIpefEVuiZuHM5SXZsL7X\n" +
                 "xWi24WY3wMqxnnrbcVmw4IA4OhVekajXnU4EBMqHzRXTwQpkv0qLfp7J5wEify7u\n" +
                 "y02j0t38k57IToCEwOY5cP/1vt/bZPJOpeefldaOnmIncF6xklywdGztncSMIzli\n" +
                 "rs1+Jnbrjx9DnhWmBJSa6yWL7sZjBa8YvZBvQBBHC1b82LFawjwKHKDBZlT+kFnp\n" +
                 "aboDHKhR4+DbXREkwlU4In70tcMUbitkKt6T2qpErskeKy2uF4iUDQxo3NGMC4AX\n" +
                 "vQIDAQAB\n" +
                 "-----END PUBLIC KEY-----";
        String timestamp = "1761731254";
        String nonce = "z0PqTTIyTE6vLkZWzX3RmBlS5yodUY5J";
        String serialNo = "PUB_KEY_ID_0117300912032025102500112177000200";
        String signature = "yMBK0e54XUS9lzmgeuWzg0Xw4UIQg3/f2o3BreByipvnaJcb2eiSU/gdL8MnJWXgXYaVE/c5NgXAsagbGQhgKS6RQ37ZlUhWTun+jFGfFi5V2Djd01JaSMHHzGHWXApIV8DJnJlr2uCeJtX3U0SNXovWxlkeId8SPFPQRK4DNIjiDJsmzlB33VZ+ma8QtGsUg5E+dP9wz42xfUbGpnMjjKuyK7sUeSrgRaSqQ6fb1sUNGy1yaCs/ZS/KpMJL3d3eZbB9cRIP7eanlHx11Xi8tboLPmyVSTm01LHYdCBAAQzK3QlbtCMaK0dzpFepEABpVt42ajH0GfVPsyvep7k5/A==";
        String result = "{\"id\":\"3946093d-5053-5d9d-9821-3b52acccac06\",\"create_time\":\"2025-10-29T17:47:29+08:00\",\"resource_type\":\"encrypt-resource\",\"event_type\":\"MCHTRANSFER.BILL.FINISHED\",\"summary\":\"商家转账单据终态通知\",\"resource\":{\"original_type\":\"mch_payment\",\"algorithm\":\"AEAD_AES_256_GCM\",\"ciphertext\":\"mWr+e0tYehbdV7nIMXeFTe/moALfDh+ky48XWBd0Oc0mb99JhTnHgbfIfRijNdTPKm1NMAO0/l4oJ78rCiLnanW2u2Dx7Rme9JjJ41PqXaQKuQeNSaEez2yK1uwagpu1bWL5fStNXFHDpgzzd+2fFPCv+9pTWD6BhbPq5L2WKjOpFNe4wsl4cozUS2hzyU63nv+QbIlEOyQgXu/cXc0fU4AKUO1mApnBILIA9pHNJaDZiktfyYKHz01u39iVnVJSWDKllWKIEeEhlzKrQvP1vdArj/ifvaymlwYRyFmRz2TNkYxw4LLEWj772wFEibRZTp0mbW/78gDP/yxOreIgdI75VrxJx46JYfT9lWHD7BTDH/c97CnWQlRq2EH4E14Es4bQYqnuWJIrrB6XZ1duOvVS86hP2dq2nMFKYN6niRKeTfCJ3yRU0A==\",\"associated_data\":\"mch_payment\",\"nonce\":\"PwmzF2cR2MRD\"}}";
        boolean b = WxPayKit.verifySignature(signature, result, nonce, timestamp, "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA7sdF3xWrWPCYRjwulCpY\n" +
                "KeER1+deVRLWqsMxibarVXt9+hJpMwZtqyEW5/KQrK8CIpefEVuiZuHM5SXZsL7X\n" +
                "xWi24WY3wMqxnnrbcVmw4IA4OhVekajXnU4EBMqHzRXTwQpkv0qLfp7J5wEify7u\n" +
                "y02j0t38k57IToCEwOY5cP/1vt/bZPJOpeefldaOnmIncF6xklywdGztncSMIzli\n" +
                "rs1+Jnbrjx9DnhWmBJSa6yWL7sZjBa8YvZBvQBBHC1b82LFawjwKHKDBZlT+kFnp\n" +
                "aboDHKhR4+DbXREkwlU4In70tcMUbitkKt6T2qpErskeKy2uF4iUDQxo3NGMC4AX\n" +
                "vQIDAQAB");

        JSONObject jsonObject = JSONObject.parseObject(result);
        JSONObject resource = jsonObject.getJSONObject("resource");
        String associatedData = resource.getString("associated_data");
        String ciphertext = resource.getString("ciphertext");
        String nonceStr = resource.getString("nonce");


    }

    public String decryptToString(String associatedData, String nonceStr, String ciphertext) {
        AesUtil aesUtil = new AesUtil(config.getApiV3Key().getBytes(StandardCharsets.UTF_8));
        try {
            return aesUtil.decryptToString(
                    associatedData.getBytes(StandardCharsets.UTF_8),
                    nonceStr.getBytes(StandardCharsets.UTF_8),
                    ciphertext
            );
        } catch (GeneralSecurityException e) {
            throw new RuntimeException(e);
        }
    }

     public String getOpenId(String code) {

        String response = HttpUtil.get("https://api.weixin.qq.com/sns/jscode2session",
                Map.of("appid", config.getAppId() , "secret", config.getAppSecret(), "js_code", code, "grant_type", "authorization_code")
        );
        log.info("获取openId响应: {}", response);
        JSONObject jsonObject = JSONObject.parseObject(response);
        String openId = jsonObject.getString("openid");
        if (openId != null && !openId.isBlank()) {
            return openId;
        }
        throw new RuntimeException("获取openId失败");
    }

     public Map<String, String> v3Pay(String openId, BigDecimal amount, String desc, String tradeNo, String type) {

        if (desc == null) desc = "订单支付";
        UnifiedOrderModel model = new UnifiedOrderModel();
        model.setAppid(config.getAppId());
        model.setMchid(config.getMchId());
        model.setDescription(desc);
        model.setOut_trade_no(tradeNo);
        int total = amount.setScale(2, RoundingMode.UP).multiply(new BigDecimal(100)).intValueExact();
        model.setAmount(new Amount(total, "CNY"));
        model.setNotify_url(config.getNotifyUrl() + "/" + type);
        model.setPayer(new Payer(openId, null, null));

        String payInfo = JSONObject.toJSONString(model);
        log.info("统一下单参数: {}", payInfo);

        IJPayHttpResponse resp;
        try {
            resp = WxPayApi.v3(
                    RequestMethodEnum.POST,
                    WxDomainEnum.CHINA.toString(),
                    BasePayApiEnum.JS_API_PAY.toString(),
                    config.mchId,
                    getSerialNumberFromPem(config.apiCert),
                    null,
                    config.privateKey,
                    payInfo
            );
            log.info("统一下单响应: {}", resp);
            String body = resp.getBody();
            JSONObject jsonObject = JSONObject.parseObject(body);
            String prepayId = jsonObject.getString("prepay_id");
            if (StrUtil.isBlank(prepayId)) {
                throw new RuntimeException(jsonObject.getString("message"));
            }
            return WxPayKit.jsApiCreateSign(config.appId, prepayId, config.privateKey);

        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }


     Map<String, String> v2Pay(String openId, BigDecimal amount, String desc, String tradeNo) {

        Map<String, String> payModel = com.ijpay.wxpay.model.UnifiedOrderModel.builder()
                .appid(config.appId)
                .mch_id(config.mchId)
                .body(desc)
                .out_trade_no(tradeNo)
                .total_fee(amount.setScale(2, RoundingMode.UP).multiply(new BigDecimal(100)).intValueExact() + "")
                .fee_type("CNY")
                .notify_url(config.notifyUrl)
                .openid(openId)
                .trade_type("JSAPI")
                .build()
                .createSign(config.apiV2Key, SignType.MD5);

        log.info("统一下单参数: {}", payModel);
        String resp = WxPayApi.pushOrder(false, payModel);
        log.info("统一下单响应: {}", resp);

        Map<String, String> resultMap = WxPayKit.xmlToMap(resp);
        String returnCode = resultMap.get("return_code");
        String returnMsg = resultMap.get("return_msg");
        if (!WxPayKit.codeIsOk(returnCode)) {
            throw new CzgException(returnMsg);
        }
        String resultCode = resultMap.get("result_code");
        if (!WxPayKit.codeIsOk(resultCode)) {
            throw new CzgException(returnMsg);
        }
        String prepayId = resultMap.get("prepay_id");
        return WxPayKit.prepayIdCreateSign(prepayId, config.appId, config.appSecret, SignType.MD5);
    }


      String refund(String tradeNo, String refundTradeNo, BigDecimal amount) {

        int finalAmount = amount.multiply(new BigDecimal(100)).intValueExact();
        RefundModel model = new RefundModel();
        model.setOut_trade_no(tradeNo);
        model.setOut_refund_no(refundTradeNo);
        model.setAmount(new RefundAmount(finalAmount, "CNY", finalAmount));
        model.setNotify_url(config.refundNotifyUrl);

        String info = JSONObject.toJSONString(model);
        log.info("统一退款参数: {}", info);
        IJPayHttpResponse response;
        try {
            response = WxPayApi.v3(
                    RequestMethodEnum.POST,
                    WxDomainEnum.CHINA.toString(),
                    BasePayApiEnum.REFUND.toString(),
                    config.mchId,
                    getSerialNumber(),
                    getSerialNumber(),
                    config.apiCertKey,
                    info
            );
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        log.info("统一退款响应 {}", response);
        String body = response.getBody();
        JSONObject jsonObject = JSONObject.parseObject(body);
        if ("ABNORMAL".equals(jsonObject.getString("status")) || response.getStatus() != 200) {
            throw new CzgException("退款异常," + jsonObject.getString("message"));
        }
        return jsonObject.getString("refund_id");
    }

     public String genCode(String path, String scene) {
        Map<String, Object> params = Map.of(
                "scene", scene,
                "page", path,
                "width", 430
        );
        var response = HttpRequest.post("https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=" + getAccessToken(false))
                .body(JSONObject.toJSONString(params))
                .execute();

        byte[] bodyBytes = response.bodyBytes();
        String str = new String(bodyBytes);
        if (str.contains("errmsg")) {
            JSONObject json = JSONObject.parseObject(str);
            throw new CzgException(json.getString("errmsg"));
        }
        return "data:image/png;base64," + Base64.getEncoder().encodeToString(bodyBytes);
    }
    public static X509Certificate loadCertificate(String certStr) throws Exception {
        // 去掉 PEM 头尾
        String pem = certStr
                .replace("-----BEGIN CERTIFICATE-----", "")
                .replace("-----END CERTIFICATE-----", "")
                .replaceAll("\\s+", "");

        byte[] der = Base64.getDecoder().decode(pem);
        CertificateFactory factory = CertificateFactory.getInstance("X.509");
        return (X509Certificate) factory.generateCertificate(new ByteArrayInputStream(der));
    }


    public String rsaEncryptOAEP(String content) {
        try {
            Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding");
            cipher.init(Cipher.ENCRYPT_MODE,  RsaKit.loadPublicKey(config.pubKey));
            byte[] dataByte = content.getBytes(StandardCharsets.UTF_8);
            byte[] cipherData = cipher.doFinal(dataByte);
            return cn.hutool.core.codec.Base64.encode(cipherData);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }


     public JSONObject transferBalance(String openId, String name, BigDecimal amount, String remarkTxt, String billNoTxt) {

        String remark = remarkTxt == null ? "佣金" : remarkTxt;
        String billNo = billNoTxt == null ? IdUtil.simpleUUID() : billNoTxt;
        Map<String, Object> params = new java.util.HashMap<>(Map.of(
                "appid", config.appId,
                "out_bill_no", billNo,
                "transfer_scene_id", "1000",
                "openid", openId,
                "transfer_amount", amount.multiply(BigDecimal.valueOf(100)).intValue(),
                "transfer_remark", remark,
                "notify_url", config.transferNotifyUrl,
                "transfer_scene_report_infos", new Object[]{
                        Map.of("info_type", "活动名称", "info_content", "分销奖励"),
                        Map.of("info_type", "奖励说明", "info_content", "用户提现")
                }
        ));
        if (amount.compareTo(BigDecimal.valueOf(0.3)) >= 0) {
            params.put("user_name", rsaEncryptOAEP(name));
        }
        log.info("转账到零钱参数: {}", JSONObject.toJSONString(params));
        IJPayHttpResponse response = null;
        try {
            response = WxPayApi.v3(
                    RequestMethodEnum.POST,
                    WxDomainEnum.CHINA.toString(),
                    "/v3/fund-app/mch-transfer/transfer-bills",
                    config.mchId,
                    getSerialNumberFromPem(config.apiCert),
                    null,
                    config.privateKey,
                    JSONObject.toJSONString(params)
            );
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        log.info("转账到零钱响应 {}", response.getBody());
        JSONObject resp = JSONObject.parseObject(response.getBody());
        if (resp.containsKey("code")) {
            throw new CzgException("转账到零钱失败" + resp.getString("message") + ", 响应: " + resp.toJSONString());
        }
        return resp;
    }


}
