package com.jeesharp.business.modules.mall.service.impl;

import cn.hutool.core.util.StrUtil;
import com.jeesharp.business.modules.mall.entity.MallFreightTemplate;
import com.jeesharp.business.modules.mall.entity.MallFreightTemplateRegion;
import com.jeesharp.business.modules.mall.enums.MallFreightChargeTypeEnum;
import com.jeesharp.business.modules.mall.mapper.MallFreightTemplateMapper;
import com.jeesharp.business.modules.mall.mapper.MallFreightTemplateRegionMapper;
import com.jeesharp.business.modules.mall.service.MallFreightTemplateService;
import com.jeesharp.business.modules.mall.vo.MallFreightTemplateQueryVO;
import com.jeesharp.business.modules.mall.vo.MallFreightTemplateRegionQueryVO;
import com.jeesharp.framework.mybatis.core.service.CrudService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

/**
 * 运费模板领域服务：按计费类型与地区规则计算运费，地区行可覆盖模板首费/续费。
 *
 * @author jeesharp
 * @since 2026-05-16
 */
@Service
@Transactional(readOnly = true)
public class MallFreightTemplateServiceImpl
    extends CrudService<MallFreightTemplateMapper, MallFreightTemplate, MallFreightTemplateQueryVO>
    implements MallFreightTemplateService {

    private static final int ENABLED_STATUS = 1;
    private static final String IDS_SEPARATOR = ",";

    @Resource
    private MallFreightTemplateRegionMapper mallFreightTemplateRegionMapper;

    @Override
    @Transactional(readOnly = false)
    public void createTemplate(MallFreightTemplate entity) {
        save(entity);
    }

    @Override
    @Transactional(readOnly = false)
    public void updateTemplate(MallFreightTemplate entity) {
        update(entity);
    }

    @Override
    public List<MallFreightTemplate> getEnabledTemplates() {
        MallFreightTemplateQueryVO query = new MallFreightTemplateQueryVO();
        query.setStatus(ENABLED_STATUS);
        return findList(query);
    }

    @Override
    public BigDecimal calculateFreight(String templateId, String province, String city,
                                       BigDecimal totalWeight, Integer totalQuantity, BigDecimal orderAmount) {
        return calculateFreight(templateId, null, null, null, province, city, null,
            totalWeight, totalQuantity, orderAmount);
    }

    @Override
    public BigDecimal calculateFreight(String templateId,
                                       String provinceCode, String cityCode, String districtCode,
                                       String province, String city, String district,
                                       BigDecimal totalWeight, Integer totalQuantity, BigDecimal orderAmount) {
        MallFreightTemplate template = get(templateId);
        if (template == null || template.getStatus() == null || template.getStatus() != ENABLED_STATUS) {
            return BigDecimal.ZERO;
        }
        MallFreightChargeTypeEnum charge = MallFreightChargeTypeEnum.fromCode(template.getChargeType());
        if (charge == MallFreightChargeTypeEnum.FREE) {
            return BigDecimal.ZERO;
        }
        BigDecimal goods = orderAmount == null ? BigDecimal.ZERO : orderAmount;
        if (template.getFreeAmount() != null && goods.compareTo(template.getFreeAmount()) >= 0) {
            return BigDecimal.ZERO;
        }
        MallFreightTemplateRegion region = resolveRegionRow(template.getId(), provinceCode, cityCode, districtCode,
            province, city, district);
        BigDecimal effFirst = pickFee(region == null ? null : region.getFirstFee(), template.getFirstFee());
        BigDecimal effContinue = pickFee(region == null ? null : region.getContinueFee(), template.getContinueFee());
        if (charge == MallFreightChargeTypeEnum.WEIGHT) {
            BigDecimal w = totalWeight == null ? BigDecimal.ZERO : totalWeight;
            return calculateWeightFreight(template, w, effFirst, effContinue);
        }
        if (charge == MallFreightChargeTypeEnum.ITEM) {
            int q = totalQuantity == null ? 0 : totalQuantity;
            return calculateItemFreight(template, q, effFirst, effContinue);
        }
        return BigDecimal.ZERO;
    }

    @Override
    public MallFreightTemplate getDefaultTemplate() {
        List<MallFreightTemplate> templates = getEnabledTemplates();
        return templates.isEmpty() ? null : templates.get(0);
    }

    private static BigDecimal pickFee(BigDecimal regionFee, BigDecimal templateFee) {
        if (regionFee != null) {
            return regionFee;
        }
        return templateFee == null ? BigDecimal.ZERO : templateFee;
    }

    private MallFreightTemplateRegion resolveRegionRow(String templateId,
                                                       String provinceCode, String cityCode, String districtCode,
                                                       String province, String city, String district) {
        MallFreightTemplateRegionQueryVO q = new MallFreightTemplateRegionQueryVO();
        q.setTemplateId(templateId);
        List<MallFreightTemplateRegion> list = mallFreightTemplateRegionMapper.findList(q);
        if (list == null || list.isEmpty()) {
            return null;
        }
        list.sort(Comparator.comparing(MallFreightTemplateRegion::getSortOrder, Comparator.nullsLast(Integer::compareTo)));
        MallFreightTemplateRegion wildcard = null;
        for (MallFreightTemplateRegion row : list) {
            if (isWildcardRow(row)) {
                if (wildcard == null) {
                    wildcard = row;
                }
                continue;
            }
            if (rowMatches(row, provinceCode, cityCode, districtCode, province, city, district)) {
                return row;
            }
        }
        return wildcard;
    }

    private static boolean isWildcardRow(MallFreightTemplateRegion row) {
        if (row == null) {
            return false;
        }
        String codes = StrUtil.trim(row.getRegionCodes());
        return "*".equals(codes);
    }

    private static boolean rowMatches(MallFreightTemplateRegion row,
                                      String provinceCode, String cityCode, String districtCode,
                                      String province, String city, String district) {
        Set<String> codeSet = splitCodes(row.getRegionCodes());
        if (!codeSet.isEmpty()) {
            if (StrUtil.isNotBlank(districtCode) && codeSet.contains(districtCode.trim())) {
                return true;
            }
            if (StrUtil.isNotBlank(cityCode) && codeSet.contains(cityCode.trim())) {
                return true;
            }
            if (StrUtil.isNotBlank(provinceCode) && codeSet.contains(provinceCode.trim())) {
                return true;
            }
        }
        return nameTokensMatch(row.getRegionNames(), province, city, district);
    }

    private static Set<String> splitCodes(String raw) {
        if (StrUtil.isBlank(raw)) {
            return Set.of();
        }
        String[] parts = raw.split(",");
        LinkedHashSet<String> set = new LinkedHashSet<>();
        for (String p : parts) {
            String t = StrUtil.trim(p);
            if (StrUtil.isNotBlank(t) && !"*".equals(t)) {
                set.add(t);
            }
        }
        return set;
    }

    private static boolean nameTokensMatch(String regionNames, String province, String city, String district) {
        if (StrUtil.isBlank(regionNames)) {
            return false;
        }
        List<String> tokens = new ArrayList<>();
        for (String part : regionNames.split("[,，]")) {
            String t = StrUtil.trim(part);
            if (StrUtil.isNotBlank(t)) {
                tokens.add(t);
            }
        }
        if (tokens.isEmpty()) {
            return false;
        }
        String p = StrUtil.blankToDefault(province, "");
        String c = StrUtil.blankToDefault(city, "");
        String d = StrUtil.blankToDefault(district, "");
        for (String token : tokens) {
            if (StrUtil.isNotBlank(d) && token.contains(d)) {
                return true;
            }
            if (StrUtil.isNotBlank(c) && token.contains(c)) {
                return true;
            }
            if (StrUtil.isNotBlank(p) && token.contains(p)) {
                return true;
            }
        }
        return false;
    }

    private BigDecimal calculateWeightFreight(MallFreightTemplate template, BigDecimal totalWeight,
                                              BigDecimal firstFee, BigDecimal continueFee) {
        if (totalWeight.compareTo(BigDecimal.ZERO) <= 0) {
            return BigDecimal.ZERO;
        }
        BigDecimal firstWeight = template.getFirstWeight() != null ? template.getFirstWeight() : BigDecimal.ONE;
        BigDecimal continueWeight = template.getContinueWeight() != null ? template.getContinueWeight() : BigDecimal.ONE;
        if (totalWeight.compareTo(firstWeight) <= 0) {
            return firstFee;
        }
        BigDecimal extraWeight = totalWeight.subtract(firstWeight);
        BigDecimal extraFeeCount = extraWeight.divide(continueWeight, 0, RoundingMode.CEILING);
        return firstFee.add(extraFeeCount.multiply(continueFee)).setScale(2, RoundingMode.HALF_UP);
    }

    private BigDecimal calculateItemFreight(MallFreightTemplate template, int totalQuantity,
                                            BigDecimal firstFee, BigDecimal continueFee) {
        if (totalQuantity <= 0) {
            return BigDecimal.ZERO;
        }
        int firstItem = template.getFirstItem() != null ? template.getFirstItem() : 1;
        if (totalQuantity <= firstItem) {
            return firstFee.setScale(2, RoundingMode.HALF_UP);
        }
        int extraItems = totalQuantity - firstItem;
        return firstFee.add(BigDecimal.valueOf(extraItems).multiply(continueFee)).setScale(2, RoundingMode.HALF_UP);
    }
}
