package com.jeesharp.business.modules.mall.application.support;

import com.jeesharp.business.modules.common.constants.PlatformTenantConstants;

import com.jeesharp.business.modules.common.enums.DisabledEnabledEnum;

import cn.hutool.core.util.StrUtil;
import com.jeesharp.business.modules.coupon.api.CouponCheckoutApi;
import com.jeesharp.business.modules.coupon.dto.CouponCheckoutContextDTO;
import com.jeesharp.business.modules.mall.dto.MallCheckoutPricingRequest;
import com.jeesharp.business.modules.mall.dto.MallMerchantCouponDTO;
import com.jeesharp.business.modules.mall.dto.MallOrderSubmitLineDTO;
import com.jeesharp.business.modules.mall.entity.MallMemberAddress;
import com.jeesharp.business.modules.mall.entity.MallOrderItem;
import com.jeesharp.business.modules.mall.entity.MallSku;
import com.jeesharp.business.modules.mall.entity.MallSpu;
import com.jeesharp.business.modules.mall.enums.MallDeliveryTypeEnum;
import com.jeesharp.business.modules.mall.enums.MallOrderItemGiftEnum;

import com.jeesharp.business.modules.mall.service.MallFreightTemplateService;
import com.jeesharp.business.modules.mall.service.MallSkuService;
import com.jeesharp.business.modules.mall.service.MallSpuService;
import com.jeesharp.business.modules.mall.vo.MallCheckoutPricingResultVO;
import com.jeesharp.business.modules.mall.vo.MallFreightMerchantBreakdownVO;
import com.jeesharp.framework.exception.BizErrorException;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * 商城结算定价：按商户运费、商户券、首单立减与平台券分摊统一试算；供 {@code MallOrderApplicationService} 复用。
 *
 * @author jeesharp
 * @since 2026-06-10
 */
@Component
@AllArgsConstructor
@Transactional(readOnly = true)
public class MallCheckoutPricingSupport {

    private final MallSpuService mallSpuService;
    private final MallSkuService mallSkuService;
    private final MallFreightTemplateService mallFreightTemplateService;
    private final MallFirstOrderPromoSupport mallFirstOrderPromoSupport;
    private final CouponCheckoutApi couponCheckoutApi;
    private final MallCouponCheckoutContextSupport mallCouponCheckoutContextSupport;

    public MallCheckoutPricingResultVO price(MallCheckoutPricingRequest request) {
        if (request == null || StrUtil.isBlank(request.getMemberId())) {
            throw new BizErrorException("请先登录");
        }
        if (request.getItems() == null || request.getItems().isEmpty()) {
            throw new BizErrorException("请选择商品");
        }
        MallDeliveryTypeEnum deliveryKind = request.getDeliveryType() == null
                ? MallDeliveryTypeEnum.DELIVERY : request.getDeliveryType();

        LinkedHashMap<String, TenantSubmitGroup> tenantGroups = resolveTenantSubmitGroups(request.getItems());
        Map<String, String> merchantCouponByTenant = mapMerchantCoupons(request.getMerchantCoupons());
        applyLegacyMerchantCouponIfNeeded(request, tenantGroups, merchantCouponByTenant);
        String platformCouponId = resolvePlatformCouponId(request);

        List<String> tenantKeys = new ArrayList<>(tenantGroups.keySet());
        List<MallCheckoutPricingResultVO.TenantPricingLine> pricingLines = new ArrayList<>();

        BigDecimal batchGoods = BigDecimal.ZERO;
        BigDecimal batchFreight = BigDecimal.ZERO;
        BigDecimal batchMerchantCoupon = BigDecimal.ZERO;

        for (String tenantKey : tenantKeys) {
            TenantSubmitGroup group = tenantGroups.get(tenantKey);
            BigDecimal freight;
            if (deliveryKind == MallDeliveryTypeEnum.PICKUP) {
                freight = BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
            } else {
                if (request.getAddress() == null) {
                    throw new BizErrorException("收货地址不能为空");
                }
                freight = computeDeliveryFreight(request.getAddress(), group.lineDtos);
            }
            group.freightAmount = freight;
            batchGoods = batchGoods.add(group.goodsTotal);
            batchFreight = batchFreight.add(freight);

            BigDecimal merchantBase = group.goodsTotal.add(freight).setScale(2, RoundingMode.HALF_UP);
            String merchantCouponId = merchantCouponByTenant.get(tenantKey);
            BigDecimal merchantDiscount = BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
            if (StrUtil.isNotBlank(merchantCouponId)) {
                assertMerchantCouponTenant(merchantCouponId, group.tenantCode, group.tenantId);
                CouponCheckoutContextDTO merchantCtx =
                        mallCouponCheckoutContextSupport.buildContext(request.getMemberId(), merchantBase, group.lineDtos);
                merchantDiscount = couponCheckoutApi.resolveDiscount(merchantCtx, merchantCouponId);
            }
            batchMerchantCoupon = batchMerchantCoupon.add(merchantDiscount);

            MallCheckoutPricingResultVO.TenantPricingLine line = new MallCheckoutPricingResultVO.TenantPricingLine();
            line.setTenantKey(tenantKey);
            line.setTenantCode(group.tenantCode);
            line.setTenantId(group.tenantId);
            line.setGoodsAmount(group.goodsTotal);
            line.setFreightAmount(freight);
            line.setMerchantCouponAmount(merchantDiscount);
            line.setMerchantMemberCouponId(merchantCouponId);
            line.setLineDtos(new ArrayList<>(group.lineDtos));
            line.setLines(new ArrayList<>(group.lines));
            pricingLines.add(line);
        }

        BigDecimal amountBeforeDiscount = batchGoods.add(batchFreight).setScale(2, RoundingMode.HALF_UP);
        BigDecimal afterMerchant = amountBeforeDiscount.subtract(batchMerchantCoupon).setScale(2, RoundingMode.HALF_UP);
        if (afterMerchant.compareTo(BigDecimal.ZERO) < 0) {
            afterMerchant = BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
        }

        BigDecimal firstOrderDiscount = mallFirstOrderPromoSupport.resolveFirstOrderDiscount(
                request.getMemberId(), afterMerchant);

        Map<String, BigDecimal> firstOrderWeights = new LinkedHashMap<>();
        for (MallCheckoutPricingResultVO.TenantPricingLine line : pricingLines) {
            BigDecimal w = line.getGoodsAmount().add(line.getFreightAmount())
                    .subtract(line.getMerchantCouponAmount()).setScale(2, RoundingMode.HALF_UP);
            if (w.compareTo(BigDecimal.ZERO) < 0) {
                w = BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
            }
            firstOrderWeights.put(line.getTenantKey(), w);
        }
        Map<String, BigDecimal> firstOrderAlloc = MallAmountAllocationSupport.allocate(
                tenantKeys, firstOrderWeights, firstOrderDiscount);

        BigDecimal batchGoodsOnly = batchGoods.setScale(2, RoundingMode.HALF_UP);
        BigDecimal platformDiscount = BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
        Map<String, BigDecimal> platformAlloc = new LinkedHashMap<>();
        if (StrUtil.isNotBlank(platformCouponId)) {
            assertPlatformCoupon(platformCouponId);
            CouponCheckoutContextDTO platformCtx =
                    mallCouponCheckoutContextSupport.buildContext(request.getMemberId(), batchGoodsOnly, request.getItems());
            platformDiscount = couponCheckoutApi.resolveDiscount(platformCtx, platformCouponId);

            Map<String, BigDecimal> platformWeights = new LinkedHashMap<>();
            for (MallCheckoutPricingResultVO.TenantPricingLine line : pricingLines) {
                CouponCheckoutContextDTO tenantCtx = mallCouponCheckoutContextSupport.buildContext(
                        request.getMemberId(), line.getGoodsAmount(), line.getLineDtos());
                BigDecimal probe = couponCheckoutApi.resolveDiscount(tenantCtx, platformCouponId);
                platformWeights.put(line.getTenantKey(),
                        probe.compareTo(BigDecimal.ZERO) > 0 ? line.getGoodsAmount() : BigDecimal.ZERO);
            }
            platformAlloc = MallAmountAllocationSupport.allocate(tenantKeys, platformWeights, platformDiscount);
        }

        BigDecimal batchPay = amountBeforeDiscount.subtract(batchMerchantCoupon)
                .subtract(firstOrderDiscount).subtract(platformDiscount).setScale(2, RoundingMode.HALF_UP);
        if (batchPay.compareTo(BigDecimal.ZERO) < 0) {
            batchPay = BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
        }

        List<MallFreightMerchantBreakdownVO> breakdown = new ArrayList<>();
        for (MallCheckoutPricingResultVO.TenantPricingLine line : pricingLines) {
            BigDecimal firstAlloc = firstOrderAlloc.getOrDefault(line.getTenantKey(), BigDecimal.ZERO);
            BigDecimal platAlloc = platformAlloc.getOrDefault(line.getTenantKey(), BigDecimal.ZERO);
            line.setFirstOrderDiscountAmount(firstAlloc);
            line.setPlatformCouponAmount(platAlloc);
            BigDecimal subPay = line.getGoodsAmount().add(line.getFreightAmount())
                    .subtract(line.getMerchantCouponAmount())
                    .subtract(firstAlloc)
                    .subtract(platAlloc)
                    .setScale(2, RoundingMode.HALF_UP);
            if (subPay.compareTo(BigDecimal.ZERO) < 0) {
                subPay = BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
            }
            line.setPayAmount(subPay);

            MallFreightMerchantBreakdownVO row = new MallFreightMerchantBreakdownVO();
            row.setTenantKey(line.getTenantKey());
            row.setTenantCode(line.getTenantCode());
            row.setTenantId(line.getTenantId());
            row.setGoodsAmount(line.getGoodsAmount());
            row.setFreightAmount(line.getFreightAmount());
            row.setMerchantCouponAmount(line.getMerchantCouponAmount());
            row.setFirstOrderDiscountAmount(firstAlloc);
            row.setPlatformCouponAmount(platAlloc);
            row.setPayAmount(subPay);
            breakdown.add(row);
        }

        MallCheckoutPricingResultVO result = new MallCheckoutPricingResultVO();
        result.setGoodsAmount(batchGoods.setScale(2, RoundingMode.HALF_UP));
        result.setFreightAmount(batchFreight.setScale(2, RoundingMode.HALF_UP));
        result.setTotalAmount(amountBeforeDiscount);
        result.setFirstOrderDiscountAmount(firstOrderDiscount);
        result.setMerchantCouponAmount(batchMerchantCoupon.setScale(2, RoundingMode.HALF_UP));
        result.setPlatformCouponAmount(platformDiscount);
        result.setCouponAmount(batchMerchantCoupon.add(platformDiscount).setScale(2, RoundingMode.HALF_UP));
        result.setPayAmount(batchPay);
        result.setMerchantBreakdown(breakdown);
        result.setTenantLines(pricingLines);
        return result;
    }

    private static Map<String, String> mapMerchantCoupons(List<MallMerchantCouponDTO> merchantCoupons) {
        Map<String, String> map = new HashMap<>();
        if (merchantCoupons == null) {
            return map;
        }
        for (MallMerchantCouponDTO dto : merchantCoupons) {
            if (dto == null || StrUtil.isBlank(dto.getTenantKey()) || StrUtil.isBlank(dto.getMemberCouponId())) {
                continue;
            }
            map.put(dto.getTenantKey().trim(), dto.getMemberCouponId().trim());
        }
        return map;
    }

    private void applyLegacyMerchantCouponIfNeeded(MallCheckoutPricingRequest request,
                                                   LinkedHashMap<String, TenantSubmitGroup> tenantGroups,
                                                   Map<String, String> merchantCouponByTenant) {
        if (!merchantCouponByTenant.isEmpty() || StrUtil.isBlank(request.getLegacyMemberCouponId())) {
            return;
        }
        if (tenantGroups.size() != 1) {
            return;
        }
        String legacyId = request.getLegacyMemberCouponId().trim();
        String tenantCode = couponCheckoutApi.resolveTemplateTenantCode(legacyId);
        if (PlatformTenantConstants.isPlatformTenant(tenantCode)) {
            return;
        }
        String onlyKey = tenantGroups.keySet().iterator().next();
        merchantCouponByTenant.put(onlyKey, legacyId);
    }

    private String resolvePlatformCouponId(MallCheckoutPricingRequest request) {
        if (StrUtil.isNotBlank(request.getPlatformMemberCouponId())) {
            return request.getPlatformMemberCouponId().trim();
        }
        if (StrUtil.isBlank(request.getLegacyMemberCouponId())) {
            return null;
        }
        String legacyId = request.getLegacyMemberCouponId().trim();
        String tenantCode = couponCheckoutApi.resolveTemplateTenantCode(legacyId);
        if (PlatformTenantConstants.isPlatformTenant(tenantCode)) {
            return legacyId;
        }
        return null;
    }

    private void assertPlatformCoupon(String memberCouponId) {
        String tenantCode = couponCheckoutApi.resolveTemplateTenantCode(memberCouponId);
        if (!PlatformTenantConstants.isPlatformTenant(tenantCode)) {
            throw new BizErrorException("所选优惠券不是平台券");
        }
    }

    private void assertMerchantCouponTenant(String memberCouponId, String tenantCode, String tenantId) {
        String couponTenant = couponCheckoutApi.resolveTemplateTenantCode(memberCouponId);
        if (PlatformTenantConstants.isPlatformTenant(couponTenant)) {
            throw new BizErrorException("平台券请使用 platformMemberCouponId 传递");
        }
        if (StrUtil.isNotBlank(tenantCode) && StrUtil.isNotBlank(couponTenant)
                && !tenantCode.equals(couponTenant)) {
            throw new BizErrorException("商户券与店铺不匹配");
        }
        if (StrUtil.isBlank(tenantCode) && StrUtil.isNotBlank(tenantId) && StrUtil.isNotBlank(couponTenant)
                && !tenantId.equals(couponTenant)) {
            throw new BizErrorException("商户券与店铺不匹配");
        }
    }

    public LinkedHashMap<String, TenantSubmitGroup> resolveTenantSubmitGroups(List<MallOrderSubmitLineDTO> items) {
        LinkedHashMap<String, TenantSubmitGroup> groups = new LinkedHashMap<>();
        for (MallOrderSubmitLineDTO line : items) {
            if (line == null || StrUtil.isBlank(line.getSpuId())) {
                throw new BizErrorException("商品SPU不能为空");
            }
            MallSku sku = resolveSkuForSubmit(line.getSpuId(), line.getSkuId());
            MallSpu spu = mallSpuService.get(line.getSpuId());
            if (spu == null) {
                throw new BizErrorException("商品不存在");
            }
            String tenantCode = StrUtil.trimToNull(spu.getTenantCode());
            String tenantId = StrUtil.trimToNull(spu.getTenantId());
            if (tenantCode == null && tenantId == null) {
                throw new BizErrorException("商品未配置商户，无法下单：" + StrUtil.blankToDefault(spu.getSpuName(), line.getSpuId()));
            }
            String tenantKey = StrUtil.blankToDefault(tenantCode, tenantId);
            TenantSubmitGroup group = groups.computeIfAbsent(tenantKey, key -> {
                TenantSubmitGroup created = new TenantSubmitGroup();
                created.tenantKey = key;
                created.tenantCode = tenantCode;
                created.tenantId = tenantId;
                return created;
            });

            BigDecimal price = sku.getPrice() == null ? BigDecimal.ZERO : sku.getPrice();
            int qty = line.getQuantity();
            BigDecimal lineTotal = price.multiply(BigDecimal.valueOf(qty)).setScale(2, RoundingMode.HALF_UP);
            group.goodsTotal = group.goodsTotal.add(lineTotal);

            MallOrderItem item = new MallOrderItem();
            item.setSpuId(line.getSpuId());
            item.setSkuId(sku.getId());
            item.setSkuName(StrUtil.blankToDefault(sku.getSkuName(), "商品"));
            item.setSpecInfo(StrUtil.blankToDefault(sku.getSpecInfo(), ""));
            item.setPrice(price);
            item.setQuantity(qty);
            item.setTotalAmount(lineTotal);
            item.setImage(StrUtil.blankToDefault(sku.getImage(), ""));
            item.setIsGift(MallOrderItemGiftEnum.NO.getValue());
            group.lines.add(item);
            group.lineDtos.add(line);
        }
        return groups;
    }

    private MallSku resolveSkuForSubmit(String spuId, String skuId) {
        if (StrUtil.isBlank(spuId)) {
            throw new BizErrorException("商品SPU不能为空");
        }
        if (StrUtil.isNotBlank(skuId)) {
            MallSku sku = mallSkuService.get(skuId);
            if (sku == null || !spuId.equals(sku.getSpuId())) {
                throw new BizErrorException("SKU不可用");
            }
            if (!DisabledEnabledEnum.ENABLED.getValue().equals(sku.getStatus())) {
                throw new BizErrorException("SKU已下架");
            }
            return sku;
        }
        List<MallSku> list = mallSkuService.listEnabledSkus(spuId);
        if (list == null || list.isEmpty()) {
            throw new BizErrorException("商品暂无在售规格");
        }
        list.sort(Comparator.comparing(MallSku::getId, Comparator.nullsLast(String::compareTo)));
        return list.get(0);
    }

    private BigDecimal computeDeliveryFreight(MallMemberAddress addr, List<MallOrderSubmitLineDTO> lineDtos) {
        if (addr == null) {
            return BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
        }
        String noneKey = "__NO_TEMPLATE__";
        Map<String, FreightAgg> byTemplate = new LinkedHashMap<>();
        for (MallOrderSubmitLineDTO line : lineDtos) {
            MallSku sku = resolveSkuForSubmit(line.getSpuId(), line.getSkuId());
            MallSpu spu = mallSpuService.get(line.getSpuId());
            if (spu == null) {
                throw new BizErrorException("商品不存在");
            }
            BigDecimal price = sku.getPrice() == null ? BigDecimal.ZERO : sku.getPrice();
            int qty = line.getQuantity();
            BigDecimal lineTotal = price.multiply(BigDecimal.valueOf(qty)).setScale(2, RoundingMode.HALF_UP);
            String tid = StrUtil.trimToNull(spu.getFreightTemplateId());
            String key = tid == null ? noneKey : tid;
            FreightAgg agg = byTemplate.computeIfAbsent(key, k -> new FreightAgg());
            agg.totalQty += qty;
            agg.totalWeight = agg.totalWeight.add(BigDecimal.valueOf(qty));
            agg.goodsAmount = agg.goodsAmount.add(lineTotal);
            if (tid == null) {
                BigDecimal unit = spu.getFreightAmount() == null ? BigDecimal.ZERO : spu.getFreightAmount();
                agg.fallbackFreight = agg.fallbackFreight.add(unit.multiply(BigDecimal.valueOf(qty)));
            }
        }
        BigDecimal sum = BigDecimal.ZERO;
        for (Map.Entry<String, FreightAgg> e : byTemplate.entrySet()) {
            if (noneKey.equals(e.getKey())) {
                sum = sum.add(e.getValue().fallbackFreight);
            } else {
                sum = sum.add(mallFreightTemplateService.calculateFreight(e.getKey(),
                        addr.getProvinceCode(), addr.getCityCode(), addr.getDistrictCode(),
                        addr.getProvince(), addr.getCity(), addr.getDistrict(),
                        e.getValue().totalWeight, e.getValue().totalQty, e.getValue().goodsAmount));
            }
        }
        if (sum.compareTo(BigDecimal.ZERO) < 0) {
            sum = BigDecimal.ZERO;
        }
        return sum.setScale(2, RoundingMode.HALF_UP);
    }

    public static final class TenantSubmitGroup {
        private String tenantKey;
        private String tenantCode;
        private String tenantId;
        private BigDecimal goodsTotal = BigDecimal.ZERO;
        private BigDecimal freightAmount = BigDecimal.ZERO;
        private final List<MallOrderSubmitLineDTO> lineDtos = new ArrayList<>();
        private final List<MallOrderItem> lines = new ArrayList<>();
    }

    private static final class FreightAgg {
        private int totalQty;
        private BigDecimal totalWeight = BigDecimal.ZERO;
        private BigDecimal goodsAmount = BigDecimal.ZERO;
        private BigDecimal fallbackFreight = BigDecimal.ZERO;
    }
}
