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 lombok.extern.slf4j.Slf4j;

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
 */
@Slf4j
public abstract class BaseWx {
    protected Config config;
    public RedisService redisService;
    /**
     * 微信 AccessToken 接口地址
     */
    protected static final String WX_ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token";
    /**
     * AccessToken 默认过期时间（秒）：微信官方默认7200秒
     */
    protected static final Long DEFAULT_EXPIRES_IN = 7200L;
    /**
     * 缓存过期偏移量（秒）：提前200秒过期，避免接口调用时刚好过期
     */
    protected static final Long EXPIRES_OFFSET = 200L;

    /**
     * 初始化配置信息
     */
    protected abstract void init();

    /**
     * 获取微信 AccessToken
     *
     * @param refresh 是否强制刷新（true=忽略缓存，重新获取；false=优先用缓存）
     * @return 有效的 AccessToken
     */
    public abstract String getAccessToken(boolean refresh);

    /**
     * 通过code 获取用户手机号
     *
     * @param code 微信code
     */
    public 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("获取手机号失败");
    }

    public String getOpenId(String code) {
        init();
        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) {
        init();
        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 CzgException(jsonObject.getString("message"));
            }
            return WxPayKit.jsApiCreateSign(config.appId, prepayId, config.privateKey);

        } catch (Exception e) {
            log.error("微信V3原生支付失败", e);
            throw new CzgException("暂无法支付,请联系管理员");
        }
    }

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

    public JSONObject verifySignature(HttpServletRequest request) {
        try {
            init();
            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 String decryptToString(String associatedData, String nonceStr, String ciphertext) {
        init();
        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);
        }
    }

    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());
                    return serialNo;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("无效的证书", e);
        }
        return null;
    }

    public String getSerialNumber(String certPath, String mchId) {

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


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

    /**
     * 提现
     */
    public JSONObject transferBalance(String openId, String name, BigDecimal amount, String remarkTxt, String billNoTxt) {
        init();
        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, config.pubKey));
        }
        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;
    }

    String refund(String tradeNo, String refundTradeNo, BigDecimal amount) {
        init();
        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(config.getCertPath(), config.mchId),
                    getSerialNumber(config.getCertPath(), config.mchId),
                    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, String pubKey) {
        try {
            Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding");
            cipher.init(Cipher.ENCRYPT_MODE, RsaKit.loadPublicKey(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);
        }
    }

    @Data
    protected 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;
    }

}
