package com.ruoyi.system.service.impl;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.ruoyi.system.domain.PosFood;
import com.ruoyi.system.domain.PosOrder;
import com.ruoyi.system.domain.PromotionActivity;
import com.ruoyi.system.domain.PromotionActivityRule;
import com.ruoyi.system.domain.PromotionCouponBatch;
import com.ruoyi.system.domain.PromotionCouponRule;
import com.ruoyi.system.domain.PromotionUserCoupon;
import com.ruoyi.system.dto.PromotionCalcRequest;
import com.ruoyi.system.dto.PromotionCalcResponse;
import com.ruoyi.system.dto.PromotionCalcResponse.AvailableCoupon;
import com.ruoyi.system.dto.PromotionCalcResponse.LineItem;
import com.ruoyi.system.dto.PromotionCalcResponse.MatchedRule;
import com.ruoyi.system.dto.PromotionCalcResponse.PathResult;
import com.ruoyi.system.dto.PromotionCalcResponse.PromotionDetail;
import com.ruoyi.system.mapper.PosFoodMapper;
import com.ruoyi.system.mapper.PosOrderMapper;
import com.ruoyi.system.mapper.PromotionActivityMapper;
import com.ruoyi.system.mapper.PromotionActivityRuleMapper;
import com.ruoyi.system.mapper.PromotionCouponBatchMapper;
import com.ruoyi.system.mapper.PromotionCouponRuleMapper;
import com.ruoyi.system.mapper.PromotionUserCouponMapper;
import com.ruoyi.system.service.IPromotionCalcService;

/**
 * 促销算价Service实现
 *
 * 两路算价：Path A（折扣/第二份半价）vs Path B（满减），取最优路径。
 * 叠加新客立减、优惠券后得出最终金额。
 */
@Service
public class PromotionCalcServiceImpl implements IPromotionCalcService
{
    private static final BigDecimal MIN_AMOUNT = new BigDecimal("1");

    @Autowired
    private PromotionActivityMapper activityMapper;

    @Autowired
    private PromotionActivityRuleMapper activityRuleMapper;

    @Autowired
    private PromotionCouponBatchMapper couponBatchMapper;

    @Autowired
    private PromotionCouponRuleMapper couponRuleMapper;

    @Autowired
    private PromotionUserCouponMapper userCouponMapper;

    @Autowired
    private PosFoodMapper posFoodMapper;

    @Autowired
    private PosOrderMapper posOrderMapper;

    @Autowired
    private PromotionDiscountLimitChecker discountLimitChecker;

    @Override
    public PromotionCalcResponse calculate(PromotionCalcRequest request, Long userId)
    {
        Long storeId = request.getStoreId();
        Long couponId = request.getCouponId();
        String forcePath = request.getForcePath();

        // 将 DTO items 转为内部 Map 格式
        List<Map<String, Object>> items = new ArrayList<>();
        if (request.getItems() != null)
        {
            for (PromotionCalcRequest.CartItem ci : request.getItems())
            {
                Map<String, Object> m = new LinkedHashMap<>();
                m.put("productId", ci.getProductId());
                m.put("quantity", ci.getQuantity());
                m.put("specPrice", ci.getSpecPrice());
                items.add(m);
            }
        }

        // ---- 1. 获取商品真实价格，计算 originalAmount ----
        Map<Long, BigDecimal> priceMap = new HashMap<>();
        Map<Long, String> nameMap = new HashMap<>();
        for (Map<String, Object> item : items)
        {
            Long productId = toLong(item.get("productId"));
            if (productId != null && !priceMap.containsKey(productId))
            {
                PosFood food = posFoodMapper.selectById(productId);
                if (food != null)
                {
                    priceMap.put(productId, food.getPrice());
                    nameMap.put(productId, food.getName());
                }
            }
        }

        BigDecimal originalAmount = BigDecimal.ZERO;
        List<Map<String, Object>> itemsWithPrice = new ArrayList<>();
        for (Map<String, Object> item : items)
        {
            Long productId = toLong(item.get("productId"));
            int quantity = toInt(item.get("quantity"));
            // 实付单价 = 商品价格 + 规格价格(规格价可为0/不传), 按行计算以支持同商品多规格
            BigDecimal basePrice = priceMap.getOrDefault(productId, BigDecimal.ZERO);
            BigDecimal specPrice = (BigDecimal) item.get("specPrice");
            if (specPrice == null)
            {
                specPrice = BigDecimal.ZERO;
            }
            BigDecimal unitPrice = basePrice.add(specPrice);
            originalAmount = originalAmount.add(unitPrice.multiply(BigDecimal.valueOf(quantity)));

            Map<String, Object> ip = new LinkedHashMap<>();
            ip.put("productId", productId);
            ip.put("quantity", quantity);
            ip.put("unitPrice", unitPrice);
            ip.put("name", nameMap.getOrDefault(productId, ""));
            itemsWithPrice.add(ip);
        }
        originalAmount = originalAmount.setScale(0, RoundingMode.HALF_UP);

        // ---- 2. 查询门店生效的促销活动 ----
        List<PromotionActivity> activeActivities = activityMapper.selectActiveByStoreId(storeId);
        List<PromotionActivityRule> activeRules = activityRuleMapper.selectActiveRulesByStoreId(storeId);

        Map<Long, PromotionActivity> activityMap = activeActivities.stream()
                .collect(Collectors.toMap(PromotionActivity::getId, a -> a, (a, b) -> a));

        Map<Long, List<PromotionActivityRule>> rulesByActivity = activeRules.stream()
                .collect(Collectors.groupingBy(PromotionActivityRule::getActivityId));

        Map<Long, List<PromotionActivityRule>> rulesByProduct = activeRules.stream()
                .filter(r -> r.getProductId() != null)
                .collect(Collectors.groupingBy(PromotionActivityRule::getProductId));

        // ---- 3. PATH A: 折扣(type=2) / 第二份半价(type=3) ----
        Map<String, Object> pathAResult = calculatePathA(itemsWithPrice, activeActivities,
                rulesByActivity, rulesByProduct, activityMap);

        // ---- 4. PATH B: 满减(type=1) ----
        Map<String, Object> pathBResult = calculatePathB(itemsWithPrice, originalAmount,
                activeActivities, rulesByActivity, activityMap);

        // ---- 5. 比较两条路径 ----
        BigDecimal subtotalA = (BigDecimal) pathAResult.get("subtotal");
        BigDecimal subtotalB = (BigDecimal) pathBResult.get("subtotal");

        String optimalPath;
        if (forcePath != null && ("A".equalsIgnoreCase(forcePath) || "B".equalsIgnoreCase(forcePath)))
        {
            optimalPath = forcePath.toUpperCase();
        }
        else
        {
            optimalPath = subtotalA.compareTo(subtotalB) < 0 ? "A" : "B";
        }

        BigDecimal afterPromotion = "A".equals(optimalPath) ? subtotalA : subtotalB;
        BigDecimal promotionReduce = originalAmount.subtract(afterPromotion);

        // ---- 5.5 优惠幅度预算（spec FR-025）：预算 = 比例 × originalAmount，按 促销→新客→券 层层封顶 ----
        // budgetLimit=null（开关关闭）即预算无限：下面所有封顶分支全部跳过，行为与现状完全一致
        BigDecimal budgetLimit = discountLimitChecker.isEnabled()
                ? originalAmount.multiply(discountLimitChecker.getRatioPercent().movePointLeft(2))
                        .setScale(0, RoundingMode.HALF_UP)
                : null;
        // 促销+新客的已占预算（券之前）；候选券列表也复用它算"剩余预算"，与当前选中哪张券无关
        BigDecimal budgetUsedBeforeCoupon = BigDecimal.ZERO;
        boolean promotionCapped = false;
        // 半价胜出豁免：第二杯半价结构性封顶该商品小计的25%，不占预算也不封顶（spec Session 2026-09-02）
        boolean halfPriceExempt = "A".equals(optimalPath)
                && Integer.valueOf(3).equals(pathAResult.get("appliedType"));
        if (budgetLimit != null && !halfPriceExempt && promotionReduce.compareTo(BigDecimal.ZERO) > 0)
        {
            BigDecimal capped = promotionReduce.min(budgetLimit);
            promotionCapped = capped.compareTo(promotionReduce) < 0;
            budgetUsedBeforeCoupon = budgetUsedBeforeCoupon.add(capped);
            promotionReduce = capped;
            afterPromotion = originalAmount.subtract(promotionReduce);
        }

        // ---- 6. 新客立减(type=4) ----
        BigDecimal newCustomerReduce = BigDecimal.ZERO;
        if (userId != null)
        {
            LambdaQueryWrapper<PosOrder> orderWrapper = new LambdaQueryWrapper<>();
            orderWrapper.eq(PosOrder::getUserId, userId)
                        .eq(PosOrder::getMdId, storeId)
                        .eq(PosOrder::getPayStatus, 1L);
            Long paidCount = posOrderMapper.selectCount(orderWrapper);

            if (paidCount == 0)
            {
                for (PromotionActivity act : activeActivities)
                {
                    if (act.getType() != null && act.getType() == 4)
                    {
                        List<PromotionActivityRule> ncRules = rulesByActivity.get(act.getId());
                        if (ncRules != null && !ncRules.isEmpty())
                        {
                            BigDecimal reduce = ncRules.get(0).getReduceAmount();
                            if (reduce != null && reduce.compareTo(BigDecimal.ZERO) > 0)
                            {
                                newCustomerReduce = reduce;
                            }
                        }
                        break;
                    }
                }
            }
        }

        // 新客立减同样封顶：实减 = min(设置额, 剩余预算)。创建侧无门槛无分母，统一由预算兜底
        boolean newCustomerCapped = false;
        if (budgetLimit != null && newCustomerReduce.compareTo(BigDecimal.ZERO) > 0)
        {
            BigDecimal remaining = budgetLimit.subtract(budgetUsedBeforeCoupon).max(BigDecimal.ZERO);
            BigDecimal capped = newCustomerReduce.min(remaining);
            newCustomerCapped = capped.compareTo(newCustomerReduce) < 0;
            budgetUsedBeforeCoupon = budgetUsedBeforeCoupon.add(capped);
            newCustomerReduce = capped;
        }

        // ---- 7. 优惠券 ----
        BigDecimal couponReduce = BigDecimal.ZERO;
        boolean couponCapped = false;
        String couponName = null;
        Boolean couponConflict = null;
        String conflictNote = null;
        PromotionCouponBatch batch = null;
        // 商品券生效时, 记录参与优惠的商品明细(名称/原价/优惠后价)
        List<Map<String, Object>> couponProductItems = null;

        if (couponId != null)
        {
            PromotionUserCoupon userCoupon = userCouponMapper.selectById(couponId);
            if (userCoupon != null && userCoupon.getUserId().equals(userId)
                    && userCoupon.getStatus() != null && userCoupon.getStatus() == 0)
            {
                if (userCoupon.getExpireTime() != null && userCoupon.getExpireTime().before(new Date()))
                {
                    // 已过期，不处理
                }
                else
                {
                    batch = couponBatchMapper.selectById(userCoupon.getBatchId());
                    PromotionCouponRule couponRule = couponRuleMapper.selectRuleByBatchId(userCoupon.getBatchId());

                    if (batch != null && couponRule != null)
                    {
                        couponName = batch.getName();
                        int isMutex = couponRule.getIsMutex() != null ? couponRule.getIsMutex() : 0;

                        if (isMutex == 1 && promotionReduce.compareTo(BigDecimal.ZERO) > 0)
                        {
                            couponConflict = true;
                            conflictNote = "互斥券不可与满减/折扣叠加，选择此券将取消促销优惠";
                            CouponCalcResult mutexResult = calcCouponReduce(couponRule, batch, itemsWithPrice, originalAmount, priceMap, nameMap);
                            couponReduce = mutexResult.reduce;
                            couponProductItems = mutexResult.items;
                            // 互斥券路径（spec FR-025）：促销/新客清零，券自身用全额预算封顶
                            if (budgetLimit != null && couponReduce.compareTo(budgetLimit) > 0)
                            {
                                couponReduce = budgetLimit;
                                couponCapped = true;
                                applyCappedCouponReduce(couponProductItems, couponReduce);
                            }
                            afterPromotion = originalAmount;
                            promotionReduce = BigDecimal.ZERO;
                            newCustomerReduce = BigDecimal.ZERO;
                        }
                        else
                        {
                            BigDecimal baseForCoupon = afterPromotion.subtract(newCustomerReduce);
                            baseForCoupon = baseForCoupon.max(BigDecimal.ZERO);

                            if (couponRule.getThreshold() == null
                                    || baseForCoupon.compareTo(couponRule.getThreshold()) >= 0)
                            {
                                if (batch.getCouponType() != null && batch.getCouponType() == 2
                                        && couponRule.getProductId() != null && "A".equals(optimalPath))
                                {
                                    List<PromotionActivityRule> productRules = rulesByProduct.get(couponRule.getProductId());
                                    if (productRules != null)
                                    {
                                        boolean inDiscount = productRules.stream().anyMatch(r ->
                                        {
                                            PromotionActivity act = activityMap.get(r.getActivityId());
                                            return act != null && (act.getType() == 2 || act.getType() == 3);
                                        });
                                        if (inDiscount)
                                        {
                                            couponConflict = true;
                                            conflictNote = "商品券对应的商品在折扣活动中，不可与Path A叠加";
                                        }
                                    }
                                }

                                if (couponConflict == null)
                                {
                                    CouponCalcResult normalResult = calcCouponReduce(couponRule, batch, itemsWithPrice,
                                            baseForCoupon, priceMap, nameMap);
                                    couponReduce = normalResult.reduce;
                                    couponProductItems = normalResult.items;
                                    // 普通券路径：实减 = min(可抵金额, 促销+新客占用后的剩余预算)，部分抵扣
                                    if (budgetLimit != null && couponReduce.compareTo(BigDecimal.ZERO) > 0)
                                    {
                                        BigDecimal remaining = budgetLimit.subtract(budgetUsedBeforeCoupon)
                                                .max(BigDecimal.ZERO);
                                        if (couponReduce.compareTo(remaining) > 0)
                                        {
                                            couponReduce = remaining;
                                            couponCapped = true;
                                            applyCappedCouponReduce(couponProductItems, couponReduce);
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }

        // ---- 8. 最终金额 ----
        BigDecimal finalAmount = afterPromotion.subtract(newCustomerReduce).subtract(couponReduce);
        finalAmount = finalAmount.max(MIN_AMOUNT).setScale(0, RoundingMode.HALF_UP);

        // ---- 9. 构建响应DTO ----
        PromotionCalcResponse response = new PromotionCalcResponse();
        response.setOriginalAmount(originalAmount);
        response.setPathA(toPathResult(pathAResult));
        response.setPathB(toPathResult(pathBResult));
        response.setOptimalPath(optimalPath);
        response.setFinalAmount(finalAmount);

        if (newCustomerReduce.compareTo(BigDecimal.ZERO) > 0)
        {
            response.setNewCustomerReduce(newCustomerReduce);
        }
        if (couponId != null)
        {
            response.setCouponId(couponId);
        }
        if (couponName != null)
        {
            response.setCouponName(couponName);
        }
        if (couponReduce.compareTo(BigDecimal.ZERO) > 0)
        {
            response.setCouponReduce(couponReduce);
        }
        if (couponConflict != null)
        {
            response.setCouponConflict(couponConflict);
        }
        if (conflictNote != null)
        {
            response.setConflictNote(conflictNote);
        }
        // 设置优惠券批次ID（用于下单快照写入）
        if (couponId != null && batch != null)
        {
            response.setCouponBatchId(batch.getId());
        }
        // 商品券: 返回参与优惠的商品明细(名称/原价/优惠后价)
        if (couponProductItems != null && !couponProductItems.isEmpty())
        {
            response.setCouponProductItems(couponProductItems.stream()
                    .map(this::toLineItem).collect(Collectors.toList()));
        }

        // ---- 10. 明细列表 ----
        List<PromotionDetail> details = new ArrayList<>();
        if (promotionReduce.compareTo(BigDecimal.ZERO) > 0)
        {
            PromotionDetail detail = new PromotionDetail();
            detail.setType("promotion");
            if ("A".equals(optimalPath))
            {
                detail.setSubType((Integer) pathAResult.get("appliedType"));
                detail.setName((String) pathAResult.get("label"));
                // 找到对应类型的促销活动ID
                Integer appliedType = (Integer) pathAResult.get("appliedType");
                for (PromotionActivity act : activeActivities)
                {
                    if (act.getType() != null && act.getType().equals(appliedType))
                    {
                        detail.setRefId(act.getId());
                        break;
                    }
                }
            }
            else
            {
                detail.setSubType(1);
                detail.setName((String) pathBResult.get("label"));
                // 从满减匹配规则中获取活动ID
                @SuppressWarnings("unchecked")
                Map<String, Object> matchedRule = (Map<String, Object>) pathBResult.get("matchedRule");
                if (matchedRule != null)
                {
                    detail.setRefId(toLong(matchedRule.get("activityId")));
                }
            }
            detail.setReduce(promotionReduce);
            if (promotionCapped)
            {
                detail.setLimitCapped(true);
            }
            details.add(detail);
        }
        if (newCustomerReduce.compareTo(BigDecimal.ZERO) > 0)
        {
            PromotionDetail detail = new PromotionDetail();
            detail.setType("promotion");
            detail.setSubType(4);
            detail.setName("新客立减");
            detail.setReduce(newCustomerReduce);
            if (newCustomerCapped)
            {
                detail.setLimitCapped(true);
            }
            // 找到新客立减活动ID
            for (PromotionActivity act : activeActivities)
            {
                if (act.getType() != null && act.getType() == 4)
                {
                    detail.setRefId(act.getId());
                    break;
                }
            }
            details.add(detail);
        }
        if (couponReduce.compareTo(BigDecimal.ZERO) > 0)
        {
            PromotionDetail detail = new PromotionDetail();
            detail.setType("coupon");
            detail.setSubType(0);
            detail.setName(couponName);
            detail.setReduce(couponReduce);
            if (couponCapped)
            {
                detail.setLimitCapped(true);
            }
            // 优惠券的refId设为券批次ID
            if (batch != null)
            {
                detail.setRefId(batch.getId());
            }
            details.add(detail);
        }
        response.setDetails(details);

        // ---- 11. 可用优惠券列表 ----
        List<Map<String, Object>> availableCouponMaps = buildAvailableCoupons(storeId, userId,
                originalAmount, afterPromotion, promotionReduce, itemsWithPrice,
                rulesByProduct, activityMap, priceMap, nameMap, optimalPath,
                budgetLimit, budgetUsedBeforeCoupon);
        response.setAvailableCoupons(availableCouponMaps.stream()
                .map(this::toAvailableCoupon)
                .collect(Collectors.toList()));

        return response;
    }

    // ==================== DTO转换方法 ====================

    private PathResult toPathResult(Map<String, Object> pathMap)
    {
        PathResult pr = new PathResult();
        pr.setLabel((String) pathMap.get("label"));
        pr.setSubtotal((BigDecimal) pathMap.get("subtotal"));
        if (pathMap.containsKey("appliedType"))
        {
            pr.setAppliedType((Integer) pathMap.get("appliedType"));
        }
        if (pathMap.containsKey("promotionReduce"))
        {
            pr.setPromotionReduce((BigDecimal) pathMap.get("promotionReduce"));
        }

        @SuppressWarnings("unchecked")
        List<Map<String, Object>> itemList = (List<Map<String, Object>>) pathMap.get("items");
        if (itemList != null)
        {
            pr.setItems(itemList.stream().map(this::toLineItem).collect(Collectors.toList()));
        }

        @SuppressWarnings("unchecked")
        Map<String, Object> matchedRuleMap = (Map<String, Object>) pathMap.get("matchedRule");
        if (matchedRuleMap != null)
        {
            MatchedRule mr = new MatchedRule();
            mr.setId(toLong(matchedRuleMap.get("id")));
            mr.setActivityId(toLong(matchedRuleMap.get("activityId")));
            mr.setActivityName((String) matchedRuleMap.get("activityName"));
            mr.setThreshold((BigDecimal) matchedRuleMap.get("threshold"));
            mr.setReduceAmount((BigDecimal) matchedRuleMap.get("reduceAmount"));
            pr.setMatchedRule(mr);
        }

        return pr;
    }

    private LineItem toLineItem(Map<String, Object> itemMap)
    {
        LineItem li = new LineItem();
        li.setProductId(toLong(itemMap.get("productId")));
        li.setQuantity(toInt(itemMap.get("quantity")));
        li.setUnitPrice((BigDecimal) itemMap.get("unitPrice"));
        li.setName((String) itemMap.get("name"));
        li.setOriginalLineTotal((BigDecimal) itemMap.get("originalLineTotal"));
        li.setFinalLineTotal((BigDecimal) itemMap.get("finalLineTotal"));
        li.setDiscountRate((BigDecimal) itemMap.get("discountRate"));
        li.setHalfPriceApplied((Boolean) itemMap.get("halfPriceApplied"));
        return li;
    }

    private AvailableCoupon toAvailableCoupon(Map<String, Object> couponMap)
    {
        AvailableCoupon ac = new AvailableCoupon();
        ac.setId(toLong(couponMap.get("id")));
        ac.setName((String) couponMap.get("name"));
        ac.setCouponType((Integer) couponMap.get("couponType"));
        ac.setIsMutex((Integer) couponMap.get("isMutex"));
        ac.setThreshold((BigDecimal) couponMap.get("threshold"));
        ac.setAmount((BigDecimal) couponMap.get("amount"));
        ac.setDiscountRate((BigDecimal) couponMap.get("discountRate"));
        ac.setUsable((Boolean) couponMap.get("usable"));
        ac.setUnusableReason((String) couponMap.get("unusableReason"));
        ac.setConflictWithPromotion((Boolean) couponMap.get("conflictWithPromotion"));
        ac.setCouponPreviewReduce((BigDecimal) couponMap.get("couponPreviewReduce"));
        @SuppressWarnings("unchecked")
        List<Map<String, Object>> productPreview = (List<Map<String, Object>>) couponMap.get("productPreview");
        if (productPreview != null && !productPreview.isEmpty())
        {
            ac.setProductPreview(productPreview.stream().map(this::toLineItem).collect(Collectors.toList()));
        }
        ac.setExpireTime((java.util.Date) couponMap.get("expireTime"));
        return ac;
    }

    // ==================== 内部计算方法（保持Map） ====================

    /**
     * PATH A: 折扣(type=2) vs 第二份半价(type=3)，取更优
     */
    private Map<String, Object> calculatePathA(List<Map<String, Object>> itemsWithPrice,
                                               List<PromotionActivity> activeActivities,
                                               Map<Long, List<PromotionActivityRule>> rulesByActivity,
                                               Map<Long, List<PromotionActivityRule>> rulesByProduct,
                                               Map<Long, PromotionActivity> activityMap)
    {
        Map<String, Object> pathResult = new LinkedHashMap<>();
        pathResult.put("label", "折扣");
        pathResult.put("appliedType", 2);

        Map<String, Object> discountResult = calcDiscount(itemsWithPrice, rulesByProduct, activityMap);
        Map<String, Object> halfPriceResult = calcHalfPrice(itemsWithPrice, rulesByProduct, activityMap);

        BigDecimal discountSubtotal = (BigDecimal) discountResult.get("subtotal");
        BigDecimal halfPriceSubtotal = (BigDecimal) halfPriceResult.get("subtotal");

        if (halfPriceSubtotal.compareTo(discountSubtotal) < 0)
        {
            pathResult.put("label", "第二份半价");
            pathResult.put("appliedType", 3);
            pathResult.put("items", halfPriceResult.get("items"));
            pathResult.put("subtotal", halfPriceSubtotal);
            pathResult.put("promotionReduce",
                    discountResult.containsKey("originalRef")
                            ? ((BigDecimal) discountResult.get("originalRef")).subtract(halfPriceSubtotal)
                                    .setScale(0, RoundingMode.HALF_UP) : BigDecimal.ZERO);
        }
        else
        {
            pathResult.put("items", discountResult.get("items"));
            pathResult.put("subtotal", discountSubtotal);
            pathResult.put("promotionReduce",
                    discountResult.containsKey("originalRef")
                            ? ((BigDecimal) discountResult.get("originalRef")).subtract(discountSubtotal)
                                    .setScale(0, RoundingMode.HALF_UP) : BigDecimal.ZERO);
        }

        return pathResult;
    }

    /**
     * 折扣计算: 匹配到商品的规则 → unitPrice * discountRate
     */
    private Map<String, Object> calcDiscount(List<Map<String, Object>> itemsWithPrice,
                                             Map<Long, List<PromotionActivityRule>> rulesByProduct,
                                             Map<Long, PromotionActivity> activityMap)
    {
        Map<String, Object> result = new LinkedHashMap<>();
        List<Map<String, Object>> itemList = new ArrayList<>();
        BigDecimal subtotal = BigDecimal.ZERO;
        BigDecimal originalRef = BigDecimal.ZERO;

        for (Map<String, Object> item : itemsWithPrice)
        {
            Long productId = toLong(item.get("productId"));
            int quantity = toInt(item.get("quantity"));
            BigDecimal unitPrice = (BigDecimal) item.get("unitPrice");

            BigDecimal lineOriginal = unitPrice.multiply(BigDecimal.valueOf(quantity));
            originalRef = originalRef.add(lineOriginal);

            BigDecimal finalPrice = lineOriginal;
            PromotionActivityRule matchedRule = null;

            List<PromotionActivityRule> productRules = rulesByProduct.get(productId);
            if (productRules != null)
            {
                for (PromotionActivityRule rule : productRules)
                {
                    PromotionActivity act = activityMap.get(rule.getActivityId());
                    if (act != null && act.getType() == 2 && rule.getDiscountRate() != null)
                    {
                        if (rule.getMinQuantity() == null || quantity >= rule.getMinQuantity())
                        {
                            BigDecimal discounted = unitPrice.multiply(rule.getDiscountRate())
                                    .multiply(BigDecimal.valueOf(quantity))
                                    .setScale(0, RoundingMode.HALF_UP);
                            if (discounted.compareTo(finalPrice) < 0)
                            {
                                finalPrice = discounted;
                                matchedRule = rule;
                            }
                        }
                    }
                }
            }

            Map<String, Object> lineItem = new LinkedHashMap<>();
            lineItem.put("productId", productId);
            lineItem.put("quantity", quantity);
            lineItem.put("unitPrice", unitPrice);
            lineItem.put("originalLineTotal", lineOriginal.setScale(0, RoundingMode.HALF_UP));
            lineItem.put("finalLineTotal", finalPrice.setScale(0, RoundingMode.HALF_UP));
            lineItem.put("name", item.get("name"));
            if (matchedRule != null)
            {
                lineItem.put("discountRate", matchedRule.getDiscountRate());
            }
            itemList.add(lineItem);
            subtotal = subtotal.add(finalPrice);
        }

        result.put("items", itemList);
        result.put("subtotal", subtotal.setScale(0, RoundingMode.HALF_UP));
        result.put("originalRef", originalRef.setScale(0, RoundingMode.HALF_UP));
        return result;
    }

    /**
     * 第二份半价计算: 每两件中的第二件按50%
     */
    private Map<String, Object> calcHalfPrice(List<Map<String, Object>> itemsWithPrice,
                                              Map<Long, List<PromotionActivityRule>> rulesByProduct,
                                              Map<Long, PromotionActivity> activityMap)
    {
        Map<String, Object> result = new LinkedHashMap<>();
        List<Map<String, Object>> itemList = new ArrayList<>();
        BigDecimal subtotal = BigDecimal.ZERO;
        BigDecimal originalRef = BigDecimal.ZERO;

        for (Map<String, Object> item : itemsWithPrice)
        {
            Long productId = toLong(item.get("productId"));
            int quantity = toInt(item.get("quantity"));
            BigDecimal unitPrice = (BigDecimal) item.get("unitPrice");

            BigDecimal lineOriginal = unitPrice.multiply(BigDecimal.valueOf(quantity));
            originalRef = originalRef.add(lineOriginal);

            BigDecimal finalPrice = lineOriginal;
            PromotionActivityRule matchedRule = null;

            List<PromotionActivityRule> productRules = rulesByProduct.get(productId);
            if (productRules != null)
            {
                for (PromotionActivityRule rule : productRules)
                {
                    PromotionActivity act = activityMap.get(rule.getActivityId());
                    if (act != null && act.getType() == 3)
                    {
                        if (rule.getMinQuantity() == null || quantity >= rule.getMinQuantity())
                        {
                            int pairs = quantity / 2;
                            int remainder = quantity % 2;
                            BigDecimal halfPriceTotal = unitPrice.multiply(BigDecimal.valueOf(pairs))
                                    .multiply(new BigDecimal("1.5"))
                                    .add(unitPrice.multiply(BigDecimal.valueOf(remainder)))
                                    .setScale(0, RoundingMode.HALF_UP);
                            if (halfPriceTotal.compareTo(finalPrice) < 0)
                            {
                                finalPrice = halfPriceTotal;
                                matchedRule = rule;
                            }
                        }
                    }
                }
            }

            Map<String, Object> lineItem = new LinkedHashMap<>();
            lineItem.put("productId", productId);
            lineItem.put("quantity", quantity);
            lineItem.put("unitPrice", unitPrice);
            lineItem.put("originalLineTotal", lineOriginal.setScale(0, RoundingMode.HALF_UP));
            lineItem.put("finalLineTotal", finalPrice.setScale(0, RoundingMode.HALF_UP));
            lineItem.put("name", item.get("name"));
            if (matchedRule != null)
            {
                lineItem.put("halfPriceApplied", true);
            }
            itemList.add(lineItem);
            subtotal = subtotal.add(finalPrice);
        }

        result.put("items", itemList);
        result.put("subtotal", subtotal.setScale(0, RoundingMode.HALF_UP));
        result.put("originalRef", originalRef.setScale(0, RoundingMode.HALF_UP));
        return result;
    }

    /**
     * PATH B: 满减(type=1) — 找最高匹配门槛的规则
     */
    private Map<String, Object> calculatePathB(List<Map<String, Object>> itemsWithPrice,
                                               BigDecimal originalAmount,
                                               List<PromotionActivity> activeActivities,
                                               Map<Long, List<PromotionActivityRule>> rulesByActivity,
                                               Map<Long, PromotionActivity> activityMap)
    {
        Map<String, Object> pathResult = new LinkedHashMap<>();
        pathResult.put("label", "满减");

        List<Map<String, Object>> itemList = new ArrayList<>();
        for (Map<String, Object> item : itemsWithPrice)
        {
            Map<String, Object> lineItem = new LinkedHashMap<>();
            lineItem.put("productId", item.get("productId"));
            lineItem.put("quantity", item.get("quantity"));
            lineItem.put("unitPrice", item.get("unitPrice"));
            BigDecimal lineTotal = ((BigDecimal) item.get("unitPrice"))
                    .multiply(BigDecimal.valueOf(toInt(item.get("quantity"))))
                    .setScale(0, RoundingMode.HALF_UP);
            lineItem.put("originalLineTotal", lineTotal);
            lineItem.put("finalLineTotal", lineTotal);
            lineItem.put("name", item.get("name"));
            itemList.add(lineItem);
        }
        pathResult.put("items", itemList);

        PromotionActivityRule bestMatch = null;
        PromotionActivity bestActivity = null;

        for (PromotionActivity act : activeActivities)
        {
            if (act.getType() != null && act.getType() == 1)
            {
                List<PromotionActivityRule> rules = rulesByActivity.get(act.getId());
                if (rules != null)
                {
                    for (PromotionActivityRule rule : rules)
                    {
                        if (rule.getThreshold() != null && rule.getReduceAmount() != null
                                && originalAmount.compareTo(rule.getThreshold()) >= 0)
                        {
                            if (bestMatch == null || rule.getThreshold().compareTo(bestMatch.getThreshold()) > 0)
                            {
                                bestMatch = rule;
                                bestActivity = act;
                            }
                        }
                    }
                }
            }
        }

        if (bestMatch != null)
        {
            BigDecimal subtotal = originalAmount.subtract(bestMatch.getReduceAmount())
                    .max(MIN_AMOUNT).setScale(0, RoundingMode.HALF_UP);
            pathResult.put("subtotal", subtotal);
            pathResult.put("promotionReduce", originalAmount.subtract(subtotal));
            Map<String, Object> matchedRuleInfo = new LinkedHashMap<>();
            matchedRuleInfo.put("id", bestMatch.getId());
            matchedRuleInfo.put("activityId", bestMatch.getActivityId());
            matchedRuleInfo.put("threshold", bestMatch.getThreshold());
            matchedRuleInfo.put("reduceAmount", bestMatch.getReduceAmount());
            if (bestActivity != null)
            {
                matchedRuleInfo.put("activityName", bestActivity.getName());
            }
            pathResult.put("matchedRule", matchedRuleInfo);
        }
        else
        {
            pathResult.put("subtotal", originalAmount);
            pathResult.put("promotionReduce", BigDecimal.ZERO);
        }

        return pathResult;
    }

    /**
     * 券减免计算结果: reduce=减免金额, items=商品券参与的商品明细(仅商品券有)
     */
    private static class CouponCalcResult
    {
        BigDecimal reduce = BigDecimal.ZERO;
        List<Map<String, Object>> items = new ArrayList<>();
    }

    /**
     * 计算单张券的减免金额; 商品券(couponType=2)同时返回参与商品明细(名称/原价/优惠后价)
     */
    private CouponCalcResult calcCouponReduce(PromotionCouponRule couponRule, PromotionCouponBatch batch,
                                              List<Map<String, Object>> itemsWithPrice,
                                              BigDecimal baseAmount, Map<Long, BigDecimal> priceMap,
                                              Map<Long, String> nameMap)
    {
        CouponCalcResult result = new CouponCalcResult();
        if (batch.getCouponType() != null && batch.getCouponType() == 2)
        {
            if (couponRule.getProductId() != null)
            {
                Long pid = couponRule.getProductId();
                // 汇总购物车内该商品的所有行(支持同商品多规格): 每行实付单价=商品价格+规格价格
                int quantity = 0;
                BigDecimal productPrice = priceMap.getOrDefault(pid, BigDecimal.ZERO);
                BigDecimal totalProductPrice = BigDecimal.ZERO;
                for (Map<String, Object> item : itemsWithPrice)
                {
                    if (pid.equals(toLong(item.get("productId"))))
                    {
                        int qty = toInt(item.get("quantity"));
                        BigDecimal unitPrice = (BigDecimal) item.get("unitPrice");
                        if (quantity == 0)
                        {
                            productPrice = unitPrice; // 首行单价(展示用)
                        }
                        totalProductPrice = totalProductPrice.add(unitPrice.multiply(BigDecimal.valueOf(qty)));
                        quantity += qty;
                    }
                }
                BigDecimal finalLineTotal = totalProductPrice;
                if (couponRule.getDiscountRate() != null)
                {
                    BigDecimal reduction = totalProductPrice.multiply(BigDecimal.ONE.subtract(couponRule.getDiscountRate()));
                    result.reduce = reduction.max(BigDecimal.ZERO).min(totalProductPrice)
                            .setScale(0, RoundingMode.HALF_UP);
                    finalLineTotal = totalProductPrice.subtract(result.reduce).max(BigDecimal.ZERO)
                            .setScale(0, RoundingMode.HALF_UP);
                }
                else if (couponRule.getAmount() != null)
                {
                    result.reduce = couponRule.getAmount().min(totalProductPrice)
                            .setScale(0, RoundingMode.HALF_UP);
                    finalLineTotal = totalProductPrice.subtract(result.reduce).max(BigDecimal.ZERO)
                            .setScale(0, RoundingMode.HALF_UP);
                }
                // 购物车含该商品才产出明细
                if (quantity > 0)
                {
                    Map<String, Object> lineItem = new LinkedHashMap<>();
                    lineItem.put("productId", pid);
                    lineItem.put("quantity", quantity);
                    lineItem.put("unitPrice", productPrice);
                    lineItem.put("name", nameMap.getOrDefault(pid, ""));
                    lineItem.put("originalLineTotal", totalProductPrice.setScale(0, RoundingMode.HALF_UP));
                    lineItem.put("finalLineTotal", finalLineTotal);
                    if (couponRule.getDiscountRate() != null)
                    {
                        lineItem.put("discountRate", couponRule.getDiscountRate());
                    }
                    result.items.add(lineItem);
                }
            }
            return result;
        }
        else
        {
            if (couponRule.getAmount() != null)
            {
                result.reduce = couponRule.getAmount().min(baseAmount).setScale(0, RoundingMode.HALF_UP);
            }
            return result;
        }
    }

    /**
     * 券被预算封顶后，同步把商品券行预览的优惠后价改为按封顶实减重算
     * （originalLineTotal − 封顶后实减），保证返回/快照金额与实减一致
     */
    private void applyCappedCouponReduce(List<Map<String, Object>> couponItems, BigDecimal cappedReduce)
    {
        for (Map<String, Object> lineItem : couponItems)
        {
            BigDecimal original = (BigDecimal) lineItem.get("originalLineTotal");
            if (original != null)
            {
                lineItem.put("finalLineTotal", original.subtract(cappedReduce).max(BigDecimal.ZERO)
                        .setScale(0, RoundingMode.HALF_UP));
            }
        }
    }

    /**
     * 构建用户可用优惠券列表
     *
     * budgetLimit 为 null（限制开关关闭）时不算预算，行为与现状完全一致；
     * budgetUsedBeforeCoupon 为促销+新客已占预算（候选券与当前选中券互为替代，故只看券前占用）
     */
    private List<Map<String, Object>> buildAvailableCoupons(Long storeId, Long userId,
                                                            BigDecimal originalAmount,
                                                            BigDecimal afterPromotion,
                                                            BigDecimal promotionReduce,
                                                            List<Map<String, Object>> itemsWithPrice,
                                                            Map<Long, List<PromotionActivityRule>> rulesByProduct,
                                                            Map<Long, PromotionActivity> activityMap,
                                                            Map<Long, BigDecimal> priceMap,
                                                            Map<Long, String> nameMap,
                                                            String optimalPath,
                                                            BigDecimal budgetLimit,
                                                            BigDecimal budgetUsedBeforeCoupon)
    {
        List<Map<String, Object>> available = new ArrayList<>();
        if (userId == null)
        {
            return available;
        }

        LambdaQueryWrapper<PromotionUserCoupon> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(PromotionUserCoupon::getUserId, userId)
               .eq(PromotionUserCoupon::getStoreId, storeId)
               .eq(PromotionUserCoupon::getStatus, 0)
               .gt(PromotionUserCoupon::getExpireTime, new Date());

        List<PromotionUserCoupon> userCoupons = userCouponMapper.selectList(wrapper);
        for (PromotionUserCoupon uc : userCoupons)
        {
            PromotionCouponBatch batch = couponBatchMapper.selectById(uc.getBatchId());
            if (batch == null || batch.getStatus() == null || batch.getStatus() != 1)
            {
                continue;
            }
            PromotionCouponRule rule = couponRuleMapper.selectRuleByBatchId(uc.getBatchId());
            if (rule == null)
            {
                continue;
            }

            Map<String, Object> couponInfo = new LinkedHashMap<>();
            couponInfo.put("id", uc.getId());
            couponInfo.put("name", batch.getName());
            couponInfo.put("couponType", batch.getCouponType());
            couponInfo.put("isMutex", rule.getIsMutex());
            couponInfo.put("threshold", rule.getThreshold());
            couponInfo.put("amount", rule.getAmount());
            couponInfo.put("discountRate", rule.getDiscountRate());
            couponInfo.put("expireTime", uc.getExpireTime());

            boolean usable = true;
            int isMutex = rule.getIsMutex() != null ? rule.getIsMutex() : 0;

            if (isMutex == 1 && promotionReduce.compareTo(BigDecimal.ZERO) > 0)
            {
                couponInfo.put("conflictWithPromotion", true);
            }

            if (rule.getThreshold() != null)
            {
                BigDecimal baseForCheck = isMutex == 1 ? originalAmount : afterPromotion;
                if (baseForCheck.compareTo(rule.getThreshold()) < 0)
                {
                    usable = false;
                }
            }

            if (batch.getCouponType() != null && batch.getCouponType() == 2 && rule.getProductId() != null)
            {
                // 预览: 该商品券作用于的商品能优惠多少(名称/原价/优惠后价)
                CouponCalcResult preview = calcCouponReduce(rule, batch, itemsWithPrice, afterPromotion, priceMap, nameMap);
                if (preview.reduce.compareTo(BigDecimal.ZERO) > 0)
                {
                    couponInfo.put("couponPreviewReduce", preview.reduce);
                }
                if (!preview.items.isEmpty())
                {
                    couponInfo.put("productPreview", preview.items);
                }
                boolean inCart = itemsWithPrice.stream()
                        .anyMatch(i -> rule.getProductId().equals(toLong(i.get("productId"))));
                if (!inCart)
                {
                    usable = false;
                }
                // 商品券对应的商品如在折扣/第二份半价活动中且 Path A 生效, 则不可用(避免折上折, spec FR-022)
                if (usable && "A".equals(optimalPath))
                {
                    List<PromotionActivityRule> productRules = rulesByProduct.get(rule.getProductId());
                    if (productRules != null)
                    {
                        boolean inDiscount = productRules.stream().anyMatch(r ->
                        {
                            PromotionActivity act = activityMap.get(r.getActivityId());
                            return act != null && (act.getType() == 2 || act.getType() == 3);
                        });
                        if (inDiscount)
                        {
                            usable = false;
                            couponInfo.put("conflictWithPromotion", true);
                        }
                    }
                }
            }

            // 预算封顶（spec FR-026）：候选实抵 = min(原可抵金额, 剩余预算)；互斥券选中会清掉促销，用全额预算
            // 放在商品券预览之后，封顶后的实抵值覆盖未封顶预览
            if (budgetLimit != null)
            {
                // 互斥券只有在实际会取消促销时（促销>0，与 calculate 互斥分支进入条件一致）才享受全额预算；
                // 无促销时互斥券走普通分支，按促销+新客占用后的剩余预算封顶
                boolean mutexActive = isMutex == 1 && promotionReduce.compareTo(BigDecimal.ZERO) > 0;
                BigDecimal candidateBudget = mutexActive ? budgetLimit
                        : budgetLimit.subtract(budgetUsedBeforeCoupon).max(BigDecimal.ZERO);
                BigDecimal baseForCalc = mutexActive ? originalAmount : afterPromotion;
                CouponCalcResult budgetCalc = calcCouponReduce(rule, batch, itemsWithPrice,
                        baseForCalc, priceMap, nameMap);
                BigDecimal cappedReduce = budgetCalc.reduce.min(candidateBudget);
                if (budgetCalc.reduce.compareTo(BigDecimal.ZERO) > 0
                        && cappedReduce.compareTo(BigDecimal.ZERO) <= 0)
                {
                    usable = false;
                    couponInfo.put("unusableReason", "本单优惠已达优惠上限");
                    couponInfo.put("couponPreviewReduce", BigDecimal.ZERO);
                }
                else if (cappedReduce.compareTo(BigDecimal.ZERO) > 0)
                {
                    couponInfo.put("couponPreviewReduce", cappedReduce);
                    if (cappedReduce.compareTo(budgetCalc.reduce) < 0)
                    {
                        // 商品券预览行同步按封顶实减重算，避免预览行金额与 couponPreviewReduce 矛盾
                        applyCappedCouponReduce(budgetCalc.items, cappedReduce);
                        couponInfo.put("productPreview", budgetCalc.items);
                    }
                }
            }

            couponInfo.put("usable", usable);
            available.add(couponInfo);
        }

        return available;
    }

    private Long toLong(Object obj)
    {
        if (obj == null)
        {
            return null;
        }
        if (obj instanceof Long)
        {
            return (Long) obj;
        }
        if (obj instanceof Number)
        {
            return ((Number) obj).longValue();
        }
        try
        {
            return Long.parseLong(obj.toString());
        }
        catch (NumberFormatException e)
        {
            return null;
        }
    }

    private int toInt(Object obj)
    {
        if (obj == null)
        {
            return 0;
        }
        if (obj instanceof Number)
        {
            return ((Number) obj).intValue();
        }
        try
        {
            return Integer.parseInt(obj.toString());
        }
        catch (NumberFormatException e)
        {
            return 0;
        }
    }
}
