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

import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.jeesharp.business.modules.finance.api.MerchantSettleConfigApi;
import com.jeesharp.business.modules.finance.merchantsettle.vo.MerchantSettleConfigSnapshotVO;
import com.jeesharp.business.modules.finance.payorder.entity.PayOrder;
import com.jeesharp.business.modules.finance.payorder.service.PayOrderService;
import com.jeesharp.business.modules.finance.payorder.support.PayOrderWechatRouteSupport;
import com.jeesharp.business.modules.finance.paymentrecord.enums.PayRecordBizSceneEnum;
import com.jeesharp.business.modules.finance.profitsharing.convert.ProfitSharingOrderConvert;
import com.jeesharp.business.modules.finance.profitsharing.entity.ProfitSharingOrder;
import com.jeesharp.business.modules.finance.profitsharing.entity.ProfitSharingReceiver;
import com.jeesharp.business.modules.finance.profitsharing.enums.ProfitSharingLogActionEnum;
import com.jeesharp.business.modules.finance.profitsharing.enums.ProfitSharingReceiverResultEnum;
import com.jeesharp.business.modules.finance.profitsharing.enums.ProfitSharingStatusEnum;
import com.jeesharp.business.modules.finance.profitsharing.service.ProfitSharingLogService;
import com.jeesharp.business.modules.finance.profitsharing.service.ProfitSharingOrderService;
import com.jeesharp.business.modules.finance.profitsharing.service.ProfitSharingReceiverService;
import com.jeesharp.business.modules.finance.profitsharing.vo.ProfitSharingOrderInfoVO;
import com.jeesharp.business.modules.finance.profitsharing.vo.ProfitSharingOrderListVO;
import com.jeesharp.business.modules.finance.profitsharing.vo.ProfitSharingOrderQueryVO;
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.PayProfitSharingChannelApi;
import com.jeesharp.business.modules.pay.api.PaySubMerchantOnboardingApi;
import com.jeesharp.business.modules.pay.profitsharing.dto.PayProfitSharingFinishRequestDTO;
import com.jeesharp.business.modules.pay.profitsharing.dto.PayProfitSharingRequestDTO;
import com.jeesharp.business.modules.pay.profitsharing.dto.PayProfitSharingResultDTO;
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.Propagation;
import org.springframework.transaction.annotation.Transactional;

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

/**
 * 微信分账应用服务：支付成功落待分账记录，子单完成后请求渠道分账并管理重试。
 *
 * @author jeesharp
 * @since 2026-06-11
 */
@Slf4j
@Service
@AllArgsConstructor
@Transactional(readOnly = true)
public class ProfitSharingApplicationService {

    private static final int MAX_RETRY_COUNT = 10;
    private static final int RETRY_BATCH_LIMIT = 50;
    private static final String RECEIVER_TYPE_MERCHANT = "MERCHANT_ID";

    private final ProfitSharingOrderService profitSharingOrderService;
    private final ProfitSharingReceiverService profitSharingReceiverService;
    private final ProfitSharingLogService profitSharingLogService;
    private final MerchantSettleConfigApi merchantSettleConfigApi;
    private final PaySubMerchantOnboardingApi paySubMerchantOnboardingApi;
    private final PayProfitSharingChannelApi payProfitSharingChannelApi;
    private final PayOrderService payOrderService;
    private final PaySettleBizOrderResolver paySettleBizOrderResolver;

    public ProfitSharingOrderInfoVO get(String id) {
        ProfitSharingOrder entity = profitSharingOrderService.get(id);
        if (entity == null) {
            throw new BizErrorException("分账单不存在");
        }
        ProfitSharingOrderInfoVO vo = ProfitSharingOrderConvert.INSTANCE.toInfoVO(entity);
        enrichDisplayFields(vo, entity);
        vo.setReceivers(ProfitSharingOrderConvert.INSTANCE.toReceiverListVOList(
                profitSharingReceiverService.listByProfitSharingOrderId(entity.getId())));
        return vo;
    }

    public PageRespVO<ProfitSharingOrderListVO> findPage(Page<ProfitSharingOrder> page, ProfitSharingOrderQueryVO query) {
        resolveQueryAssociations(query);
        Page<ProfitSharingOrder> result = profitSharingOrderService.findPage(page, query);
        PageRespVO<ProfitSharingOrderListVO> pageVo = ProfitSharingOrderConvert.INSTANCE.toPageRespVO(result);
        enrichListDisplayFields(pageVo.getRecords(), result.getRecords());
        return pageVo;
    }

    @Transactional(readOnly = false)
    public void createPendingForPay(String financePayOrderId, String bizScene, String payBatchId, String bizOrderId,
            String transactionId) {
        if (StrUtil.isBlank(financePayOrderId)) {
            return;
        }
        PayOrder payOrder = payOrderService.get(financePayOrderId);
        if (payOrder == null || payOrder.getNeedProfitSharing() == null || payOrder.getNeedProfitSharing() != 1) {
            return;
        }
        if (!PayOrderWechatRouteSupport.hasPartnerPayRoute(payOrder)) {
            log.debug("跳过分账 pending：非 PARTNER 路由 payOrderId={}", financePayOrderId);
            return;
        }
        String scene = StrUtil.blankToDefault(bizScene, payOrder.getBizScene());
        String txnId = StrUtil.blankToDefault(transactionId, payOrder.getThirdPartyTradeNo());
        String batchKey = StrUtil.blankToDefault(payBatchId, payOrder.getBizOrderId());
        List<PaySettleBizOrderSnapshot> subOrders = paySettleBizOrderResolver.listSubOrdersForPay(scene, batchKey,
                StrUtil.blankToDefault(bizOrderId, payOrder.getBizOrderId()));
        for (PaySettleBizOrderSnapshot sub : subOrders) {
            if (sub == null || StrUtil.isBlank(sub.getBizOrderId())) {
                continue;
            }
            String subBatchId = StrUtil.blankToDefault(sub.getPayBatchId(), batchKey);
            createPendingForSubOrder(sub, payOrder, txnId, subBatchId, scene);
        }
    }

    @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
    public void scheduleSharingByBizOrder(String bizScene, String bizOrderId) {
        if (StrUtil.isBlank(bizOrderId)) {
            return;
        }
        ProfitSharingOrder existing = profitSharingOrderService.findByMallOrderId(bizOrderId.trim());
        if (existing == null) {
            log.debug("子单 {} 无待分账记录，跳过分账调度", bizOrderId);
            return;
        }
        if (ProfitSharingStatusEnum.SUCCESS.getValue().equals(existing.getStatus())) {
            return;
        }
        executeSharing(existing.getId(), ProfitSharingLogActionEnum.REQUEST.getValue());
    }

    @Transactional(readOnly = false)
    public void retry(String profitSharingOrderId) {
        if (StrUtil.isBlank(profitSharingOrderId)) {
            throw new BizErrorException("分账单ID不能为空");
        }
        ProfitSharingOrder order = profitSharingOrderService.get(profitSharingOrderId);
        if (order == null) {
            throw new BizErrorException("分账单不存在");
        }
        if (ProfitSharingStatusEnum.SUCCESS.getValue().equals(order.getStatus())) {
            return;
        }
        executeSharing(order.getId(), ProfitSharingLogActionEnum.RETRY.getValue());
    }

    @Transactional(readOnly = false)
    public int retryFailedShares() {
        List<ProfitSharingOrder> candidates = profitSharingOrderService.listRetryCandidates(
                LocalDateTime.now(), MAX_RETRY_COUNT, RETRY_BATCH_LIMIT);
        int count = 0;
        for (ProfitSharingOrder order : candidates) {
            try {
                executeSharing(order.getId(), ProfitSharingLogActionEnum.RETRY.getValue());
                count++;
            } catch (Exception ex) {
                log.warn("分账自动重试失败 id={}: {}", order.getId(), ex.getMessage());
            }
        }
        return count;
    }

    private void createPendingForSubOrder(PaySettleBizOrderSnapshot sub, PayOrder payOrder, String transactionId,
            String payBatchId, String bizScene) {
        if (profitSharingOrderService.findByMallOrderId(sub.getBizOrderId()) != null) {
            return;
        }
        MerchantSettleConfigSnapshotVO snapshot = merchantSettleConfigApi.resolveSnapshot(sub.getTenantId());
        if (!Boolean.TRUE.equals(snapshot.getNeedProfitSharing())) {
            return;
        }
        String subMchId = resolveSubMchId(snapshot, sub.getTenantId());
        if (StrUtil.isBlank(subMchId)) {
            log.warn("子单 {} 商户 {} 未配置 sub_mch_id，跳过待分账记录创建", sub.getBizOrderId(), sub.getTenantId());
            return;
        }
        BigDecimal payAmount = resolvePayAmount(sub);
        SharingAmount sharingAmount = calcSharingAmount(payAmount, snapshot.getCommissionRate());

        ProfitSharingOrder entity = new ProfitSharingOrder();
        entity.setSharingNo("PS" + IdUtil.getSnowflakeNextIdStr());
        entity.setFinancePayOrderId(payOrder.getId());
        entity.setPayBatchId(payBatchId);
        entity.setMallOrderId(sub.getBizOrderId());
        entity.setBizScene(StrUtil.blankToDefault(bizScene, payOrder.getBizScene()));
        entity.setMerchantTenantId(sub.getTenantId());
        entity.setSubMchId(subMchId);
        entity.setTransactionId(transactionId);
        entity.setOutTradeNo(payOrder.getOutTradeNo());
        entity.setPayAmount(payAmount);
        entity.setCommissionRate(snapshot.getCommissionRate() != null ? snapshot.getCommissionRate() : BigDecimal.ZERO);
        entity.setCommissionAmount(sharingAmount.commissionAmount());
        entity.setMerchantAmount(sharingAmount.merchantAmount());
        entity.setSharingAmountFen(sharingAmount.merchantAmountFen());
        entity.setStatus(ProfitSharingStatusEnum.PENDING.getValue());
        entity.setRetryCount(0);
        entity.setFinishStatus(0);
        entity.setTenantId(sub.getTenantId());
        profitSharingOrderService.save(entity);
    }

    private void executeSharing(String profitSharingOrderId, String logAction) {
        ProfitSharingOrder order = profitSharingOrderService.get(profitSharingOrderId);
        if (order == null) {
            throw new BizErrorException("分账单不存在");
        }
        if (ProfitSharingStatusEnum.SUCCESS.getValue().equals(order.getStatus())) {
            return;
        }
        if (order.getSharingAmountFen() == null || order.getSharingAmountFen() < 1) {
            markSuccessWithoutChannel(order, "分账金额为 0，无需请求微信");
            return;
        }
        if (StrUtil.isBlank(order.getTransactionId()) || StrUtil.isBlank(order.getSubMchId())) {
            markFailed(order, "MISSING_PARAM", "缺少 transaction_id 或 sub_mch_id");
            return;
        }

        order.setStatus(ProfitSharingStatusEnum.PROCESSING.getValue());
        profitSharingOrderService.update(order);

        PayProfitSharingRequestDTO request = new PayProfitSharingRequestDTO();
        request.setSubMchId(order.getSubMchId());
        request.setTransactionId(order.getTransactionId());
        request.setOutOrderNo(order.getSharingNo());
        request.setAmountFen(order.getSharingAmountFen());
        request.setDescription("商城子单分账-" + order.getMallOrderId());

        PayProfitSharingResultDTO channelResult = payProfitSharingChannelApi.requestOrders(request);
        profitSharingLogService.appendLog(order.getId(), logAction, null, channelResult.getRawResponse(),
                Boolean.TRUE.equals(channelResult.getSuccess()), channelResult.getErrorMsg(), order.getTenantId());

        if (Boolean.TRUE.equals(channelResult.getSuccess())) {
            applyChannelSuccess(order, channelResult);
            return;
        }
        markFailed(order, channelResult.getErrorCode(), channelResult.getErrorMsg());
    }

    private void applyChannelSuccess(ProfitSharingOrder order, PayProfitSharingResultDTO channelResult) {
        order.setWxOrderId(channelResult.getWxOrderId());
        if ("FINISHED".equalsIgnoreCase(channelResult.getState())) {
            order.setStatus(ProfitSharingStatusEnum.SUCCESS.getValue());
            order.setSharingTime(LocalDateTime.now());
        } else {
            order.setStatus(ProfitSharingStatusEnum.PROCESSING.getValue());
        }
        order.setErrorCode(null);
        order.setErrorMsg(null);
        profitSharingOrderService.update(order);
        saveReceiverRow(order, ProfitSharingReceiverResultEnum.SUCCESS.getValue());
        tryFinishSharingBatch(order);
    }

    private void markSuccessWithoutChannel(ProfitSharingOrder order, String remark) {
        order.setStatus(ProfitSharingStatusEnum.SUCCESS.getValue());
        order.setSharingTime(LocalDateTime.now());
        order.setRemarks(remark);
        profitSharingOrderService.update(order);
        tryFinishSharingBatch(order);
    }

    private void markFailed(ProfitSharingOrder order, String errorCode, String errorMsg) {
        int retry = order.getRetryCount() == null ? 0 : order.getRetryCount();
        order.setStatus(ProfitSharingStatusEnum.FAIL.getValue());
        order.setErrorCode(StrUtil.sub(errorCode, 0, 64));
        order.setErrorMsg(StrUtil.sub(errorMsg, 0, 500));
        order.setRetryCount(retry + 1);
        order.setNextRetryTime(calcNextRetryTime(retry + 1));
        profitSharingOrderService.update(order);
    }

    private void saveReceiverRow(ProfitSharingOrder order, String result) {
        ProfitSharingReceiver receiver = new ProfitSharingReceiver();
        receiver.setProfitSharingOrderId(order.getId());
        receiver.setReceiverType(RECEIVER_TYPE_MERCHANT);
        receiver.setReceiverAccount(order.getSubMchId());
        receiver.setAmountFen(order.getSharingAmountFen());
        receiver.setDescription("商户货款");
        receiver.setResult(result);
        receiver.setTenantId(order.getTenantId());
        profitSharingReceiverService.saveReceiver(receiver);
    }

    private String resolveSubMchId(MerchantSettleConfigSnapshotVO snapshot, String merchantTenantId) {
        if (StrUtil.isNotBlank(snapshot.getSubMchId())) {
            return snapshot.getSubMchId().trim();
        }
        return paySubMerchantOnboardingApi.resolveSubMchId(merchantTenantId);
    }

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

    private static SharingAmount calcSharingAmount(BigDecimal payAmount, BigDecimal commissionRate) {
        BigDecimal base = payAmount == null ? BigDecimal.ZERO : payAmount;
        BigDecimal rate = commissionRate == null ? BigDecimal.ZERO : commissionRate;
        BigDecimal commission = base.multiply(rate).setScale(2, RoundingMode.HALF_UP);
        BigDecimal merchant = base.subtract(commission);
        int merchantFen = merchant.multiply(BigDecimal.valueOf(100)).setScale(0, RoundingMode.HALF_UP).intValue();
        return new SharingAmount(commission, merchant, Math.max(merchantFen, 0));
    }

    private static LocalDateTime calcNextRetryTime(int retryCount) {
        long minutes = Math.min(30L * (1L << Math.min(retryCount, 6)), 24 * 60L);
        return LocalDateTime.now().plusMinutes(minutes);
    }

    private void tryFinishSharingBatch(ProfitSharingOrder trigger) {
        if (trigger == null) {
            return;
        }
        List<ProfitSharingOrder> batchOrders = StrUtil.isNotBlank(trigger.getPayBatchId())
                ? profitSharingOrderService.listByPayBatchId(trigger.getPayBatchId())
                : profitSharingOrderService.listByTransactionId(trigger.getTransactionId());
        if (batchOrders.isEmpty()) {
            return;
        }
        List<ProfitSharingOrder> activeOrders = batchOrders.stream()
                .filter(row -> !ProfitSharingStatusEnum.CANCELLED.getValue().equals(row.getStatus()))
                .toList();
        if (activeOrders.isEmpty()) {
            return;
        }
        for (ProfitSharingOrder row : activeOrders) {
            if (!ProfitSharingStatusEnum.SUCCESS.getValue().equals(row.getStatus())) {
                return;
            }
        }
        Set<String> subMchIds = new HashSet<>();
        for (ProfitSharingOrder row : activeOrders) {
            if (StrUtil.isNotBlank(row.getSubMchId())) {
                subMchIds.add(row.getSubMchId().trim());
            }
        }
        String transactionId = batchOrders.get(0).getTransactionId();
        String payBatchId = batchOrders.get(0).getPayBatchId();
        for (String subMchId : subMchIds) {
            if (profitSharingOrderService.isFinishDone(transactionId, subMchId)) {
                continue;
            }
            executeFinish(payBatchId, transactionId, subMchId, trigger.getTenantId());
        }
    }

    private void executeFinish(String payBatchId, String transactionId, String subMchId, String tenantId) {
        String finishOutNo = buildFinishOutOrderNo(transactionId, subMchId);
        PayProfitSharingFinishRequestDTO request = new PayProfitSharingFinishRequestDTO();
        request.setSubMchId(subMchId);
        request.setTransactionId(transactionId);
        request.setOutOrderNo(finishOutNo);
        request.setDescription("批次分账完结");
        PayProfitSharingResultDTO result = payProfitSharingChannelApi.finishOrder(request);
        String logOrderId = resolveLogOrderId(payBatchId, transactionId, subMchId);
        profitSharingLogService.appendLog(logOrderId, ProfitSharingLogActionEnum.FINISH.getValue(), null,
                result.getRawResponse(), Boolean.TRUE.equals(result.getSuccess()), result.getErrorMsg(), tenantId);
        if (Boolean.TRUE.equals(result.getSuccess())) {
            profitSharingOrderService.markFinishDone(payBatchId, transactionId, subMchId);
        } else {
            log.warn("完结分账失败 txn={} subMch={}: {}", transactionId, subMchId, result.getErrorMsg());
        }
    }

    private String resolveLogOrderId(String payBatchId, String transactionId, String subMchId) {
        List<ProfitSharingOrder> rows = StrUtil.isNotBlank(payBatchId)
                ? profitSharingOrderService.listByPayBatchId(payBatchId)
                : profitSharingOrderService.listByTransactionId(transactionId);
        for (ProfitSharingOrder row : rows) {
            if (subMchId.equals(row.getSubMchId())) {
                return row.getId();
            }
        }
        return rows.isEmpty() ? null : rows.get(0).getId();
    }

    private static String buildFinishOutOrderNo(String transactionId, String subMchId) {
        return "PSF" + subMchId + transactionId;
    }

    private void resolveQueryAssociations(ProfitSharingOrderQueryVO query) {
        if (query == null) {
            return;
        }
        String bizScene = PayRecordBizSceneEnum.MALL_ORDER.getCode();
        if (StrUtil.isNotBlank(query.getOrderNo()) && StrUtil.isBlank(query.getMallOrderId())) {
            String orderId = paySettleBizOrderResolver.findFirstOrderIdByOrderNo(bizScene, query.getOrderNo());
            if (StrUtil.isNotBlank(orderId)) {
                query.setMallOrderId(orderId);
            }
        }
        if (StrUtil.isNotBlank(query.getBatchNo()) && StrUtil.isBlank(query.getPayBatchId())) {
            String batchId = paySettleBizOrderResolver.findPayBatchIdByBatchNo(bizScene, query.getBatchNo());
            if (StrUtil.isNotBlank(batchId)) {
                query.setPayBatchId(batchId);
            }
        }
    }

    private void enrichListDisplayFields(List<ProfitSharingOrderListVO> voList, List<ProfitSharingOrder> entities) {
        if (voList == null || entities == null || voList.isEmpty()) {
            return;
        }
        Map<String, String> orderNoMap = loadOrderNoMap(entities);
        Map<String, String> batchNoMap = loadBatchNoMap(entities);
        for (int i = 0; i < voList.size() && i < entities.size(); i++) {
            ProfitSharingOrderListVO vo = voList.get(i);
            ProfitSharingOrder entity = entities.get(i);
            vo.setOrderNo(orderNoMap.get(entity.getMallOrderId()));
            vo.setBatchNo(batchNoMap.get(entity.getPayBatchId()));
        }
    }

    private void enrichDisplayFields(ProfitSharingOrderInfoVO vo, ProfitSharingOrder entity) {
        if (vo == null || entity == null) {
            return;
        }
        String bizScene = StrUtil.blankToDefault(entity.getBizScene(), PayRecordBizSceneEnum.MALL_ORDER.getCode());
        if (StrUtil.isNotBlank(entity.getMallOrderId())) {
            PaySettleBizOrderSnapshot order = paySettleBizOrderResolver.getOrder(bizScene, entity.getMallOrderId());
            if (order != null) {
                vo.setOrderNo(order.getOrderNo());
            }
        }
        if (PaySettleMergePayRules.supportsMergePayBatch(bizScene) && StrUtil.isNotBlank(entity.getPayBatchId())) {
            Map<String, String> batchMap = paySettleBizOrderResolver.mapBatchNoByIds(bizScene,
                    List.of(entity.getPayBatchId()));
            vo.setBatchNo(batchMap.get(entity.getPayBatchId()));
        }
    }

    private Map<String, String> loadOrderNoMap(List<ProfitSharingOrder> entities) {
        if (entities == null || entities.isEmpty()) {
            return new HashMap<>();
        }
        return paySettleBizOrderResolver.mapOrderNoByIds(PayRecordBizSceneEnum.MALL_ORDER.getCode(), entities.stream()
                .map(ProfitSharingOrder::getMallOrderId)
                .filter(StrUtil::isNotBlank)
                .toList());
    }

    private Map<String, String> loadBatchNoMap(List<ProfitSharingOrder> entities) {
        if (entities == null || entities.isEmpty()) {
            return new HashMap<>();
        }
        return paySettleBizOrderResolver.mapBatchNoByIds(PayRecordBizSceneEnum.MALL_ORDER.getCode(), entities.stream()
                .filter(e -> PaySettleMergePayRules.supportsMergePayBatch(
                        StrUtil.blankToDefault(e.getBizScene(), PayRecordBizSceneEnum.MALL_ORDER.getCode())))
                .map(ProfitSharingOrder::getPayBatchId)
                .filter(StrUtil::isNotBlank)
                .toList());
    }

    private record SharingAmount(BigDecimal commissionAmount, BigDecimal merchantAmount, int merchantAmountFen) {
    }
}
