package com.jeesharp.business.modules.pay.service.weixin;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.binarywang.wxpay.bean.notify.SignatureHeader;
import com.github.binarywang.wxpay.bean.notify.WxPayNotifyV3Result;
import com.github.binarywang.wxpay.bean.notify.WxPayPartnerNotifyV3Result;
import com.github.binarywang.wxpay.bean.notify.WxPayPartnerRefundNotifyV3Result;
import com.github.binarywang.wxpay.bean.notify.WxPayRefundNotifyV3Result;
import com.github.binarywang.wxpay.exception.WxPayException;
import com.github.binarywang.wxpay.service.WxPayService;
import com.google.gson.Gson;
import com.jeesharp.business.modules.pay.config.weixin.PayWechatCredentialSupport;
import com.jeesharp.business.modules.pay.config.weixin.PayWechatWxPayClientHolder;
import com.jeesharp.business.modules.pay.paychannelconfig.support.PayChannelTenantSupport;
import com.jeesharp.business.modules.pay.paywechatconfig.entity.PayWechatConfig;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;

/**
 * 支付中心：微信 V3 回调验签与解密（配置来自 {@code pay_wechat_config}）。
 * <p>解析优先使用 {@link WxPayService#parsePartnerOrderNotifyV3Result}（服务商/子商户）；失败时回退
 * {@link WxPayService#parseOrderNotifyV3Result}（直连商户），与当前 JSAPI 统一下单形态兼容。</p>
 * <p>外层报文校验与应答形态对齐微信支付商户文档「支付成功回调通知」：{@code resource_type} 须为
 * {@code encrypt-resource}；商户应在验签通过后尽快应答 HTTP 200/204 且无包体（由调用方 Controller 实现）。</p>
 *
 * @see <a href="https://pay.weixin.qq.com/doc/v3/merchant/4012791861">支付成功回调通知</a>
 * @author jeesharp
 * @since 2026-05-25
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class PayWechatV3NotificationService {

    private static final TypeReference<LinkedHashMap<String, Object>> MAP_TYPE = new TypeReference<>() {};

    private final PayWechatWxPayClientHolder payWechatWxPayClientHolder;
    private final ObjectMapper objectMapper = new ObjectMapper();
    private final Gson gson = new Gson();

    public Map<String, Object> parseNotification(String rawBody, Map<String, String> headers) throws Exception {
        return parseNotification(rawBody, headers, null);
    }

    public Map<String, Object> parseNotification(String rawBody, Map<String, String> headers, String tenantIdHint)
            throws Exception {
        if (rawBody == null || rawBody.isBlank()) {
            throw new IllegalArgumentException("请求体为空");
        }
        Map<String, Object> body = objectMapper.readValue(rawBody, MAP_TYPE);
        validateOuterWechatV3Notify(body);

        String tenantId = PayChannelTenantSupport.normalize(tenantIdHint);
        PayWechatConfig config = payWechatWxPayClientHolder.requireActiveConfig(tenantId);
        SignatureHeader signatureHeader;
        if (PayWechatCredentialSupport.isSkipSignatureVerify(config)) {
            log.warn("Pay center wechat notify: 已跳过签名校验，禁止在生产环境开启");
            signatureHeader = null;
        } else {
            signatureHeader = buildSignatureHeader(headers, config);
        }
        WxPayService wxPayService = payWechatWxPayClientHolder.requireWxPayService(tenantId);
        try {
            PayWechatCredentialSupport.requireValidApiV3KeyForRealWx(config.getApiV3Key());
            Object decryptBean = isRefundNotifyEvent(body)
                ? parseRefundNotifyDecryptBean(wxPayService, rawBody, signatureHeader)
                : parseNotifyDecryptBean(wxPayService, rawBody, signatureHeader);
            @SuppressWarnings("unchecked")
            Map<String, Object> decrypted = gson.fromJson(gson.toJson(decryptBean), Map.class);
            body.put("resource_decrypted", decrypted);
            return body;
        } catch (WxPayException e) {
            if (isInvalidApiV3AesKeyError(e)) {
                throw new IllegalStateException(apiV3KeyInvalidForDecryptHint(), e);
            }
            String msg = e.getCustomErrorMsg() != null && !e.getCustomErrorMsg().isBlank()
                ? e.getCustomErrorMsg() : e.getMessage();
            if (msg != null && (msg.contains("头部信息验证失败") || msg.contains("验签"))) {
                throw new SecurityException("微信支付回调验签失败", e);
            }
            throw e;
        } catch (IllegalArgumentException e) {
            if (isInvalidApiV3AesKeyError(e)) {
                throw new IllegalStateException(apiV3KeyInvalidForDecryptHint(), e);
            }
            throw e;
        }
    }

    private static String apiV3KeyInvalidForDecryptHint() {
        return "微信支付回调解密失败：pay_wechat_config.api_v3_key 无效（须为商户平台 32 位密钥，不能为界面脱敏占位 "
            + PayWechatCredentialSupport.SECRET_FIELD_MASK + "）。请在管理后台【支付·微信配置】保存正确密钥或修正数据库。";
    }

    /**
     * WxJava 解密失败时常为 {@code IllegalArgumentException: ... Invalid AES key length: N bytes}，
     * 或 {@link java.security.InvalidKeyException} 出现在 cause 链中。
     */
    private static boolean isInvalidApiV3AesKeyError(Throwable e) {
        for (Throwable t = e; t != null; t = t.getCause()) {
            if (t instanceof java.security.InvalidKeyException) {
                return true;
            }
            String m = t.getMessage();
            if (m != null && m.contains("Invalid AES key length")) {
                return true;
            }
        }
        return false;
    }

    /**
     * 校验 V3 回调外层字段（与官方文档「通知参数」一致）。
     */
    private static void validateOuterWechatV3Notify(Map<String, Object> body) {
        Object resourceType = body.get("resource_type");
        if (resourceType == null || String.valueOf(resourceType).isBlank()) {
            throw new IllegalArgumentException("缺少 resource_type");
        }
        if (!"encrypt-resource".equals(String.valueOf(resourceType))) {
            throw new IllegalArgumentException("resource_type 须为 encrypt-resource");
        }
    }

    private SignatureHeader buildSignatureHeader(Map<String, String> headers, PayWechatConfig config) {
        Map<String, String> n = normalizeHeaders(headers);
        String timestamp = n.get("wechatpay-timestamp");
        String nonce = n.get("wechatpay-nonce");
        String signature = n.get("wechatpay-signature");
        String serial = n.get("wechatpay-serial");
        if (isBlank(timestamp) || isBlank(nonce) || isBlank(signature)) {
            throw new IllegalArgumentException("缺少微信支付回调签名头");
        }
        String platformSerial = config.getPlatformSerialNo();
        if (!isBlank(platformSerial) && !platformSerial.equals(serial)) {
            throw new SecurityException("微信支付平台证书序列号不匹配");
        }
        return SignatureHeader.builder()
            .timeStamp(timestamp)
            .nonce(nonce)
            .signature(signature)
            .serial(serial == null ? "" : serial)
            .build();
    }

    private static Map<String, String> normalizeHeaders(Map<String, String> headers) {
        Map<String, String> out = new LinkedHashMap<>();
        if (headers == null) {
            return out;
        }
        headers.forEach((k, v) -> {
            if (k != null) {
                out.put(k.toLowerCase(Locale.ROOT), v);
            }
        });
        return out;
    }

    private static boolean isBlank(String value) {
        return value == null || value.isBlank();
    }

    private static boolean isRefundNotifyEvent(Map<String, Object> body) {
        Object eventType = body.get("event_type");
        return eventType != null && String.valueOf(eventType).startsWith("REFUND.");
    }

    /**
     * 优先服务商 V3 退款回调解析，失败则直连退款解析。
     */
    private Object parseRefundNotifyDecryptBean(WxPayService wxPayService, String rawBody, SignatureHeader signatureHeader)
        throws WxPayException {
        try {
            WxPayPartnerRefundNotifyV3Result partner =
                wxPayService.parsePartnerRefundNotifyV3Result(rawBody, signatureHeader);
            if (partner.getResult() != null) {
                log.debug("Pay center wechat refund notify: use parsePartnerRefundNotifyV3Result");
                return partner.getResult();
            }
        } catch (WxPayException e) {
            log.info(
                "Pay center wechat refund notify: parsePartnerRefundNotifyV3Result failed ({}), try parseRefundNotifyV3Result",
                e.getMessage());
        }
        WxPayRefundNotifyV3Result direct = wxPayService.parseRefundNotifyV3Result(rawBody, signatureHeader);
        if (direct.getResult() == null) {
            throw new IllegalStateException("微信退款回调解密结果为空");
        }
        log.debug("Pay center wechat refund notify: use parseRefundNotifyV3Result");
        return direct.getResult();
    }

    /**
     * 优先服务商 V3 支付回调解析，失败则直连解析（与统一下单直连 / 服务商模式对齐）。
     */
    private Object parseNotifyDecryptBean(WxPayService wxPayService, String rawBody, SignatureHeader signatureHeader)
        throws WxPayException {
        try {
            WxPayPartnerNotifyV3Result partner = wxPayService.parsePartnerOrderNotifyV3Result(rawBody, signatureHeader);
            if (partner.getResult() != null) {
                log.debug("Pay center wechat notify: use parsePartnerOrderNotifyV3Result");
                return partner.getResult();
            }
        } catch (WxPayException e) {
            log.info("Pay center wechat notify: parsePartnerOrderNotifyV3Result failed ({}), try parseOrderNotifyV3Result",
                e.getMessage());
        }
        WxPayNotifyV3Result direct = wxPayService.parseOrderNotifyV3Result(rawBody, signatureHeader);
        if (direct.getResult() == null) {
            throw new IllegalStateException("微信支付回调解密结果为空");
        }
        log.debug("Pay center wechat notify: use parseOrderNotifyV3Result");
        return direct.getResult();
    }
}
