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

import cn.hutool.core.util.StrUtil;
import com.jeesharp.business.modules.finance.merchantsettle.enums.SettleModeEnum;
import com.jeesharp.business.modules.finance.payorder.entity.PayOrder;
import com.jeesharp.business.modules.finance.payorder.enums.PayOrderStatusEnum;
import com.jeesharp.business.modules.finance.payrefund.entity.PayRefundOrder;
import com.jeesharp.business.modules.finance.payrefund.enums.PayRefundOrderStatusEnum;
import com.jeesharp.business.modules.finance.paymentrecord.enums.PayRecordBizSceneEnum;
import com.jeesharp.business.modules.finance.profitsharing.entity.ProfitSharingOrder;
import com.jeesharp.business.modules.finance.profitsharing.enums.ProfitSharingStatusEnum;
import com.jeesharp.business.modules.finance.reconcile.enums.FinanceReconcileRecordTypeEnum;
import com.jeesharp.business.modules.finance.reconcile.service.FinanceReconcileService;
import com.jeesharp.business.modules.finance.reconcile.vo.FinanceReconcileExportVO;
import com.jeesharp.business.modules.finance.reconcile.vo.FinanceReconcileQueryVO;
import com.jeesharp.business.modules.finance.reconcile.vo.FinanceReconcileSummaryVO;
import com.jeesharp.business.modules.mall.api.MallFinanceReadApi;
import com.jeesharp.framework.exception.BizErrorException;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Comparator;
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
 */
@Service
@AllArgsConstructor
@Transactional(readOnly = true)
public class FinanceReconcileApplicationService {

    private static final int MAX_RANGE_DAYS = 93;

    private final FinanceReconcileService financeReconcileService;
    private final MallFinanceReadApi mallFinanceReadApi;

    public FinanceReconcileSummaryVO summarize(FinanceReconcileQueryVO query) {
        validateQuery(query);
        FinanceReconcileSummaryVO summary = new FinanceReconcileSummaryVO();
        summary.setPayCount(0L);
        summary.setPayAmountTotal(BigDecimal.ZERO);
        summary.setSharingCount(0L);
        summary.setSharingMerchantAmountTotal(BigDecimal.ZERO);
        summary.setRefundCount(0L);
        summary.setRefundAmountTotal(BigDecimal.ZERO);

        if (includeType(query, FinanceReconcileRecordTypeEnum.PAY)) {
            List<PayOrder> payOrders = financeReconcileService.listPayOrders(query);
            summary.setPayCount((long) payOrders.size());
            summary.setPayAmountTotal(sumPayAmount(payOrders));
        }
        if (includeType(query, FinanceReconcileRecordTypeEnum.SHARING)) {
            List<ProfitSharingOrder> sharingOrders = financeReconcileService.listSharingOrders(query);
            summary.setSharingCount((long) sharingOrders.size());
            summary.setSharingMerchantAmountTotal(sumSharingMerchantAmount(sharingOrders));
        }
        if (includeType(query, FinanceReconcileRecordTypeEnum.REFUND)) {
            List<PayRefundOrder> refundOrders = financeReconcileService.listRefundOrders(query);
            summary.setRefundCount((long) refundOrders.size());
            summary.setRefundAmountTotal(sumRefundAmount(refundOrders));
        }
        return summary;
    }

    public List<FinanceReconcileExportVO> findExportList(FinanceReconcileQueryVO query) {
        validateQuery(query);
        List<FinanceReconcileExportVO> rows = new ArrayList<>();
        Set<String> mallOrderIds = new HashSet<>();
        Set<String> payBatchIds = new HashSet<>();

        if (includeType(query, FinanceReconcileRecordTypeEnum.PAY)) {
            for (PayOrder order : financeReconcileService.listPayOrders(query)) {
                rows.add(toPayExportRow(order));
                if (StrUtil.isNotBlank(order.getBizOrderId())) {
                    payBatchIds.add(order.getBizOrderId().trim());
                }
            }
        }
        if (includeType(query, FinanceReconcileRecordTypeEnum.SHARING)) {
            for (ProfitSharingOrder order : financeReconcileService.listSharingOrders(query)) {
                rows.add(toSharingExportRow(order));
                collectMallRefs(order.getMallOrderId(), order.getPayBatchId(), mallOrderIds, payBatchIds);
            }
        }
        if (includeType(query, FinanceReconcileRecordTypeEnum.REFUND)) {
            for (PayRefundOrder order : financeReconcileService.listRefundOrders(query)) {
                rows.add(toRefundExportRow(order));
                collectMallRefs(order.getMallOrderId(), order.getPayBatchId(), mallOrderIds, payBatchIds);
            }
        }

        Map<String, String> orderNoMap = loadOrderNoMap(mallOrderIds);
        Map<String, String> batchNoMap = loadBatchNoMap(payBatchIds);
        for (FinanceReconcileExportVO row : rows) {
            if (StrUtil.isNotBlank(row.getMallOrderId())) {
                row.setOrderNo(orderNoMap.get(row.getMallOrderId()));
            }
            String batchKey = StrUtil.isNotBlank(row.getPayBatchId()) ? row.getPayBatchId() : row.getBizOrderId();
            if (StrUtil.isNotBlank(batchKey)) {
                row.setBatchNo(batchNoMap.get(batchKey));
            }
        }

        rows.sort(Comparator
                .comparing(FinanceReconcileExportVO::getBizTime, Comparator.nullsLast(Comparator.naturalOrder()))
                .thenComparing(FinanceReconcileExportVO::getRecordTypeName));
        return rows;
    }

    private void validateQuery(FinanceReconcileQueryVO query) {
        if (query == null || query.getBizTimeStart() == null || query.getBizTimeEnd() == null) {
            throw new BizErrorException("请选择业务开始与结束时间");
        }
        if (query.getBizTimeEnd().isBefore(query.getBizTimeStart())) {
            throw new BizErrorException("结束时间不能早于开始时间");
        }
        long days = ChronoUnit.DAYS.between(query.getBizTimeStart().toLocalDate(), query.getBizTimeEnd().toLocalDate());
        if (days > MAX_RANGE_DAYS) {
            throw new BizErrorException("对账时间范围不能超过 " + MAX_RANGE_DAYS + " 天");
        }
    }

    private boolean includeType(FinanceReconcileQueryVO query, FinanceReconcileRecordTypeEnum type) {
        List<String> types = query.getRecordTypes();
        if (types == null || types.isEmpty()) {
            return true;
        }
        return types.stream().anyMatch(item -> type.getValue().equalsIgnoreCase(StrUtil.trim(item)));
    }

    private FinanceReconcileExportVO toPayExportRow(PayOrder order) {
        FinanceReconcileExportVO row = new FinanceReconcileExportVO();
        row.setRecordTypeName(FinanceReconcileRecordTypeEnum.PAY.getDesc());
        row.setBizSceneName(PayRecordBizSceneEnum.getDescByCode(order.getBizScene()));
        row.setBizTime(order.getPayTime());
        row.setPlatformDocNo(order.getId());
        row.setBizOrderId(order.getBizOrderId());
        row.setPayBatchId(order.getBizOrderId());
        row.setOutTradeNo(order.getOutTradeNo());
        row.setTransactionId(order.getThirdPartyTradeNo());
        row.setWxDocNo(order.getThirdPartyTradeNo());
        row.setAmount(order.getPayAmount());
        row.setStatusName(PayOrderStatusEnum.getDescByValue(order.getPayStatus()));
        row.setSettleMode(resolveSettleModeName(order.getSettleMode()));
        return row;
    }

    private FinanceReconcileExportVO toSharingExportRow(ProfitSharingOrder order) {
        FinanceReconcileExportVO row = new FinanceReconcileExportVO();
        row.setRecordTypeName(FinanceReconcileRecordTypeEnum.SHARING.getDesc());
        row.setBizSceneName(PayRecordBizSceneEnum.getDescByCode(order.getBizScene()));
        row.setBizTime(order.getSharingTime() != null ? order.getSharingTime() : order.getCreateDate());
        row.setPlatformDocNo(order.getSharingNo());
        row.setBizOrderId(order.getFinancePayOrderId());
        row.setMallOrderId(order.getMallOrderId());
        row.setPayBatchId(order.getPayBatchId());
        row.setOutTradeNo(order.getOutTradeNo());
        row.setTransactionId(order.getTransactionId());
        row.setWxDocNo(order.getWxOrderId());
        row.setAmount(order.getMerchantAmount());
        row.setCommissionAmount(order.getCommissionAmount());
        row.setStatusName(ProfitSharingStatusEnum.getDescByValue(order.getStatus()));
        row.setMerchantTenantId(order.getMerchantTenantId());
        row.setSubMchId(order.getSubMchId());
        return row;
    }

    private FinanceReconcileExportVO toRefundExportRow(PayRefundOrder order) {
        FinanceReconcileExportVO row = new FinanceReconcileExportVO();
        row.setRecordTypeName(FinanceReconcileRecordTypeEnum.REFUND.getDesc());
        row.setBizSceneName(PayRecordBizSceneEnum.getDescByCode(order.getBizScene()));
        row.setBizTime(order.getRefundTime() != null ? order.getRefundTime() : order.getCreateDate());
        row.setPlatformDocNo(order.getRefundNo());
        row.setBizOrderId(order.getFinancePayOrderId());
        row.setMallOrderId(order.getMallOrderId());
        row.setPayBatchId(order.getPayBatchId());
        row.setOutTradeNo(order.getOutTradeNo());
        row.setTransactionId(order.getTransactionId());
        row.setWxDocNo(order.getWxRefundId());
        row.setAmount(order.getRefundAmount());
        row.setStatusName(PayRefundOrderStatusEnum.getDescByValue(order.getStatus()));
        row.setMerchantTenantId(order.getMerchantTenantId());
        row.setSubMchId(order.getSubMchId());
        return row;
    }

    private static void collectMallRefs(String mallOrderId, String payBatchId,
                                        Set<String> mallOrderIds, Set<String> payBatchIds) {
        if (StrUtil.isNotBlank(mallOrderId)) {
            mallOrderIds.add(mallOrderId.trim());
        }
        if (StrUtil.isNotBlank(payBatchId)) {
            payBatchIds.add(payBatchId.trim());
        }
    }

    private Map<String, String> loadOrderNoMap(Set<String> mallOrderIds) {
        if (mallOrderIds == null || mallOrderIds.isEmpty()) {
            return new HashMap<>();
        }
        return mallFinanceReadApi.mapOrderNoByIds(mallOrderIds);
    }

    private Map<String, String> loadBatchNoMap(Set<String> payBatchIds) {
        if (payBatchIds == null || payBatchIds.isEmpty()) {
            return new HashMap<>();
        }
        return mallFinanceReadApi.mapBatchNoByIds(payBatchIds);
    }

    private static BigDecimal sumPayAmount(List<PayOrder> orders) {
        BigDecimal total = BigDecimal.ZERO;
        for (PayOrder order : orders) {
            if (order.getPayAmount() != null) {
                total = total.add(order.getPayAmount());
            }
        }
        return total;
    }

    private static BigDecimal sumSharingMerchantAmount(List<ProfitSharingOrder> orders) {
        BigDecimal total = BigDecimal.ZERO;
        for (ProfitSharingOrder order : orders) {
            if (order.getMerchantAmount() != null) {
                total = total.add(order.getMerchantAmount());
            }
        }
        return total;
    }

    private static BigDecimal sumRefundAmount(List<PayRefundOrder> orders) {
        BigDecimal total = BigDecimal.ZERO;
        for (PayRefundOrder order : orders) {
            if (order.getRefundAmount() != null) {
                total = total.add(order.getRefundAmount());
            }
        }
        return total;
    }

    private static String resolveSettleModeName(String settleMode) {
        if (StrUtil.isBlank(settleMode)) {
            return "";
        }
        for (SettleModeEnum item : SettleModeEnum.values()) {
            if (item.getValue().equals(settleMode)) {
                return item.getDesc();
            }
        }
        return settleMode;
    }
}
