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

import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.jeesharp.business.modules.finance.merchantsettle.enums.SettleModeEnum;
import com.jeesharp.business.modules.finance.payorder.convert.PayOrderConvert;
import com.jeesharp.business.modules.finance.payorder.dto.PayOrderSaveDTO;
import com.jeesharp.business.modules.finance.payorder.dto.PayOrderPrepayRouteDTO;
import com.jeesharp.business.modules.finance.payorder.entity.PayOrder;
import com.jeesharp.business.modules.finance.payorder.enums.FinancePayNotifyStatusEnum;
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.vo.PayOrderInfoVO;
import com.jeesharp.business.modules.finance.payorder.vo.PayOrderIntentVO;
import com.jeesharp.business.modules.finance.payorder.vo.PayOrderListVO;
import com.jeesharp.business.modules.finance.payorder.vo.PayOrderQueryVO;
import com.jeesharp.business.modules.finance.paymentrecord.enums.PayRecordBizSceneEnum;
import com.jeesharp.business.modules.finance.paymentrecord.enums.PayRecordPayTypeEnum;
import com.jeesharp.business.modules.pay.api.PayWechatTransactionChannelApi;
import com.jeesharp.business.modules.pay.dto.PayWechatTransactionOpsRequestDTO;
import com.jeesharp.business.modules.pay.dto.PayWechatTransactionOpsResultDTO;
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.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;

/**
 * 支付单应用服务：编排 C 端发起支付意图、预支付回写、微信回调落成功态，以及管理端 CRUD。
 *
 * <p>支付单生命周期：</p>
 * <pre>
 * INIT → PREPAY_CREATED（统一下单回写 prepay_id）→ SUCCESS（微信回调 / markPaid）
 * 同业务单重新下单：关旧微信单 + 本地关闭 → 新建 INIT
 * </pre>
 *
 * @author jeesharp
 * @since 2026-05-08
 */
@Slf4j
@Service
@AllArgsConstructor
@Transactional(readOnly = true)
public class PayOrderApplicationService {

    private static final String DEFAULT_PAY_CHANNEL = "WECHAT";

    private static final int OUT_TRADE_NO_ALLOCATE_MAX_RETRY = 8;

    private final PayOrderService payOrderService;

    private final PayWechatTransactionChannelApi payWechatTransactionChannelApi;

    public PayOrderInfoVO get(String id) {
        PayOrderInfoVO vo = PayOrderConvert.INSTANCE.toInfoVO(payOrderService.get(id));
        fillInfoNames(vo);
        return vo;
    }

    public PageRespVO<PayOrderListVO> findPage(Page<PayOrder> page, PayOrderQueryVO query) {
        Page<PayOrder> result = payOrderService.findPage(page, query);
        PageRespVO<PayOrderListVO> pageVo = PayOrderConvert.INSTANCE.toPageRespVO(result);
        if (pageVo.getRecords() != null) {
            pageVo.getRecords().forEach(this::fillListNames);
        }
        return pageVo;
    }

    @Transactional(readOnly = false)
    public void save(PayOrderSaveDTO dto) {
        PayOrder entity = PayOrderConvert.INSTANCE.toEntity(dto);
        if (dto.getPayStatus() == null) {
            entity.setPayStatus(PayOrderStatusEnum.INIT.getValue());
        }
        if (StrUtil.isBlank(entity.getPayChannel())) {
            entity.setPayChannel(DEFAULT_PAY_CHANNEL);
        }
        if (StrUtil.isBlank(entity.getPayType())) {
            entity.setPayType(PayRecordPayTypeEnum.WECHAT_JSAPI.getCode());
        }
        if (StrUtil.isBlank(entity.getNotifyStatus())) {
            entity.setNotifyStatus(FinancePayNotifyStatusEnum.WAIT_NOTIFY.getValue());
        }
        if (entity.getRefundTimes() == null) {
            entity.setRefundTimes(0);
        }
        if (entity.getRefundAmount() == null) {
            entity.setRefundAmount(BigDecimal.ZERO);
        }
        payOrderService.save(entity);
    }

    @Transactional(readOnly = false)
    public void delete(String ids) {
        List<String> idList = Arrays.asList(ids.split(","));
        payOrderService.batchDelete(idList);
    }

    /**
     * 创建一条新的支付意图：关闭同业务单未终态旧单、生成 32 位 out_trade_no，供微信统一下单。
     */
    @Transactional(readOnly = false)
    public PayOrderIntentVO createIntentForJsapi(String bizScene, String bizOrderId, BigDecimal amountYuan,
            String memberId, String subject) {
        return createIntentForJsapi(bizScene, bizOrderId, amountYuan, memberId, subject, null, null, null, null, null);
    }

    /**
     * 创建 JSAPI 支付意图（含 notify、openid 等合并字段）。
     */
    @Transactional(readOnly = false)
    public PayOrderIntentVO createIntentForJsapi(String bizScene, String bizOrderId, BigDecimal amountYuan,
            String memberId, String subject, String description, String notifyUrl, String channelUserId,
            String userIp) {
        return createIntentForJsapi(bizScene, bizOrderId, amountYuan, memberId, subject, description,
                notifyUrl, channelUserId, userIp, null);
    }

    /**
     * 创建 JSAPI 支付意图（含租户 ID，用于多租户支付配置）。
     */
    @Transactional(readOnly = false)
    public PayOrderIntentVO createIntentForJsapi(String bizScene, String bizOrderId, BigDecimal amountYuan,
            String memberId, String subject, String description, String notifyUrl, String channelUserId,
            String userIp, String tenantId) {
        return createIntentForJsapi(bizScene, bizOrderId, amountYuan, memberId, subject, description,
                notifyUrl, channelUserId, userIp, tenantId, null, null);
    }

    @Transactional(readOnly = false)
    public PayOrderIntentVO createIntentForJsapi(String bizScene, String bizOrderId, BigDecimal amountYuan,
            String memberId, String subject, String description, String notifyUrl, String channelUserId,
            String userIp, String tenantId, String settleMode, Boolean needProfitSharing) {
        return createIntentForJsapi(bizScene, bizOrderId, amountYuan, memberId, subject, description,
                notifyUrl, channelUserId, userIp, tenantId, settleMode, needProfitSharing, null);
    }

    /**
     * 创建 JSAPI 支付意图（完整参数：含租户、结算模式、收付通路由）。
     * <p>流程：校验 → 复用同金额待支付单 → 否则关旧单并新建 → 返回 payOrderId + out_trade_no 供统一下单。</p>
     */
    @Transactional(readOnly = false)
    public PayOrderIntentVO createIntentForJsapi(String bizScene, String bizOrderId, BigDecimal amountYuan,
            String memberId, String subject, String description, String notifyUrl, String channelUserId,
            String userIp, String tenantId, String settleMode, Boolean needProfitSharing,
            com.jeesharp.business.modules.finance.payorder.dto.PayOrderPrepayRouteDTO route) {
        if (PayRecordBizSceneEnum.fromCode(bizScene) == null) {
            throw new BizErrorException("无效的业务场景编码");
        }
        if (StrUtil.isBlank(bizOrderId)) {
            throw new BizErrorException("业务订单ID不能为空");
        }
        if (amountYuan == null || amountYuan.compareTo(BigDecimal.ZERO) <= 0) {
            throw new BizErrorException("支付金额必须大于0");
        }

        // ① 同业务单 + 同金额已有待支付单：复用 out_trade_no，仅刷新 openid/回调地址等可变字段（避免重复关单下单）
        PayOrder pending = payOrderService.findLatestPendingForBiz(bizOrderId, bizScene);
        if (pending != null && pending.getPayAmount() != null
                && pending.getPayAmount().compareTo(amountYuan) == 0) {
            refreshPendingIntentFields(pending, memberId, subject, description, notifyUrl, channelUserId, userIp,
                    tenantId, settleMode, needProfitSharing, route);
            payOrderService.update(pending);
            return toIntentVo(pending);
        }

        // ② 金额变更或无可复用单：先调微信关单 + 本地关闭旧待支付单，再插入新支付意图
        closeWechatPendingOrders(bizOrderId, bizScene);
        payOrderService.closePendingByBiz(bizOrderId, bizScene);

        PayOrder entity = buildNewPayOrderEntity(bizScene, bizOrderId, amountYuan, memberId, subject, description,
                notifyUrl, channelUserId, userIp, tenantId, settleMode, needProfitSharing, route);
        payOrderService.insertPayOrder(entity);
        return toIntentVo(entity);
    }

    /**
     * 微信统一下单成功后回写 prepay_id，并将状态置为「已生成预支付」。
     * 已 SUCCESS 的单据幂等跳过，防止回调先到、bind 后到时的状态回退。
     */
    @Transactional(readOnly = false)
    public void bindPrepayAfterJsapi(String payOrderId, String prepayIdOrPackage) {
        if (StrUtil.isBlank(payOrderId)) {
            return;
        }
        String prepayId = normalizePrepayId(prepayIdOrPackage);
        if (StrUtil.isBlank(prepayId)) {
            log.warn("bindPrepayAfterJsapi skipped: empty prepay, payOrderId={}", payOrderId);
            return;
        }
        PayOrder entity = payOrderService.get(payOrderId);
        if (entity == null) {
            return;
        }
        if (PayOrderStatusEnum.SUCCESS.getValue().equals(entity.getPayStatus())) {
            return;
        }
        entity.setPrepayId(prepayId);
        entity.setPayStatus(PayOrderStatusEnum.PREPAY_CREATED.getValue());
        payOrderService.update(entity);
    }

    /**
     * 微信回调成功路径：按商户 out_trade_no 将支付单置为成功；幂等可重复调用。
     * <p>兼容直连单号与合单 combine_out_trade_no 两种查单方式。</p>
     */
    @Transactional(readOnly = false)
    public void markPaidByWechatOutTradeNo(String outTradeNo, String thirdPartyTradeNo, BigDecimal amountYuan,
            LocalDateTime payTime) {
        if (StrUtil.isBlank(outTradeNo)) {
            return;
        }
        PayOrder entity = resolvePayOrderForWechatNotify(outTradeNo, null);
        if (entity == null) {
            log.debug("markPaidByWechatOutTradeNo: no pay_order for outTradeNo={}", outTradeNo);
            return;
        }
        applyPaidState(entity, thirdPartyTradeNo, amountYuan, payTime);
    }

    /**
     * 微信回调成功路径：按 attach 中的 payOrderId 精确定位支付单（新链路首选）。
     */
    @Transactional(readOnly = false)
    public void markPaidByPayOrderId(String payOrderId, String thirdPartyTradeNo, BigDecimal amountYuan,
            LocalDateTime payTime) {
        if (StrUtil.isBlank(payOrderId)) {
            return;
        }
        PayOrder entity = payOrderService.get(payOrderId.trim());
        if (entity == null) {
            log.debug("markPaidByPayOrderId: no pay_order for id={}", payOrderId);
            return;
        }
        applyPaidState(entity, thirdPartyTradeNo, amountYuan, payTime);
    }

    /**
     * 微信回调查单：优先 payOrderId → out_trade_no → combine_out_trade_no（合单场景）。
     */
    private PayOrder resolvePayOrderForWechatNotify(String outTradeNo, String payOrderIdHint) {
        if (StrUtil.isNotBlank(payOrderIdHint)) {
            PayOrder byId = payOrderService.get(payOrderIdHint.trim());
            if (byId != null) {
                return byId;
            }
        }
        PayOrder entity = payOrderService.getByOutTradeNo(outTradeNo);
        if (entity != null) {
            return entity;
        }
        return payOrderService.getByCombineOutTradeNo(outTradeNo);
    }

    /** 将支付单推进到 SUCCESS，写入微信流水号/实付金额/回调时间；已是 SUCCESS 则幂等返回。 */
    private void applyPaidState(PayOrder entity, String thirdPartyTradeNo, BigDecimal amountYuan,
            LocalDateTime payTime) {
        if (PayOrderStatusEnum.SUCCESS.getValue().equals(entity.getPayStatus())) {
            return;
        }
        entity.setPayStatus(PayOrderStatusEnum.SUCCESS.getValue());
        if (StrUtil.isNotBlank(thirdPartyTradeNo)) {
            entity.setThirdPartyTradeNo(thirdPartyTradeNo);
        }
        if (amountYuan != null && amountYuan.compareTo(BigDecimal.ZERO) > 0) {
            entity.setPayAmount(amountYuan);
        }
        entity.setPayTime(payTime != null ? payTime : LocalDateTime.now());
        entity.setNotifyStatus(FinancePayNotifyStatusEnum.NOTIFY_OK.getValue());
        entity.setNotifyTime(LocalDateTime.now());
        payOrderService.update(entity);
    }

    /**
     * 创建新支付意图前，向微信侧关闭同业务单下已预下单但未支付的订单。
     * 仅处理已生成 prepay_id 或 PREPAY_CREATED 状态的旧单；关单失败仅打日志，不阻断新单创建。
     */
    private void closeWechatPendingOrders(String bizOrderId, String bizScene) {
        List<PayOrder> pendingList = payOrderService.findPendingListByBiz(bizOrderId, bizScene);
        if (pendingList == null || pendingList.isEmpty()) {
            return;
        }
        for (PayOrder pending : pendingList) {
            if (pending == null || StrUtil.isBlank(pending.getOutTradeNo())) {
                continue;
            }
            if (!PayOrderStatusEnum.PREPAY_CREATED.getValue().equals(pending.getPayStatus())
                    && StrUtil.isBlank(pending.getPrepayId())) {
                continue;
            }
            try {
                PayWechatTransactionOpsRequestDTO opsRequest = new PayWechatTransactionOpsRequestDTO();
                opsRequest.setPlatformTenantId(pending.getTenantId());
                opsRequest.setPayRouteType(pending.getPayRouteType());
                opsRequest.setOutTradeNo(pending.getOutTradeNo());
                opsRequest.setSubMchId(pending.getSubMchId());
                opsRequest.setCombineOutTradeNo(pending.getCombineOutTradeNo());
                PayWechatTransactionOpsResultDTO closeResult = payWechatTransactionChannelApi.close(opsRequest);
                if (!Boolean.TRUE.equals(closeResult.getSuccess())) {
                    log.warn("微信关单失败 outTradeNo={}, route={}, msg={}",
                            pending.getOutTradeNo(), pending.getPayRouteType(), closeResult.getErrorMsg());
                }
            } catch (Exception ex) {
                log.warn("微信关单异常 outTradeNo={}, route={}: {}",
                        pending.getOutTradeNo(), pending.getPayRouteType(), ex.getMessage());
            }
        }
    }

    /**
     * 组装新支付单实体：INIT 态 + 独立 out_trade_no + 结算/收付通路由字段。
     * 合单路由时 combine_out_trade_no 默认与 out_trade_no 相同（整单对外一个商户单号）。
     */
    private PayOrder buildNewPayOrderEntity(String bizScene, String bizOrderId, BigDecimal amountYuan, String memberId,
            String subject, String description, String notifyUrl, String channelUserId, String userIp,
            String tenantId, String settleMode, Boolean needProfitSharing,
            com.jeesharp.business.modules.finance.payorder.dto.PayOrderPrepayRouteDTO route) {
        PayOrder entity = new PayOrder();
        entity.setBizScene(bizScene);
        entity.setBizOrderId(bizOrderId);
        entity.setOutTradeNo(allocateUniqueOutTradeNo(bizOrderId));
        entity.setPayAmount(amountYuan);
        entity.setMemberId(StrUtil.blankToDefault(memberId, null));
        entity.setSubject(StrUtil.blankToDefault(subject, null));
        entity.setDescription(StrUtil.blankToDefault(description, subject));
        entity.setNotifyUrl(StrUtil.blankToDefault(notifyUrl, null));
        entity.setNotifyStatus(FinancePayNotifyStatusEnum.WAIT_NOTIFY.getValue());
        entity.setChannelUserId(StrUtil.blankToDefault(channelUserId, null));
        entity.setUserIp(StrUtil.blankToDefault(userIp, null));
        entity.setRefundStatus("0");
        entity.setRefundTimes(0);
        entity.setRefundAmount(BigDecimal.ZERO);
        entity.setPayChannel(DEFAULT_PAY_CHANNEL);
        entity.setPayType(PayRecordPayTypeEnum.WECHAT_JSAPI.getCode());
        entity.setPayStatus(PayOrderStatusEnum.INIT.getValue());
        entity.setDelFlag("0");
        applySettleFields(entity, settleMode, needProfitSharing);
        applyRouteFields(entity, route);
        if ("COMBINE".equals(entity.getPayRouteType()) && StrUtil.isBlank(entity.getCombineOutTradeNo())) {
            entity.setCombineOutTradeNo(entity.getOutTradeNo());
        }
        if (StrUtil.isNotBlank(tenantId)) {
            entity.setTenantId(tenantId.trim());
        }
        return entity;
    }

    /** 复用待支付单时刷新可变字段（openid、回调地址、结算模式、路由等），out_trade_no 不变。 */
    private void refreshPendingIntentFields(PayOrder pending, String memberId, String subject, String description,
            String notifyUrl, String channelUserId, String userIp, String tenantId, String settleMode,
            Boolean needProfitSharing, com.jeesharp.business.modules.finance.payorder.dto.PayOrderPrepayRouteDTO route) {
        pending.setMemberId(StrUtil.blankToDefault(memberId, pending.getMemberId()));
        pending.setSubject(StrUtil.blankToDefault(subject, pending.getSubject()));
        pending.setDescription(StrUtil.blankToDefault(description,
                StrUtil.blankToDefault(subject, pending.getDescription())));
        pending.setNotifyUrl(StrUtil.blankToDefault(notifyUrl, pending.getNotifyUrl()));
        pending.setChannelUserId(StrUtil.blankToDefault(channelUserId, pending.getChannelUserId()));
        pending.setUserIp(StrUtil.blankToDefault(userIp, pending.getUserIp()));
        applySettleFields(pending, settleMode, needProfitSharing);
        applyRouteFields(pending, route);
        if (StrUtil.isNotBlank(tenantId)) {
            pending.setTenantId(tenantId.trim());
        }
    }

    /** 写入结算模式（默认平台代收）及是否需分账标记。 */
    private static void applySettleFields(PayOrder entity, String settleMode, Boolean needProfitSharing) {
        if (StrUtil.isNotBlank(settleMode) && SettleModeEnum.fromValue(settleMode) != null) {
            entity.setSettleMode(settleMode.trim());
        } else {
            entity.setSettleMode(SettleModeEnum.PLATFORM_COLLECT.getValue());
        }
        entity.setNeedProfitSharing(Boolean.TRUE.equals(needProfitSharing) ? 1 : 0);
    }

    /**
     * 写入微信收付通路由：直连 DIRECT / 服务商 PARTNER / 合单 COMBINE 及子商户号等。
     * route 为空时默认直连模式。
     */
    private static void applyRouteFields(PayOrder entity,
            com.jeesharp.business.modules.finance.payorder.dto.PayOrderPrepayRouteDTO route) {
        if (route == null) {
            entity.setWechatPayMode("DIRECT");
            entity.setPayRouteType("DIRECT");
            return;
        }
        if (StrUtil.isNotBlank(route.getWechatPayMode())) {
            entity.setWechatPayMode(route.getWechatPayMode().trim());
        }
        if (StrUtil.isNotBlank(route.getPayRouteType())) {
            entity.setPayRouteType(route.getPayRouteType().trim());
        }
        entity.setSubMchId(StrUtil.blankToDefault(route.getSubMchId(), null));
        entity.setCombineOutTradeNo(StrUtil.blankToDefault(route.getCombineOutTradeNo(), null));
    }

    /**
     * 生成全局唯一的商户支付单号；禁止与业务订单主键相同，避免历史误用 biz_order_id 作 out_trade_no。
     */
    private String allocateUniqueOutTradeNo(String bizOrderId) {
        for (int i = 0; i < OUT_TRADE_NO_ALLOCATE_MAX_RETRY; i++) {
            String candidate = IdUtil.fastSimpleUUID();
            if (StrUtil.isNotBlank(bizOrderId) && bizOrderId.equals(candidate)) {
                continue;
            }
            if (payOrderService.getByOutTradeNo(candidate) == null) {
                return candidate;
            }
        }
        throw new BizErrorException("生成支付单号失败，请稍后重试");
    }

    private static PayOrderIntentVO toIntentVo(PayOrder entity) {
        PayOrderIntentVO vo = new PayOrderIntentVO();
        vo.setPayOrderId(entity.getId());
        vo.setOutTradeNo(entity.getOutTradeNo());
        vo.setTenantId(entity.getTenantId());
        return vo;
    }

    private static String normalizePrepayId(String prepayIdOrPackage) {
        if (StrUtil.isBlank(prepayIdOrPackage)) {
            return null;
        }
        String s = prepayIdOrPackage.trim();
        if (s.startsWith("prepay_id=")) {
            return s.substring("prepay_id=".length()).trim();
        }
        return s;
    }

    private void fillInfoNames(PayOrderInfoVO vo) {
        if (vo == null) {
            return;
        }
        vo.setPayStatusName(PayOrderStatusEnum.getDescByValue(vo.getPayStatus()));
        vo.setBizSceneName(PayRecordBizSceneEnum.getDescByCode(vo.getBizScene()));
        vo.setNotifyStatusName(FinancePayNotifyStatusEnum.getDescByValue(vo.getNotifyStatus()));
    }

    private void fillListNames(PayOrderListVO vo) {
        if (vo == null) {
            return;
        }
        vo.setPayStatusName(PayOrderStatusEnum.getDescByValue(vo.getPayStatus()));
        vo.setBizSceneName(PayRecordBizSceneEnum.getDescByCode(vo.getBizScene()));
        vo.setNotifyStatusName(FinancePayNotifyStatusEnum.getDescByValue(vo.getNotifyStatus()));
    }
}
