package com.jeesharp.business.modules.finance.payrefund.application;

import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.jeesharp.business.modules.finance.payorder.entity.PayOrder;
import com.jeesharp.business.modules.finance.payorder.enums.PayOrderStatusEnum;
import com.jeesharp.business.modules.finance.payorder.service.PayOrderService;
import com.jeesharp.business.modules.finance.payorder.support.PayOrderWechatRouteSupport;
import com.jeesharp.business.modules.finance.payrefund.convert.PayRefundOrderConvert;
import com.jeesharp.business.modules.finance.payrefund.dto.PayRefundCreateDTO;
import com.jeesharp.business.modules.finance.payrefund.entity.PayRefundOrder;
import com.jeesharp.business.modules.finance.payrefund.enums.PayRefundOrderStatusEnum;
import com.jeesharp.business.modules.finance.payrefund.service.PayRefundOrderService;
import com.jeesharp.business.modules.finance.payrefund.vo.PayRefundOrderInfoVO;
import com.jeesharp.business.modules.finance.payrefund.vo.PayRefundOrderListVO;
import com.jeesharp.business.modules.finance.payrefund.vo.PayRefundOrderQueryVO;
import com.jeesharp.business.modules.finance.paymentrecord.enums.PayRecordBizSceneEnum;
import com.jeesharp.business.modules.finance.api.MerchantReceivableApi;
import com.jeesharp.business.modules.finance.api.PayRefundSuccessHandler;
import com.jeesharp.business.modules.finance.api.dto.PaySettleBizOrderSnapshot;
import com.jeesharp.business.modules.finance.settle.PaySettleMergePayRules;
import com.jeesharp.business.modules.finance.settle.support.PaySettleBizOrderResolver;
import com.jeesharp.business.modules.pay.api.PayCenterApi;
import com.jeesharp.business.modules.pay.api.PayRefundChannelApi;
import com.jeesharp.business.modules.pay.api.PaySubMerchantOnboardingApi;
import com.jeesharp.business.modules.pay.refund.dto.PayRefundRequestDTO;
import com.jeesharp.business.modules.pay.refund.dto.PayRefundResultDTO;
import com.jeesharp.business.modules.pay.vo.PayWechatPublicConfigVO;
import com.jeesharp.framework.exception.BizErrorException;
import com.jeesharp.framework.pojo.Page;
import com.jeesharp.framework.pojo.query.PageRespVO;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;

/**
 * 支付退款应用服务：创建退款单、调用微信渠道、处理回调并回写支付单与售后。
 *
 * @author jeesharp
 * @since 2026-06-11
 */
@Slf4j
@Service
@AllArgsConstructor
@Transactional(readOnly = true)
public class PayRefundOrderApplicationService {

    /** 不含 {@code /mobile} 前缀（由 {@code notifyBaseUrl} 提供，与支付成功回调 {@code WechatTravelPayNotifySupport} 一致）。 */
    private static final String MALL_REFUND_NOTIFY_PATH = "/order/pay/notify/wechat/refund";
    private static final String WECHAT_EVENT_REFUND_SUCCESS = "REFUND.SUCCESS";
    private static final String WECHAT_EVENT_REFUND_ABNORMAL = "REFUND.ABNORMAL";
    private static final String WECHAT_EVENT_REFUND_CLOSED = "REFUND.CLOSED";

    private final PayRefundOrderService payRefundOrderService;
    private final PayOrderService payOrderService;
    private final PayRefundChannelApi payRefundChannelApi;
    private final PaySubMerchantOnboardingApi paySubMerchantOnboardingApi;
    private final PayCenterApi payCenterApi;
    private final PaySettleBizOrderResolver paySettleBizOrderResolver;
    private final List<PayRefundSuccessHandler> payRefundSuccessHandlers;
    private final MerchantReceivableApi merchantReceivableApi;
    private final RefundSettleOrchestrator refundSettleOrchestrator;

    public PayRefundOrderInfoVO get(String id) {
        PayRefundOrder entity = payRefundOrderService.get(id);
        if (entity == null) {
            throw new BizErrorException("退款单不存在");
        }
        return PayRefundOrderConvert.INSTANCE.toInfoVO(entity);
    }

    public PageRespVO<PayRefundOrderListVO> findPage(Page<PayRefundOrder> page, PayRefundOrderQueryVO query) {
        Page<PayRefundOrder> result = payRefundOrderService.findPage(page, query);
        return PayRefundOrderConvert.INSTANCE.toPageRespVO(result);
    }

    /**
     * 创建并发起微信退款（跨业态；商城传 {@code mallOrderId} 或 {@code bizOrderId}）。
     *
     * @return 退款单主键
     */
    @Transactional(readOnly = false)
    public String createAndExecute(PayRefundCreateDTO dto) {
        if (dto == null) {
            throw new BizErrorException("退款参数不能为空");
        }
        String bizScene = resolveBizScene(dto);
        String bizOrderId = resolveBizOrderId(dto);
        if (dto.getRefundAmount() == null || dto.getRefundAmount().compareTo(BigDecimal.ZERO) <= 0) {
            throw new BizErrorException("退款金额必须大于0");
        }
        if (StrUtil.isNotBlank(dto.getAfterSaleId())) {
            PayRefundOrder active = payRefundOrderService.findActiveByAfterSaleId(dto.getAfterSaleId());
            if (active != null) {
                if (PayRefundOrderStatusEnum.SUCCESS.getValue().equals(active.getStatus())) {
                    return active.getId();
                }
                if (PayRefundOrderStatusEnum.PROCESSING.getValue().equals(active.getStatus())) {
                    return active.getId();
                }
            }
        }

        PaySettleBizOrderSnapshot bizOrder = paySettleBizOrderResolver.getOrder(bizScene, bizOrderId);
        if (bizOrder == null) {
            throw new BizErrorException("业务订单不存在");
        }
        String requestedPayBatchId = StrUtil.blankToDefault(dto.getPayBatchId(), bizOrder.getPayBatchId());
        if (!PaySettleMergePayRules.supportsMergePayBatch(bizScene)
                && StrUtil.isNotBlank(requestedPayBatchId)
                && !requestedPayBatchId.equals(bizOrderId)) {
            log.debug("非商城场景忽略 payBatchId，bizScene={} bizOrderId={}", bizScene, bizOrderId);
        }
        String payBatchId = PaySettleMergePayRules.resolvePayBatchKey(bizScene, bizOrderId, requestedPayBatchId);
        PayOrder payOrder = payOrderService.findLatestSuccessByBiz(payBatchId, bizScene);
        if (payOrder == null || !PayOrderStatusEnum.SUCCESS.getValue().equals(payOrder.getPayStatus())) {
            throw new BizErrorException("未找到已支付成功的财务支付单，无法退款");
        }
        if (StrUtil.isBlank(payOrder.getThirdPartyTradeNo())) {
            throw new BizErrorException("支付单缺少微信 transaction_id，无法退款");
        }

        BigDecimal orderPayAmount = payOrder.getPayAmount() == null ? BigDecimal.ZERO : payOrder.getPayAmount();
        BigDecimal subOrderPayAmount = resolvePayAmount(bizOrder, orderPayAmount);
        BigDecimal refunded = payRefundOrderService.sumSuccessRefundAmountByMallOrderId(bizOrder.getBizOrderId());
        BigDecimal remain = subOrderPayAmount.subtract(refunded);
        if (dto.getRefundAmount().compareTo(remain) > 0) {
            throw new BizErrorException("退款金额超过子单可退余额");
        }

        String merchantTenantId = StrUtil.blankToDefault(bizOrder.getTenantId(), payOrder.getTenantId());
        String subMchId = resolveRefundSubMchId(payOrder, merchantTenantId);

        String platformTenantId = StrUtil.blankToDefault(payOrder.getTenantId(), merchantTenantId);
        String notifyUrl = buildRefundNotifyUrl(platformTenantId);
        String defaultReason = PayRecordBizSceneEnum.MALL_ORDER.getCode().equals(bizScene)
                ? "商城售后退款" : "订单退款";

        PayRefundOrder entity = new PayRefundOrder();
        entity.setRefundNo("RF" + IdUtil.getSnowflakeNextIdStr());
        entity.setOutRefundNo(IdUtil.fastSimpleUUID());
        entity.setFinancePayOrderId(payOrder.getId());
        entity.setPayBatchId(payBatchId);
        entity.setMallOrderId(bizOrder.getBizOrderId());
        entity.setAfterSaleId(dto.getAfterSaleId());
        entity.setBizScene(bizScene);
        entity.setMerchantTenantId(merchantTenantId);
        entity.setSubMchId(subMchId);
        entity.setTransactionId(payOrder.getThirdPartyTradeNo());
        entity.setOutTradeNo(payOrder.getOutTradeNo());
        entity.setRefundAmount(dto.getRefundAmount());
        entity.setOrderPayAmount(orderPayAmount);
        entity.setStatus(PayRefundOrderStatusEnum.PENDING.getValue());
        entity.setReason(StrUtil.blankToDefault(dto.getReason(), defaultReason));
        entity.setNotifyUrl(notifyUrl);
        entity.setRetryCount(0);
        if (StrUtil.isNotBlank(platformTenantId)) {
            entity.setTenantId(platformTenantId);
        }
        payRefundOrderService.createRefundOrder(entity);

        refundSettleOrchestrator.prepareBeforeWechatRefund(entity, payOrder);

        executeChannelRefund(entity, platformTenantId);
        return entity.getId();
    }

    @Transactional(readOnly = false)
    public void retry(String refundOrderId) {
        PayRefundOrder entity = payRefundOrderService.get(refundOrderId);
        if (entity == null) {
            throw new BizErrorException("退款单不存在");
        }
        if (PayRefundOrderStatusEnum.SUCCESS.getValue().equals(entity.getStatus())) {
            return;
        }
        String platformTenantId = entity.getTenantId();
        PayOrder linkedPayOrder = payOrderService.get(entity.getFinancePayOrderId());
        String wechatPayMode = PayOrderWechatRouteSupport.resolveWechatPayMode(linkedPayOrder);
        if (PayRefundOrderStatusEnum.PROCESSING.getValue().equals(entity.getStatus())) {
            PayRefundResultDTO query = payRefundChannelApi.queryByOutRefundNo(
                    platformTenantId, wechatPayMode, entity.getSubMchId(), entity.getOutRefundNo());
            applyQueryResult(entity, query);
            payRefundOrderService.updateRefundOrder(entity);
            if (PayRefundOrderStatusEnum.SUCCESS.getValue().equals(entity.getStatus())) {
                onRefundSuccess(entity);
            }
            return;
        }
        PayOrder payOrder = payOrderService.get(entity.getFinancePayOrderId());
        if (payOrder != null) {
            refundSettleOrchestrator.prepareBeforeWechatRefund(entity, payOrder);
        }
        entity.setRetryCount(entity.getRetryCount() == null ? 1 : entity.getRetryCount() + 1);
        executeChannelRefund(entity, platformTenantId);
    }

    /**
     * 微信退款结果异步通知入口。
     */
    @Transactional(readOnly = false)
    public void handleWechatNotify(String rawBody, Map<String, String> headers) throws Exception {
        Map<String, Object> body = payCenterApi.wechatParsePayNotify(rawBody, headers);
        String eventType = stringValue(body.get("event_type"));
        if (!WECHAT_EVENT_REFUND_SUCCESS.equals(eventType)
                && !WECHAT_EVENT_REFUND_ABNORMAL.equals(eventType)
                && !WECHAT_EVENT_REFUND_CLOSED.equals(eventType)) {
            log.info("Wechat refund notify skip event_type={}", eventType);
            return;
        }
        @SuppressWarnings("unchecked")
        Map<String, Object> decrypted = (Map<String, Object>) body.get("resource_decrypted");
        if (decrypted == null) {
            throw new IllegalArgumentException("退款回调缺少解密数据");
        }
        String outRefundNo = firstNonBlank(decrypted, "out_refund_no", "outRefundNo");
        if (StrUtil.isBlank(outRefundNo)) {
            throw new IllegalArgumentException("退款回调缺少 out_refund_no");
        }
        PayRefundOrder entity = payRefundOrderService.getByOutRefundNo(outRefundNo);
        if (entity == null) {
            log.warn("Wechat refund notify: unknown out_refund_no={}", outRefundNo);
            return;
        }
        if (PayRefundOrderStatusEnum.SUCCESS.getValue().equals(entity.getStatus())) {
            return;
        }
        String wxStatus = firstNonBlank(decrypted, "refund_status", "refundStatus");
        if (StrUtil.isBlank(wxStatus)) {
            wxStatus = stringValue(decrypted.get("status"));
        }
        entity.setWxRefundId(firstNonBlank(decrypted, "refund_id", "refundId"));
        entity.setRawResponse(body.toString());
        if ("SUCCESS".equalsIgnoreCase(wxStatus) || WECHAT_EVENT_REFUND_SUCCESS.equals(eventType)) {
            entity.setStatus(PayRefundOrderStatusEnum.SUCCESS.getValue());
            entity.setRefundTime(LocalDateTime.now());
            entity.setErrorCode(null);
            entity.setErrorMsg(null);
            payRefundOrderService.updateRefundOrder(entity);
            onRefundSuccess(entity);
            return;
        }
        if (WECHAT_EVENT_REFUND_CLOSED.equals(eventType) || "CLOSED".equalsIgnoreCase(wxStatus)) {
            entity.setStatus(PayRefundOrderStatusEnum.CLOSED.getValue());
        } else {
            entity.setStatus(PayRefundOrderStatusEnum.FAIL.getValue());
        }
        entity.setErrorMsg("微信退款状态: " + wxStatus);
        payRefundOrderService.updateRefundOrder(entity);
    }

    private void executeChannelRefund(PayRefundOrder entity, String platformTenantId) {
        PayOrder payOrder = payOrderService.get(entity.getFinancePayOrderId());
        PayRefundRequestDTO request = new PayRefundRequestDTO();
        request.setPlatformTenantId(platformTenantId);
        request.setWechatPayMode(PayOrderWechatRouteSupport.resolveWechatPayMode(payOrder));
        request.setSubMchId(entity.getSubMchId());
        request.setTransactionId(entity.getTransactionId());
        request.setOutTradeNo(entity.getOutTradeNo());
        request.setOutRefundNo(entity.getOutRefundNo());
        request.setReason(entity.getReason());
        request.setRefundAmountFen(toFen(entity.getRefundAmount()));
        request.setOrderAmountFen(toFen(entity.getOrderPayAmount()));
        request.setNotifyUrl(entity.getNotifyUrl());

        PayRefundResultDTO result = payRefundChannelApi.applyRefund(request);
        entity.setRawRequest(result.getRawResponse());
        entity.setRawResponse(result.getRawResponse());
        if (result.isSuccess()) {
            entity.setStatus(PayRefundOrderStatusEnum.PROCESSING.getValue());
            entity.setWxRefundId(result.getWxRefundId());
            entity.setErrorCode(null);
            entity.setErrorMsg(null);
            if ("SUCCESS".equalsIgnoreCase(result.getStatus())) {
                entity.setStatus(PayRefundOrderStatusEnum.SUCCESS.getValue());
                entity.setRefundTime(LocalDateTime.now());
            }
        } else {
            entity.setStatus(PayRefundOrderStatusEnum.FAIL.getValue());
            entity.setErrorCode(result.getErrorCode());
            entity.setErrorMsg(StrUtil.blankToDefault(result.getErrorMsg(), "微信退款受理失败"));
        }
        payRefundOrderService.updateRefundOrder(entity);
        if (PayRefundOrderStatusEnum.SUCCESS.getValue().equals(entity.getStatus())) {
            onRefundSuccess(entity);
        }
    }

    private void applyQueryResult(PayRefundOrder entity, PayRefundResultDTO result) {
        if (result == null) {
            return;
        }
        entity.setRawResponse(result.getRawResponse());
        if (!result.isSuccess()) {
            entity.setErrorMsg(result.getErrorMsg());
            return;
        }
        entity.setWxRefundId(result.getWxRefundId());
        if ("SUCCESS".equalsIgnoreCase(result.getStatus())) {
            entity.setStatus(PayRefundOrderStatusEnum.SUCCESS.getValue());
            entity.setRefundTime(LocalDateTime.now());
        }
    }

    private void onRefundSuccess(PayRefundOrder entity) {
        accumulatePayOrderRefund(entity);
        try {
            merchantReceivableApi.recordForRefund(entity.getId());
        } catch (Exception ex) {
            log.warn("退款成功记商户应收冲减失败 refundId={}: {}", entity.getId(), ex.getMessage());
        }
        notifyBizRefundSuccess(entity);
    }

    private void notifyBizRefundSuccess(PayRefundOrder entity) {
        if (entity == null || payRefundSuccessHandlers == null) {
            return;
        }
        String bizScene = entity.getBizScene();
        String bizRefId = StrUtil.blankToDefault(entity.getAfterSaleId(), entity.getMallOrderId());
        for (PayRefundSuccessHandler handler : payRefundSuccessHandlers) {
            if (handler != null && handler.supports(bizScene)) {
                try {
                    handler.onRefundSuccess(entity.getId(), bizRefId);
                } catch (Exception ex) {
                    log.warn("退款成功业务回调失败 refundId={} scene={}: {}", entity.getId(), bizScene,
                            ex.getMessage());
                }
                return;
            }
        }
    }

    private static String resolveBizScene(PayRefundCreateDTO dto) {
        if (StrUtil.isNotBlank(dto.getBizScene())) {
            return dto.getBizScene().trim();
        }
        if (StrUtil.isNotBlank(dto.getMallOrderId())) {
            return PayRecordBizSceneEnum.MALL_ORDER.getCode();
        }
        throw new BizErrorException("业务场景不能为空");
    }

    private static String resolveBizOrderId(PayRefundCreateDTO dto) {
        if (StrUtil.isNotBlank(dto.getBizOrderId())) {
            return dto.getBizOrderId().trim();
        }
        if (StrUtil.isNotBlank(dto.getMallOrderId())) {
            return dto.getMallOrderId().trim();
        }
        throw new BizErrorException("业务订单ID不能为空");
    }

    private static BigDecimal resolvePayAmount(PaySettleBizOrderSnapshot order, BigDecimal fallback) {
        if (order.getPayAmount() != null && order.getPayAmount().compareTo(BigDecimal.ZERO) > 0) {
            return order.getPayAmount();
        }
        if (order.getTotalAmount() != null && order.getTotalAmount().compareTo(BigDecimal.ZERO) > 0) {
            return order.getTotalAmount();
        }
        return fallback == null ? BigDecimal.ZERO : fallback;
    }

    private void accumulatePayOrderRefund(PayRefundOrder entity) {
        PayOrder payOrder = payOrderService.get(entity.getFinancePayOrderId());
        if (payOrder == null) {
            return;
        }
        BigDecimal prev = payOrder.getRefundAmount() == null ? BigDecimal.ZERO : payOrder.getRefundAmount();
        payOrder.setRefundAmount(prev.add(entity.getRefundAmount()));
        payOrder.setRefundTimes(payOrder.getRefundTimes() == null ? 1 : payOrder.getRefundTimes() + 1);
        BigDecimal payAmount = payOrder.getPayAmount() == null ? BigDecimal.ZERO : payOrder.getPayAmount();
        if (payOrder.getRefundAmount().compareTo(payAmount) >= 0) {
            payOrder.setRefundStatus("2");
        } else if (payOrder.getRefundAmount().compareTo(BigDecimal.ZERO) > 0) {
            payOrder.setRefundStatus("1");
        }
        payOrderService.update(payOrder);
    }

    private String buildRefundNotifyUrl(String platformTenantId) {
        PayWechatPublicConfigVO config = payCenterApi.getWechatPublicConfig(platformTenantId);
        String notifyBase = config == null ? null : config.getNotifyBaseUrl();
        if (StrUtil.isBlank(notifyBase)) {
            throw new BizErrorException("未配置微信支付回调基地址 notifyBaseUrl");
        }
        return notifyBase.replaceAll("/+$", "") + MALL_REFUND_NOTIFY_PATH;
    }

    /**
     * PARTNER 退款须 sub_mchid（优先支付单快照，再查进件）；DIRECT 平台代收无需进件。
     */
    private String resolveRefundSubMchId(PayOrder payOrder, String merchantTenantId) {
        if (!PayOrderWechatRouteSupport.isPartnerMode(payOrder)) {
            return null;
        }
        String subMchId = StrUtil.trimToNull(payOrder.getSubMchId());
        if (StrUtil.isBlank(subMchId)) {
            subMchId = paySubMerchantOnboardingApi.resolveSubMchId(merchantTenantId);
        }
        if (StrUtil.isBlank(subMchId)) {
            throw new BizErrorException("商户未完成微信进件（sub_mch_id 为空），无法发起退款");
        }
        return subMchId;
    }

    private static long toFen(BigDecimal yuan) {
        if (yuan == null) {
            return 0L;
        }
        return yuan.movePointRight(2).setScale(0, RoundingMode.HALF_UP).longValue();
    }

    private static String stringValue(Object value) {
        return value == null ? "" : String.valueOf(value).trim();
    }

    private static String firstNonBlank(Map<String, Object> map, String... keys) {
        if (map == null || keys == null) {
            return "";
        }
        for (String key : keys) {
            String value = stringValue(map.get(key));
            if (StrUtil.isNotBlank(value)) {
                return value;
            }
        }
        return "";
    }
}
