package shop.alien.store.strategy.platformMembershipPayment.impl;

import com.alibaba.fastjson.JSON;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import shop.alien.entity.result.R;
import shop.alien.entity.store.PlatformShopMembershipOrder;
import shop.alien.entity.store.PlatformShopMembershipPaymentOrder;
import shop.alien.store.service.PlatformShopMembershipOrderService;
import shop.alien.store.service.PlatformShopMembershipPaymentOrderService;
import shop.alien.store.strategy.payment.impl.WeChatPaymentStrategyImpl;
import shop.alien.store.strategy.platformMembershipPayment.PlatformMembershipPaymentStrategy;
import shop.alien.store.util.WXPayUtility;
import shop.alien.util.common.UniqueRandomNumGenerator;
import shop.alien.util.common.constant.PaymentEnum;
import shop.alien.util.system.OSUtil;

import javax.annotation.PostConstruct;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * 平台店铺会员卡 — 微信预支付/查单（支付流水写入 platform_shop_membership_payment_order）
 * <p>支付参数与 {@link shop.alien.store.strategy.payment.impl.WeChatPaymentStrategyImpl} 一致，从 payment.wechatPay.business 读取，不读 store_payment_config。
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class PlatformMembershipWechatPaymentStrategyImpl implements PlatformMembershipPaymentStrategy {

    private static final String POSTMETHOD = "POST";
    private static final String GETMETHOD = "GET";
    private static final String REDIS_PREPAY_KEY_PREFIX = "platform:membership:wechat:prepay:order:";
    private static final long REDIS_PREPAY_EXPIRE_SECONDS = 15 * 60;
    private static final String REDIS_PREPAY_DEBOUNCE_KEY_PREFIX = "platform:membership:wechat:prepay:debounce:order:";
    private static final long REDIS_PREPAY_DEBOUNCE_SECONDS = 5;

    @Value("${payment.wechatPay.host}")
    private String wechatPayApiHost;

    @Value("${payment.wechatPay.prePayPath}")
    private String prePayPath;

    @Value("${payment.wechatPay.searchOrderByOutTradeNoPath}")
    private String searchOrderByOutTradeNoPath;

    @Value("${payment.wechatPay.refundPath:/v3/refund/domestic/refunds}")
    private String refundPath;

    @Value("${payment.wechatPay.business.refundNotifyUrl:}")
    private String businessRefundNotifyUrl;

    @Value("${payment.wechatPay.business.appId}")
    private String appId;

    @Value("${payment.wechatPay.business.mchId}")
    private String mchId;

    @Value("${payment.wechatPay.business.win.privateKeyPath}")
    private String privateWinKeyPath;

    @Value("${payment.wechatPay.business.linux.privateKeyPath}")
    private String privateLinuxKeyPath;

    @Value("${payment.wechatPay.business.win.wechatPayPublicKeyFilePath}")
    private String wechatWinPayPublicKeyFilePath;

    @Value("${payment.wechatPay.business.linux.wechatPayPublicKeyFilePath}")
    private String wechatLinuxPayPublicKeyFilePath;

    @Value("${payment.wechatPay.business.merchantSerialNumber}")
    private String merchantSerialNumber;

    @Value("${payment.wechatPay.business.wechatPayPublicKeyId}")
    private String wechatPayPublicKeyId;

    @Value("${payment.wechatPay.business.prePayNotifyUrl}")
    private String prePayNotifyUrl;

    private PrivateKey privateKey;
    private PublicKey wechatPayPublicKey;

    private final PlatformShopMembershipOrderService platformShopMembershipOrderService;
    private final PlatformShopMembershipPaymentOrderService membershipPaymentOrderService;
    private final StringRedisTemplate stringRedisTemplate;

    @PostConstruct
    public void loadWechatBusinessKeys() {
        String privateKeyPath;
        String wechatPayPublicKeyFilePath;
        if ("windows".equals(OSUtil.getOsName())) {
            privateKeyPath = privateWinKeyPath;
            wechatPayPublicKeyFilePath = wechatWinPayPublicKeyFilePath;
        } else {
            privateKeyPath = privateLinuxKeyPath;
            wechatPayPublicKeyFilePath = wechatLinuxPayPublicKeyFilePath;
        }
        this.privateKey = WXPayUtility.loadPrivateKeyFromPath(privateKeyPath);
        this.wechatPayPublicKey = WXPayUtility.loadPublicKeyFromPath(wechatPayPublicKeyFilePath);
    }

    @Override
    public R<Map<String, Object>> createPrePay(Integer storeId, Integer membershipOrderId, String amountYuan, String subject, Integer userId) {
        if (storeId == null || membershipOrderId == null) {
            return R.fail("门店ID和会员卡订单ID不能为空");
        }
        if (StringUtils.isBlank(amountYuan)) {
            return R.fail("支付金额不能为空");
        }
        BigDecimal amountYuanBD;
        try {
            amountYuanBD = new BigDecimal(amountYuan).setScale(2, RoundingMode.HALF_UP);
        } catch (NumberFormatException e) {
            return R.fail("支付金额格式不正确");
        }
        if (amountYuanBD.compareTo(BigDecimal.ZERO) <= 0) {
            return R.fail("支付金额必须大于0");
        }
        if (StringUtils.isBlank(subject)) {
            return R.fail("订单描述不能为空");
        }
        PlatformShopMembershipOrder order = platformShopMembershipOrderService.getById(membershipOrderId);
        if (order == null) {
            return R.fail("会员卡订单不存在");
        }
        if (!order.getStoreId().equals(storeId)) {
            return R.fail("订单与门店不匹配");
        }
        if (order.getPayStatus() != null && order.getPayStatus() != PlatformShopMembershipOrder.PAY_STATUS_PENDING) {
            return R.fail("订单不可支付");
        }
        BigDecimal expect = order.getPayAmount() != null ? order.getPayAmount() : BigDecimal.ZERO;
        if (amountYuanBD.compareTo(expect.setScale(2, RoundingMode.HALF_UP)) != 0) {
            return R.fail("支付金额与订单金额不一致");
        }

        String redisKey = REDIS_PREPAY_KEY_PREFIX + membershipOrderId;
        String cached = stringRedisTemplate.opsForValue().get(redisKey);
        if (StringUtils.isNotBlank(cached)) {
            try {
                @SuppressWarnings("unchecked")
                Map<String, Object> data = JSON.parseObject(cached, Map.class);
                if (data != null && !data.isEmpty()) {
                    log.info("会员卡微信预支付命中缓存，membershipOrderId={}", membershipOrderId);
                    return R.data(data);
                }
            } catch (Exception e) {
                log.warn("解析会员卡微信预支付缓存失败，将重新发起，membershipOrderId={}", membershipOrderId, e);
            }
        }

        String debounceKey = REDIS_PREPAY_DEBOUNCE_KEY_PREFIX + membershipOrderId;
        Boolean debounceSet = stringRedisTemplate.opsForValue().setIfAbsent(debounceKey, "1", REDIS_PREPAY_DEBOUNCE_SECONDS, TimeUnit.SECONDS);
        if (Boolean.FALSE.equals(debounceSet)) {
            return R.fail("请勿重复提交，请稍后再试");
        }

        int deleted = membershipPaymentOrderService.logicDeletePendingByMembershipOrderIdAndPayType(
                membershipOrderId, PaymentEnum.WECHAT_PAY.getType());
        if (deleted > 0) {
            log.info("已逻辑删除该会员卡订单下微信支付单 {} 条，membershipOrderId={}", deleted, membershipOrderId);
        }

        String outTradeNo = UniqueRandomNumGenerator.generateUniqueCode(19);
        Date now = new Date();
        PlatformShopMembershipPaymentOrder paymentOrder = new PlatformShopMembershipPaymentOrder();
        paymentOrder.setPaymentNo(membershipPaymentOrderService.generatePaymentNo());
        paymentOrder.setMembershipOrderId(order.getId());
        paymentOrder.setOrderSn(order.getOrderSn());
        paymentOrder.setStoreId(storeId);
        paymentOrder.setPayType(PaymentEnum.WECHAT_PAY.getType());
        paymentOrder.setOutTradeNo(outTradeNo);
        paymentOrder.setPayAmount(amountYuanBD);
        paymentOrder.setPayStatus(0);
        paymentOrder.setPayerUserId(userId);
        paymentOrder.setSubject(subject);
        paymentOrder.setCreatedTime(now);
        paymentOrder.setUpdatedTime(now);
        membershipPaymentOrderService.save(paymentOrder);

        try {
            WeChatPaymentStrategyImpl.CommonPrepayRequest request = new WeChatPaymentStrategyImpl.CommonPrepayRequest();
            request.appid = appId;
            request.mchid = mchId;
            request.description = subject;
            request.outTradeNo = outTradeNo;
            request.notifyUrl = StringUtils.isNotBlank(prePayNotifyUrl) ? prePayNotifyUrl : "";
            request.amount = new WeChatPaymentStrategyImpl.CommonAmountInfo();
            request.amount.total = amountYuanBD.multiply(new BigDecimal(100)).longValue();
            request.amount.currency = "CNY";

            WeChatPaymentStrategyImpl.DirectAPIv3AppPrepayResponse response = prePayOrderRun(request);
            if (response == null || StringUtils.isBlank(response.prepayId)) {
                return R.fail("微信预支付失败");
            }

            long timestamp = System.currentTimeMillis() / 1000;
            String nonce = WXPayUtility.createNonce(32);
            String message = String.format("%s\n%s\n%s\n%s\n", appId, timestamp, nonce, response.prepayId);
            Signature sign = Signature.getInstance("SHA256withRSA");
            sign.initSign(privateKey);
            sign.update(message.getBytes(StandardCharsets.UTF_8));
            String signStr = Base64.getEncoder().encodeToString(sign.sign());

            Map<String, Object> data = new HashMap<>();
            data.put("outTradeNo", outTradeNo);
            data.put("orderSn", order.getOrderSn());
            data.put("orderId", order.getId());
            data.put("paymentNo", paymentOrder.getPaymentNo());
            data.put("prepayId", response.prepayId);
            data.put("appId", appId);
            data.put("mchId", mchId);
            data.put("sign", signStr);
            data.put("timestamp", String.valueOf(timestamp));
            data.put("nonce", nonce);
            data.put("orderStr", "");

            stringRedisTemplate.opsForValue().set(redisKey, JSON.toJSONString(data), REDIS_PREPAY_EXPIRE_SECONDS, TimeUnit.SECONDS);
            log.info("会员卡微信预支付成功 storeId={}, orderSn={}, outTradeNo={}", storeId, order.getOrderSn(), outTradeNo);
            return R.data(data);
        } catch (WXPayUtility.ApiException e) {
            log.error("会员卡微信预支付异常 storeId={}, membershipOrderId={}", storeId, membershipOrderId, e);
            return R.fail("预支付失败：" + e.getMessage());
        } catch (Exception e) {
            log.error("会员卡微信预支付异常 storeId={}", storeId, e);
            return R.fail("预支付失败：" + e.getMessage());
        }
    }

    @Override
    public R<Object> queryPayStatus(Integer storeId, String outTradeNo) {
        if (storeId == null || StringUtils.isBlank(outTradeNo)) {
            return R.fail("门店ID和商户订单号不能为空");
        }
        PlatformShopMembershipPaymentOrder paymentOrder = membershipPaymentOrderService.getByOutTradeNo(outTradeNo);
        if (paymentOrder == null) {
            return R.fail("支付单不存在");
        }
        if (!storeId.equals(paymentOrder.getStoreId())) {
            return R.fail("支付单与门店不匹配");
        }
        PlatformShopMembershipOrder order = platformShopMembershipOrderService.getById(paymentOrder.getMembershipOrderId());
        if (order == null) {
            return R.fail("会员卡订单不存在");
        }
        if (order.getPayStatus() != null && order.getPayStatus() == PlatformShopMembershipOrder.PAY_STATUS_PAID) {
            return R.success("支付成功");
        }
        try {
            WeChatPaymentStrategyImpl.QueryByWxTradeNoRequest req = new WeChatPaymentStrategyImpl.QueryByWxTradeNoRequest();
            req.transactionId = outTradeNo;
            req.mchid = mchId;
            WeChatPaymentStrategyImpl.DirectAPIv3QueryResponse response = searchOrderRun(req);
            if (response == null) {
                return R.fail("查询失败");
            }
            if ("SUCCESS".equals(response.tradeState)) {
                Date now = new Date();
                paymentOrder.setPayStatus(1);
                paymentOrder.setTradeNo(response.transactionId);
                paymentOrder.setPayTime(now);
                paymentOrder.setUpdatedTime(now);
                membershipPaymentOrderService.updateById(paymentOrder);
                platformShopMembershipOrderService.applyPaySuccess(order.getId(), now, outTradeNo, response.transactionId, PaymentEnum.WECHAT_PAY.getType());
                return R.success("支付成功");
            }
            if ("CLOSED".equals(response.tradeState)) {
                return R.fail("交易已关闭");
            }
            if ("NOTPAY".equals(response.tradeState) || "USERPAYING".equals(response.tradeState)) {
                return R.fail("等待用户付款");
            }
            return R.fail("订单状态：" + (response.tradeStateDesc != null ? response.tradeStateDesc : response.tradeState));
        } catch (WXPayUtility.ApiException e) {
            log.error("会员卡微信查单异常 outTradeNo={}", outTradeNo, e);
            return R.fail("查询异常：" + e.getMessage());
        } catch (Exception e) {
            log.error("会员卡微信查单异常 storeId={}", storeId, e);
            return R.fail("查询失败：" + e.getMessage());
        }
    }

    @Override
    public String getType() {
        return PaymentEnum.WECHAT_PAY.getType();
    }

    @Override
    public R<String> refundForTest(PlatformShopMembershipPaymentOrder po, String refundAmountYuan, String refundReason) {
        if (po == null || StringUtils.isBlank(po.getOutTradeNo())) {
            return R.fail("支付单或商户订单号无效");
        }
        if (!PaymentEnum.WECHAT_PAY.getType().equals(po.getPayType())) {
            return R.fail("非微信会员卡支付单");
        }
        if (StringUtils.isBlank(refundAmountYuan)) {
            return R.fail("退款金额不能为空");
        }
        BigDecimal refundYuan;
        try {
            refundYuan = new BigDecimal(refundAmountYuan).setScale(2, RoundingMode.HALF_UP);
        } catch (NumberFormatException e) {
            return R.fail("退款金额格式不正确");
        }
        if (refundYuan.compareTo(BigDecimal.ZERO) <= 0) {
            return R.fail("退款金额必须大于0");
        }
        String outTradeNo = po.getOutTradeNo();
        long refundFen = refundYuan.multiply(new BigDecimal(100)).setScale(0, RoundingMode.HALF_UP).longValue();
        if (refundFen <= 0) {
            return R.fail("退款金额（分）无效");
        }
        long totalFen = po.getPayAmount() != null
                ? po.getPayAmount().multiply(new BigDecimal(100)).setScale(0, RoundingMode.HALF_UP).longValue()
                : 0L;
        if (totalFen > 0 && refundFen > totalFen) {
            return R.fail("退款金额不能超过原支付金额");
        }
        if (totalFen <= 0) {
            return R.fail("原支付金额异常，无法计算退款分");
        }
        try {
            WeChatPaymentStrategyImpl.CreateRequest request = new WeChatPaymentStrategyImpl.CreateRequest();
            request.outTradeNo = outTradeNo;
            request.outRefundNo = UniqueRandomNumGenerator.generateUniqueCode(19);
            request.reason = StringUtils.isNotBlank(refundReason) ? refundReason : "测试退款";
            if (StringUtils.isNotBlank(businessRefundNotifyUrl)) {
                request.notifyUrl = businessRefundNotifyUrl;
            }
            request.amount = new WeChatPaymentStrategyImpl.AmountReq();
            request.amount.refund = refundFen;
            request.amount.total = totalFen;
            request.amount.currency = "CNY";
            WeChatPaymentStrategyImpl.Refund response = refundRun(request);
            String status = response.status != null ? response.status.name() : "UNKNOWN";
            if ("SUCCESS".equals(status) || "PROCESSING".equals(status)) {
                membershipPaymentOrderService.markPaymentAndMembershipOrderRefunded(po);
            }
            log.warn("会员卡微信退款接口已返回 membershipOrderId={}, outTradeNo={}, outRefundNo={}, status={}, refundId={}",
                    po.getMembershipOrderId(), outTradeNo, response.outRefundNo, status, response.refundId);
            return R.data(("SUCCESS".equals(status) || "PROCESSING".equals(status) ? "退款受理并已回写支付单/业务订单状态（PROCESSING 表示处理中）" : "退款返回")
                    + "，membershipOrderId=" + po.getMembershipOrderId()
                    + ", status=" + status
                    + ", refund_id=" + response.refundId
                    + ", out_refund_no=" + (response.outRefundNo != null ? response.outRefundNo : request.outRefundNo));
        } catch (WXPayUtility.ApiException e) {
            log.error("[测试]会员卡微信退款失败 outTradeNo={}", outTradeNo, e);
            return R.fail("退款失败：" + e.getMessage());
        } catch (Exception e) {
            log.error("[测试]会员卡微信退款异常 membershipOrderId={}", po.getMembershipOrderId(), e);
            return R.fail("退款失败：" + e.getMessage());
        }
    }

    private WeChatPaymentStrategyImpl.Refund refundRun(WeChatPaymentStrategyImpl.CreateRequest request) throws IOException {
        String uri = refundPath;
        String reqBody = WXPayUtility.toJson(request);
        Request.Builder reqBuilder = new Request.Builder().url(wechatPayApiHost + uri);
        reqBuilder.addHeader("Accept", "application/json");
        reqBuilder.addHeader("Wechatpay-Serial", wechatPayPublicKeyId);
        reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(mchId, merchantSerialNumber, privateKey, POSTMETHOD, uri, reqBody));
        reqBuilder.addHeader("Content-Type", "application/json");
        MediaType jsonMediaType = MediaType.parse("application/json; charset=utf-8");
        RequestBody body = RequestBody.create(reqBody, jsonMediaType);
        reqBuilder.method(POSTMETHOD, body);
        OkHttpClient client = new OkHttpClient.Builder()
                .connectTimeout(10, TimeUnit.SECONDS)
                .readTimeout(30, TimeUnit.SECONDS)
                .build();
        try (Response httpResponse = client.newCall(reqBuilder.build()).execute()) {
            String respBody = WXPayUtility.extractBody(httpResponse);
            if (httpResponse.code() >= 200 && httpResponse.code() < 300) {
                WXPayUtility.validateResponse(wechatPayPublicKeyId, wechatPayPublicKey, httpResponse.headers(), respBody);
                return WXPayUtility.fromJson(respBody, WeChatPaymentStrategyImpl.Refund.class);
            }
            throw new WXPayUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers());
        }
    }

    private WeChatPaymentStrategyImpl.DirectAPIv3AppPrepayResponse prePayOrderRun(WeChatPaymentStrategyImpl.CommonPrepayRequest request) throws IOException {
        String uri = prePayPath;
        String reqBody = WXPayUtility.toJson(request);
        Request.Builder reqBuilder = new Request.Builder().url(wechatPayApiHost + uri);
        reqBuilder.addHeader("Accept", "application/json");
        reqBuilder.addHeader("Wechatpay-Serial", wechatPayPublicKeyId);
        reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(mchId, merchantSerialNumber, privateKey, POSTMETHOD, uri, reqBody));
        reqBuilder.addHeader("Content-Type", "application/json");
        MediaType jsonMediaType = MediaType.parse("application/json; charset=utf-8");
        RequestBody body = RequestBody.create(reqBody, jsonMediaType);
        reqBuilder.method(POSTMETHOD, body);
        OkHttpClient client = new OkHttpClient.Builder().build();
        try (Response httpResponse = client.newCall(reqBuilder.build()).execute()) {
            String respBody = WXPayUtility.extractBody(httpResponse);
            if (httpResponse.code() >= 200 && httpResponse.code() < 300) {
                WXPayUtility.validateResponse(wechatPayPublicKeyId, wechatPayPublicKey, httpResponse.headers(), respBody);
                return WXPayUtility.fromJson(respBody, WeChatPaymentStrategyImpl.DirectAPIv3AppPrepayResponse.class);
            }
            throw new WXPayUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers());
        }
    }

    private WeChatPaymentStrategyImpl.DirectAPIv3QueryResponse searchOrderRun(WeChatPaymentStrategyImpl.QueryByWxTradeNoRequest request) throws IOException {
        String uri = searchOrderByOutTradeNoPath.replace("{out_trade_no}", WXPayUtility.urlEncode(request.transactionId));
        Map<String, Object> args = new HashMap<>();
        args.put("mchid", mchId);
        String queryString = WXPayUtility.urlEncode(args);
        if (!queryString.isEmpty()) {
            uri = uri + "?" + queryString;
        }
        Request.Builder reqBuilder = new Request.Builder().url(wechatPayApiHost + uri);
        reqBuilder.addHeader("Accept", "application/json");
        reqBuilder.addHeader("Wechatpay-Serial", wechatPayPublicKeyId);
        reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(mchId, merchantSerialNumber, privateKey, GETMETHOD, uri, null));
        reqBuilder.method(GETMETHOD, null);
        OkHttpClient client = new OkHttpClient.Builder()
                .connectTimeout(10, TimeUnit.SECONDS)
                .readTimeout(15, TimeUnit.SECONDS)
                .build();
        try (Response httpResponse = client.newCall(reqBuilder.build()).execute()) {
            String respBody = WXPayUtility.extractBody(httpResponse);
            if (httpResponse.code() >= 200 && httpResponse.code() < 300) {
                WXPayUtility.validateResponse(wechatPayPublicKeyId, wechatPayPublicKey, httpResponse.headers(), respBody);
                return WXPayUtility.fromJson(respBody, WeChatPaymentStrategyImpl.DirectAPIv3QueryResponse.class);
            }
            throw new WXPayUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers());
        }
    }
}
