package com.yhy.module.core.service.resource.impl;

import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.BAD_REQUEST;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception0;

import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.yhy.module.core.controller.admin.resource.vo.ResourceItemQueryReqVO;
import com.yhy.module.core.controller.admin.resource.vo.ResourceItemRespVO;
import com.yhy.module.core.controller.admin.resource.vo.ResourceItemSaveReqVO;
import com.yhy.module.core.controller.admin.resource.vo.ResourcePriceSaveReqVO;
import com.yhy.module.core.dal.dataobject.resource.ResourceCatalogItemDO;
import com.yhy.module.core.dal.dataobject.resource.ResourceCategoryDO;
import com.yhy.module.core.dal.dataobject.resource.ResourceCategoryTreeMappingDO;
import com.yhy.module.core.dal.dataobject.resource.ResourceItemDO;
import com.yhy.module.core.dal.dataobject.resource.ResourcePriceDO;
import com.yhy.module.core.dal.mysql.resource.ResourceCatalogItemMapper;
import com.yhy.module.core.dal.mysql.resource.ResourceCategoryMapper;
import com.yhy.module.core.dal.mysql.resource.ResourceCategoryTreeMappingMapper;
import com.yhy.module.core.dal.mysql.resource.ResourceItemMapper;
import com.yhy.module.core.dal.mysql.resource.ResourcePriceMapper;
import com.yhy.module.core.service.resource.ResourceItemService;
import java.math.BigDecimal;
import java.time.format.DateTimeFormatter;
import java.util.List;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

@Slf4j
@Service
public class ResourceItemServiceImpl implements ResourceItemService {

    private static final DateTimeFormatter RANGE_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    @Resource
    private ResourceCatalogItemMapper catalogItemMapper;
    @Resource
    private ResourceCategoryMapper categoryMapper;
    @Resource
    private ResourceCategoryTreeMappingMapper categoryTreeMappingMapper;
    @Resource
    private ResourceItemMapper resourceItemMapper;
    @Resource
    private ResourcePriceMapper resourcePriceMapper;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createItem(ResourceItemSaveReqVO reqVO) {
        Long tenantId = TenantContextHolder.getTenantId();
        
        // 0. 创建时catalogItemId必填
        if (reqVO.getCatalogItemId() == null) {
            throw exception0(BAD_REQUEST.getCode(), "创建工料机项时，目录树节点ID不能为空");
        }
        
        // 1. 如果单位为 %，自动清空税率和价格字段
        if ("%".equals(reqVO.getUnit())) {
            reqVO.setTaxRate(null);
            reqVO.setTaxExclBasePrice(null);
            reqVO.setTaxInclBasePrice(null);
            reqVO.setTaxExclCompilePrice(null);
            reqVO.setTaxInclCompilePrice(null);
        }
        
        // 2. 验证目录树节点是否存在
        ResourceCatalogItemDO catalogItem = catalogItemMapper.selectById(reqVO.getCatalogItemId());
        if (catalogItem == null) {
            throw exception0(BAD_REQUEST.getCode(), "目录树节点不存在");
        }
        
        // 4. 验证 category_id 是否在机类树节点的允许范围内
        validateCategoryInAllowedRange(catalogItem.getCategoryTreeId(), reqVO.getCategoryId());
        
        // 5. 验证计算基数
        validateCalcBase(reqVO, catalogItem.getCategoryTreeId());
        
        // 6. 创建工料机项
        ResourceItemDO item = BeanUtils.toBean(reqVO, ResourceItemDO.class);
        item.setTenantId(tenantId);
        // 自动设置 category_tree_id（从目录树节点获取）
        item.setCategoryTreeId(catalogItem.getCategoryTreeId());
        
        // 7. 处理排序（支持基于参考节点的插入位置）
        Integer sortOrder = reqVO.getSortOrder();
        if (sortOrder == null) {
            Long referenceNodeId = reqVO.getReferenceNodeId();
            String insertPosition = reqVO.getInsertPosition();
            
            if (referenceNodeId != null && insertPosition != null) {
                // 基于参考节点计算排序值
                ResourceItemDO referenceNode = resourceItemMapper.selectById(referenceNodeId);
                if (referenceNode != null) {
                    int refSortOrder = referenceNode.getSortOrder() != null ? referenceNode.getSortOrder() : 0;
                    if ("above".equals(insertPosition)) {
                        // 在参考节点上方插入：使用参考节点的排序值，并将参考节点及其后的节点排序值+1
                        sortOrder = refSortOrder;
                        resourceItemMapper.incrementSortOrderFrom(reqVO.getCatalogItemId(), refSortOrder);
                    } else if ("below".equals(insertPosition)) {
                        // 在参考节点下方插入：使用参考节点排序值+1，并将后续节点排序值+1
                        sortOrder = refSortOrder + 1;
                        resourceItemMapper.incrementSortOrderFrom(reqVO.getCatalogItemId(), refSortOrder + 1);
                    } else {
                        // 默认追加到末尾
                        sortOrder = resourceItemMapper.selectMaxSortOrder(reqVO.getCatalogItemId()) + 1;
                    }
                } else {
                    sortOrder = resourceItemMapper.selectMaxSortOrder(reqVO.getCatalogItemId()) + 1;
                }
            } else {
                // 无参考节点，追加到末尾
                sortOrder = resourceItemMapper.selectMaxSortOrder(reqVO.getCatalogItemId()) + 1;
            }
        }
        item.setSortOrder(sortOrder);
        
        resourceItemMapper.insert(item);
        return item.getId();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateItem(ResourceItemSaveReqVO reqVO) {
        Long tenantId = TenantContextHolder.getTenantId();
        
        // 1. 验证工料机项是否存在
        ResourceItemDO db = resourceItemMapper.selectById(reqVO.getId());
        if (db == null) {
            throw exception0(BAD_REQUEST.getCode(), "工料机项不存在");
        }
        
        // 2. 如果是复合工料机，不允许将单位改为 %
        String unit = reqVO.getUnit() != null ? reqVO.getUnit() : db.getUnit();
        if (db.getIsMerged() != null && db.getIsMerged() == 1 && "%".equals(unit)) {
            throw exception0(BAD_REQUEST.getCode(), "复合工料机不允许将单位设置为 %，请先删除所有子工料机");
        }
        
        // 3. 如果单位改为 % 或者已经是 %，自动清空税率和价格字段
        boolean needClearFields = "%".equals(unit);
        
        if (needClearFields) {
            reqVO.setTaxRate(null);
            reqVO.setTaxExclBasePrice(null);
            reqVO.setTaxInclBasePrice(null);
            reqVO.setTaxExclCompilePrice(null);
            reqVO.setTaxInclCompilePrice(null);
        }
        
        // 4. 如果是复合工料机（is_merged=1），自动清空税率
        boolean isMergedItem = db.getIsMerged() != null && db.getIsMerged() == 1;
        if (isMergedItem) {
            reqVO.setTaxRate(null);
        }
        
        // 5. 如果没有传递catalogItemId，使用数据库中的值
        Long catalogItemId = reqVO.getCatalogItemId() != null ? reqVO.getCatalogItemId() : db.getCatalogItemId();
        
        // 6. 验证目录树节点是否存在
        ResourceCatalogItemDO catalogItem = catalogItemMapper.selectById(catalogItemId);
        if (catalogItem == null) {
            throw exception0(BAD_REQUEST.getCode(), "目录树节点不存在");
        }
        
        // 7. 如果没有传递categoryId，使用数据库中的值
        Long categoryId = reqVO.getCategoryId() != null ? reqVO.getCategoryId() : db.getCategoryId();
        
        // 8. 验证 category_id 是否在机类树节点的允许范围内
        validateCategoryInAllowedRange(catalogItem.getCategoryTreeId(), categoryId);
        
        // 8. 验证计算基数
        validateCalcBase(reqVO, catalogItem.getCategoryTreeId());
        
        // 9. 更新工料机项
        ResourceItemDO item = BeanUtils.toBean(reqVO, ResourceItemDO.class);
        item.setTenantId(tenantId);
        // 自动设置 category_tree_id（从目录树节点获取）
        item.setCategoryTreeId(catalogItem.getCategoryTreeId());
        
        // 10. 如果单位为 %，使用 UpdateWrapper 强制更新字段为 null
        if (needClearFields) {
            com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper<ResourceItemDO> updateWrapper = 
                new com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper<>();
            updateWrapper.eq("id", reqVO.getId())
                        .eq("tenant_id", tenantId)
                        .set("tax_rate", null)
                        .set("tax_excl_base_price", null)
                        .set("tax_incl_base_price", null)
                        .set("tax_excl_compile_price", null)
                        .set("tax_incl_compile_price", null);
            
            // 设置其他需要更新的字段（不包括 jsonb 类型）
            if (item.getCode() != null) updateWrapper.set("code", item.getCode());
            if (item.getName() != null) updateWrapper.set("name", item.getName());
            if (item.getSpec() != null) updateWrapper.set("spec", item.getSpec());
            if (item.getCategoryId() != null) updateWrapper.set("category_id", item.getCategoryId());
            if (item.getUnit() != null) updateWrapper.set("unit", item.getUnit());
            if (item.getCatalogItemId() != null) updateWrapper.set("catalog_item_id", item.getCatalogItemId());
            if (item.getCategoryTreeId() != null) updateWrapper.set("category_tree_id", item.getCategoryTreeId());
            // 注意：不在这里设置 calc_base 和 attributes，因为 jsonb 类型会导致 hstore 错误
            
            resourceItemMapper.update(null, updateWrapper);
            
            // 单独更新 jsonb 字段（如果有值）
            if (item.getCalcBase() != null || item.getAttributes() != null) {
                ResourceItemDO jsonbUpdate = new ResourceItemDO();
                jsonbUpdate.setId(reqVO.getId());
                if (item.getCalcBase() != null) jsonbUpdate.setCalcBase(item.getCalcBase());
                if (item.getAttributes() != null) jsonbUpdate.setAttributes(item.getAttributes());
                resourceItemMapper.updateById(jsonbUpdate);
            }
        } else {
            // 使用 UpdateWrapper 来支持更新 null 值（如税率清空）
            com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper<ResourceItemDO> updateWrapper = 
                new com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper<>();
            updateWrapper.eq("id", reqVO.getId())
                        .eq("tenant_id", tenantId);
            
            // 设置所有字段（包括可能为 null 的字段）
            updateWrapper.set("code", item.getCode());
            updateWrapper.set("name", item.getName());
            updateWrapper.set("spec", item.getSpec());
            updateWrapper.set("category_id", item.getCategoryId());
            updateWrapper.set("unit", item.getUnit());
            updateWrapper.set("catalog_item_id", item.getCatalogItemId());
            updateWrapper.set("category_tree_id", item.getCategoryTreeId());
            updateWrapper.set("is_merged", item.getIsMerged());
            // 价格和税率字段（允许为 null）
            updateWrapper.set("tax_rate", item.getTaxRate());
            updateWrapper.set("tax_excl_base_price", item.getTaxExclBasePrice());
            updateWrapper.set("tax_incl_base_price", item.getTaxInclBasePrice());
            updateWrapper.set("tax_excl_compile_price", item.getTaxExclCompilePrice());
            updateWrapper.set("tax_incl_compile_price", item.getTaxInclCompilePrice());
            
            resourceItemMapper.update(null, updateWrapper);
            
            // 单独更新 jsonb 字段（如果有值）
            if (item.getCalcBase() != null || item.getAttributes() != null) {
                ResourceItemDO jsonbUpdate = new ResourceItemDO();
                jsonbUpdate.setId(reqVO.getId());
                if (item.getCalcBase() != null) jsonbUpdate.setCalcBase(item.getCalcBase());
                if (item.getAttributes() != null) jsonbUpdate.setAttributes(item.getAttributes());
                resourceItemMapper.updateById(jsonbUpdate);
            }
        }
    }

    @Override
    public void deleteItem(Long id) {
        resourceItemMapper.deleteById(id);
    }

    @Override
    public PageResult<ResourceItemRespVO> pageItems(ResourceItemQueryReqVO reqVO) {
        LambdaQueryWrapper<ResourceItemDO> wrapper = new LambdaQueryWrapper<ResourceItemDO>()
                .eq(ResourceItemDO::getTenantId, TenantContextHolder.getTenantId());
        
        // 按分类节点查询
        if (reqVO.getCatalogItemId() != null) {
            if (Boolean.TRUE.equals(reqVO.getIncludeChildren())) {
                // 查询该节点所属目录树的所有工料机项
                // 先查询节点的 category_tree_id，然后查询该目录树下的所有工料机项
                wrapper.apply("catalog_item_id IN (SELECT id FROM yhy_catalog_item WHERE deleted = 0 AND category_tree_id = (SELECT category_tree_id FROM yhy_catalog_item WHERE id = {0} AND deleted = 0))", 
                             reqVO.getCatalogItemId());
            } else {
                // 只查询该节点本身的工料机项
                wrapper.eq(ResourceItemDO::getCatalogItemId, reqVO.getCatalogItemId());
            }
        } else if (reqVO.getCategoryTreeId() != null) {
            // 按整个机类树查询
            wrapper.inSql(ResourceItemDO::getCatalogItemId,
                    "select id from yhy_catalog_item where category_tree_id = " + reqVO.getCategoryTreeId());
        }
        
        if (StringUtils.hasText(reqVO.getType())) {
            wrapper.eq(ResourceItemDO::getType, reqVO.getType());
        }
        if (StringUtils.hasText(reqVO.getKeyword())) {
            String like = "%" + reqVO.getKeyword() + "%";
            // 查询匹配的目录树条目 ID
            List<ResourceCatalogItemDO> matchedItems = catalogItemMapper.selectList(new LambdaQueryWrapper<ResourceCatalogItemDO>()
                    .eq(ResourceCatalogItemDO::getTenantId, TenantContextHolder.getTenantId())
                    .and(w -> w.like(ResourceCatalogItemDO::getName, like).or().like(ResourceCatalogItemDO::getCode, like)));
            
            // keyword 搜索范围：工料机名称、工料机单位、目录树名称、目录树编码
            if (!matchedItems.isEmpty()) {
                List<Long> matchedItemIds = matchedItems.stream()
                        .map(ResourceCatalogItemDO::getId)
                        .collect(java.util.stream.Collectors.toList());
                wrapper.and(w -> w.like(ResourceItemDO::getName, like)
                        .or().like(ResourceItemDO::getUnit, like)
                        .or().in(ResourceItemDO::getCatalogItemId, matchedItemIds));
            } else {
                wrapper.and(w -> w.like(ResourceItemDO::getName, like)
                        .or().like(ResourceItemDO::getUnit, like));
            }
        }
        wrapper.orderByAsc(ResourceItemDO::getSortOrder);
        Page<ResourceItemDO> page = resourceItemMapper.selectPage(new Page<>(reqVO.getPageNo(), reqVO.getPageSize()), wrapper);
        
        // 转换为 VO 并填充类别名称
        List<ResourceItemRespVO> records = page.getRecords().stream()
                .map(this::convertToRespVO)
                .collect(java.util.stream.Collectors.toList());
        
        return new PageResult<>(records, page.getTotal());
    }
    
    @Override
    public com.yhy.module.core.controller.admin.resource.vo.ResourceItemWithPricesRespVO getItemWithPrices(Long resourceId) {
        // 1. 查询工料机项
        ResourceItemDO item = resourceItemMapper.selectById(resourceId);
        if (item == null) {
            throw exception0(BAD_REQUEST.getCode(), "工料机项不存在");
        }
        
        // 2. 查询类别（获取类别名称）
        ResourceCategoryDO category = null;
        if (item.getCategoryId() != null) {
            category = categoryMapper.selectById(item.getCategoryId());
        }
        
        // 3. 查询价格列表
        List<ResourcePriceDO> prices = resourcePriceMapper.selectList(
            new LambdaQueryWrapper<ResourcePriceDO>()
                .eq(ResourcePriceDO::getResourceId, resourceId)
                .orderByDesc(ResourcePriceDO::getId)
        );
        
        // 4. 组装响应VO
        com.yhy.module.core.controller.admin.resource.vo.ResourceItemWithPricesRespVO respVO = 
            new com.yhy.module.core.controller.admin.resource.vo.ResourceItemWithPricesRespVO();
        respVO.setId(item.getId());
        respVO.setCode(item.getCode());
        respVO.setName(item.getName());
        respVO.setSpec(item.getSpec());
        respVO.setCategoryId(item.getCategoryId());
        respVO.setCategoryName(category != null ? category.getName() : null);
        respVO.setUnit(item.getUnit());
        respVO.setTaxRate(item.getTaxRate());
        respVO.setTaxExclBasePrice(item.getTaxExclBasePrice());
        respVO.setTaxInclBasePrice(item.getTaxInclBasePrice());
        respVO.setTaxExclCompilePrice(item.getTaxExclCompilePrice());
        respVO.setTaxInclCompilePrice(item.getTaxInclCompilePrice());
        
        // 转换价格列表
        List<com.yhy.module.core.controller.admin.resource.vo.ResourceItemPriceRespVO> priceVOs = 
            prices.stream().map(this::convertToPriceVO).collect(java.util.stream.Collectors.toList());
        respVO.setPrices(priceVOs);
        
        return respVO;
    }
    
    /**
     * 转换价格DO为VO
     */
    private com.yhy.module.core.controller.admin.resource.vo.ResourceItemPriceRespVO convertToPriceVO(ResourcePriceDO price) {
        com.yhy.module.core.controller.admin.resource.vo.ResourceItemPriceRespVO vo = 
            new com.yhy.module.core.controller.admin.resource.vo.ResourceItemPriceRespVO();
        vo.setId(price.getId());
        vo.setRegionCode(price.getRegionCode());
        vo.setPeriodStart(price.getPeriodStart());
        vo.setPeriodEnd(price.getPeriodEnd());
        vo.setPriceTaxExcl(price.getPriceTaxExcl());
        vo.setPriceTaxIncl(price.getPriceTaxIncl());
        vo.setSource(price.getSource());
        return vo;
    }
    
    /**
     * 转换为响应 VO，并填充类别名称和机类树名称
     */
    private ResourceItemRespVO convertToRespVO(ResourceItemDO item) {
        ResourceItemRespVO vo = BeanUtils.toBean(item, ResourceItemRespVO.class);
        
        // 将英文类型转换为中文显示
        if (item.getType() != null) {
            vo.setType(convertTypeToDisplay(item.getType()));
        }
        
        // 填充类别名称
        if (item.getCategoryId() != null) {
            ResourceCategoryDO category = categoryMapper.selectById(item.getCategoryId());
            if (category != null) {
                vo.setCategoryName(category.getName());
            }
        }
        
        // 填充工料机机类树名称
        if (item.getCategoryTreeId() != null) {
            // TODO: 需要注入 ResourceCategoryTreeMapper
            // ResourceCategoryTreeDO categoryTree = categoryTreeMapper.selectById(item.getCategoryTreeId());
            // if (categoryTree != null) {
            //     vo.setCategoryTreeName(categoryTree.getName());
            // }
        }
        
        // 如果是复合工料机（is_merged = 1），计算并填充虚拟字段
        if (item.getIsMerged() != null && item.getIsMerged() == 1) {
            calculateMergedTotalSums(item.getId(), vo);
        }
        
        return vo;
    }
    
    /**
     * 将英文类型转换为中文显示
     */
    private String convertTypeToDisplay(String type) {
        if (type == null) return null;
        switch (type) {
            case "labor": return "人";
            case "material": return "材";
            case "machine": return "机";
            default: return type; // 已经是中文或其他格式，直接返回
        }
    }

    /**
     * 计算复合工料机的合价总和
     * 
     * @param mergedId 复合工料机ID
     * @param vo 响应VO
     */
    private void calculateMergedTotalSums(Long mergedId, ResourceItemRespVO vo) {
        // 1. 查询所有子工料机数据
        List<java.util.Map<String, Object>> children = resourceItemMapper.selectMergedChildrenForSum(mergedId);
        if (children == null || children.isEmpty()) {
            vo.setTaxExclBaseTotalSum(BigDecimal.ZERO);
            vo.setTaxInclBaseTotalSum(BigDecimal.ZERO);
            vo.setTaxExclCompileTotalSum(BigDecimal.ZERO);
            vo.setTaxInclCompileTotalSum(BigDecimal.ZERO);
            return;
        }
        
        // 2. 初始化总和
        BigDecimal taxExclBaseTotalSum = BigDecimal.ZERO;
        BigDecimal taxInclBaseTotalSum = BigDecimal.ZERO;
        BigDecimal taxExclCompileTotalSum = BigDecimal.ZERO;
        BigDecimal taxInclCompileTotalSum = BigDecimal.ZERO;
        
        // 3. 按类别分组，用于计算基数公式
        java.util.Map<Long, CategoryPriceSum> categoryPriceMap = new java.util.HashMap<>();
        
        // 4. 第一次遍历：处理所有普通工料机，构建 categoryPriceMap
        for (java.util.Map<String, Object> child : children) {
            String unit = (String) child.get("unit");
            boolean isPercentUnit = "%".equals(unit);
            
            if (!isPercentUnit) {
                // 普通工料机，直接计算
                BigDecimal quotaConsumption = (BigDecimal) child.get("quota_consumption");
                if (quotaConsumption == null) {
                    quotaConsumption = BigDecimal.ZERO;
                }
                
                // 直接使用消耗量作为因子（与工作台保持一致）
                BigDecimal factor = quotaConsumption;
                
                BigDecimal taxExclBasePrice = (BigDecimal) child.get("tax_excl_base_price");
                BigDecimal taxInclBasePrice = (BigDecimal) child.get("tax_incl_base_price");
                BigDecimal taxExclCompilePrice = (BigDecimal) child.get("tax_excl_compile_price");
                BigDecimal taxInclCompilePrice = (BigDecimal) child.get("tax_incl_compile_price");
                
                BigDecimal taxExclBaseTotal = multiplyOrZero(taxExclBasePrice, factor);
                BigDecimal taxInclBaseTotal = multiplyOrZero(taxInclBasePrice, factor);
                BigDecimal taxExclCompileTotal = multiplyOrZero(taxExclCompilePrice, factor);
                BigDecimal taxInclCompileTotal = multiplyOrZero(taxInclCompilePrice, factor);
                
                taxExclBaseTotalSum = taxExclBaseTotalSum.add(taxExclBaseTotal);
                taxInclBaseTotalSum = taxInclBaseTotalSum.add(taxInclBaseTotal);
                taxExclCompileTotalSum = taxExclCompileTotalSum.add(taxExclCompileTotal);
                taxInclCompileTotalSum = taxInclCompileTotalSum.add(taxInclCompileTotal);
                
                // 记录类别价格（用于计算基数）
                Long categoryId = (Long) child.get("category_id");
                if (categoryId != null) {
                    CategoryPriceSum categorySum = categoryPriceMap.computeIfAbsent(categoryId, k -> new CategoryPriceSum());
                    categorySum.taxExclBasePrice = categorySum.taxExclBasePrice.add(taxExclBaseTotal);
                    categorySum.taxInclBasePrice = categorySum.taxInclBasePrice.add(taxInclBaseTotal);
                    categorySum.taxExclCompilePrice = categorySum.taxExclCompilePrice.add(taxExclCompileTotal);
                    categorySum.taxInclCompilePrice = categorySum.taxInclCompilePrice.add(taxInclCompileTotal);
                }
            }
        }
        
        // 5. 第二次遍历：处理所有单位为 % 的工料机
        for (java.util.Map<String, Object> child : children) {
            String unit = (String) child.get("unit");
            boolean isPercentUnit = "%".equals(unit);
            
            if (isPercentUnit) {
                // 单位为 %，需要根据计算基数计算
                BigDecimal quotaConsumption = (BigDecimal) child.get("quota_consumption");
                if (quotaConsumption == null) {
                    quotaConsumption = BigDecimal.ZERO;
                }
                
                // 直接使用消耗量作为因子（与工作台保持一致）
                BigDecimal factor = quotaConsumption;
                
                Object calcBaseObj = child.get("calc_base");
                java.util.Map<String, Object> calcBase = null;
                
                // 处理 calc_base 可能是 String 或 Map 的情况
                if (calcBaseObj instanceof java.util.Map) {
                    @SuppressWarnings("unchecked")
                    java.util.Map<String, Object> tempMap = (java.util.Map<String, Object>) calcBaseObj;
                    calcBase = tempMap;
                } else if (calcBaseObj instanceof String) {
                    // 如果是 String，尝试解析为 JSON
                    try {
                        @SuppressWarnings("unchecked")
                        java.util.Map<String, Object> tempMap = new com.fasterxml.jackson.databind.ObjectMapper()
                                .readValue((String) calcBaseObj, java.util.Map.class);
                        calcBase = tempMap;
                    } catch (Exception e) {
                        log.error("[calculateMergedTotalSums] 解析 calc_base 失败: {}", e.getMessage(), e);
                        throw new RuntimeException("计算基数格式错误，无法解析: " + e.getMessage(), e);
                    }
                }
                
                if (calcBase != null && calcBase.containsKey("formula") && calcBase.containsKey("variables")) {
                    // 使用计算基数公式计算
                    CategoryPriceSum priceSum = calculateWithCalcBase(calcBase, categoryPriceMap, factor);
                    
                    taxExclBaseTotalSum = taxExclBaseTotalSum.add(priceSum.taxExclBasePrice);
                    taxInclBaseTotalSum = taxInclBaseTotalSum.add(priceSum.taxInclBasePrice);
                    taxExclCompileTotalSum = taxExclCompileTotalSum.add(priceSum.taxExclCompilePrice);
                    taxInclCompileTotalSum = taxInclCompileTotalSum.add(priceSum.taxInclCompilePrice);
                }
            }
        }
        
        // 6. 设置虚拟字段
        vo.setTaxExclBaseTotalSum(taxExclBaseTotalSum);
        vo.setTaxInclBaseTotalSum(taxInclBaseTotalSum);
        vo.setTaxExclCompileTotalSum(taxExclCompileTotalSum);
        vo.setTaxInclCompileTotalSum(taxInclCompileTotalSum);
    }

    /**
     * 使用计算基数公式计算价格
     */
    @SuppressWarnings("unchecked")
    private CategoryPriceSum calculateWithCalcBase(java.util.Map<String, Object> calcBase,
                                                   java.util.Map<Long, CategoryPriceSum> categoryPriceMap,
                                                   BigDecimal factor) {
        CategoryPriceSum result = new CategoryPriceSum();
        
        String formula = (String) calcBase.get("formula");
        java.util.Map<String, Object> variablesConfig = (java.util.Map<String, Object>) calcBase.get("variables");
        
        // 构建变量值映射表（4个价格字段分别计算）
        java.util.Map<String, BigDecimal> taxExclBaseVars = new java.util.HashMap<>();
        java.util.Map<String, BigDecimal> taxInclBaseVars = new java.util.HashMap<>();
        java.util.Map<String, BigDecimal> taxExclCompileVars = new java.util.HashMap<>();
        java.util.Map<String, BigDecimal> taxInclCompileVars = new java.util.HashMap<>();
        
        for (java.util.Map.Entry<String, Object> entry : variablesConfig.entrySet()) {
            String varName = entry.getKey();
            Long categoryId = Long.valueOf(entry.getValue().toString());
            
            // 从类别价格映射表中获取价格
            CategoryPriceSum categoryPrice = categoryPriceMap.get(categoryId);
            if (categoryPrice == null) {
                // 如果类别价格不存在，使用0
                categoryPrice = new CategoryPriceSum();
            }
            
            taxExclBaseVars.put(varName, categoryPrice.taxExclBasePrice);
            taxInclBaseVars.put(varName, categoryPrice.taxInclBasePrice);
            taxExclCompileVars.put(varName, categoryPrice.taxExclCompilePrice);
            taxInclCompileVars.put(varName, categoryPrice.taxInclCompilePrice);
        }
        
        // 计算公式结果
        try {
            BigDecimal taxExclBaseSum = com.yhy.module.core.util.FormulaEvaluator.evaluate(formula, taxExclBaseVars);
            BigDecimal taxInclBaseSum = com.yhy.module.core.util.FormulaEvaluator.evaluate(formula, taxInclBaseVars);
            BigDecimal taxExclCompileSum = com.yhy.module.core.util.FormulaEvaluator.evaluate(formula, taxExclCompileVars);
            BigDecimal taxInclCompileSum = com.yhy.module.core.util.FormulaEvaluator.evaluate(formula, taxInclCompileVars);
            
            // 乘以因子
            result.taxExclBasePrice = taxExclBaseSum.multiply(factor).setScale(4, java.math.RoundingMode.HALF_UP);
            result.taxInclBasePrice = taxInclBaseSum.multiply(factor).setScale(4, java.math.RoundingMode.HALF_UP);
            result.taxExclCompilePrice = taxExclCompileSum.multiply(factor).setScale(4, java.math.RoundingMode.HALF_UP);
            result.taxInclCompilePrice = taxInclCompileSum.multiply(factor).setScale(4, java.math.RoundingMode.HALF_UP);
        } catch (Exception e) {
            log.error("[calculateWithCalcBase] 公式计算失败，formula={}, error={}", formula, e.getMessage(), e);
            // 计算失败时返回0
            result = new CategoryPriceSum();
        }
        
        return result;
    }

    /**
     * 安全乘法（处理 null 值）
     */
    private BigDecimal multiplyOrZero(BigDecimal value, BigDecimal factor) {
        if (value == null) {
            return BigDecimal.ZERO;
        }
        return value.multiply(factor).setScale(4, java.math.RoundingMode.HALF_UP);
    }

    /**
     * 类别价格总和对象
     */
    private static class CategoryPriceSum {
        BigDecimal taxExclBasePrice = BigDecimal.ZERO;
        BigDecimal taxInclBasePrice = BigDecimal.ZERO;
        BigDecimal taxExclCompilePrice = BigDecimal.ZERO;
        BigDecimal taxInclCompilePrice = BigDecimal.ZERO;
    }

    @Override
    public List<ResourcePriceDO> listPrices(Long resourceId) {
        return resourcePriceMapper.selectList(new LambdaQueryWrapper<ResourcePriceDO>()
                .eq(ResourcePriceDO::getResourceId, resourceId)
                .orderByDesc(ResourcePriceDO::getId));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createPrice(ResourcePriceSaveReqVO reqVO) {
        ResourceItemDO item = resourceItemMapper.selectById(reqVO.getResourceId());
        if (item == null) {
            throw exception0(BAD_REQUEST.getCode(), "资源项不存在");
        }
        ResourcePriceDO price = buildPriceDO(reqVO);
        price.setTenantId(TenantContextHolder.getTenantId());
        resourcePriceMapper.insert(price);
        return price.getId();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updatePrice(ResourcePriceSaveReqVO reqVO) {
        ResourcePriceDO db = resourcePriceMapper.selectById(reqVO.getId());
        if (db == null) {
            throw exception0(BAD_REQUEST.getCode(), "价格不存在");
        }
        ResourcePriceDO price = buildPriceDO(reqVO);
        price.setTenantId(TenantContextHolder.getTenantId());
        resourcePriceMapper.updateById(price);
    }

    @Override
    public void deletePrice(Long id) {
        resourcePriceMapper.deleteById(id);
    }

    private ResourcePriceDO buildPriceDO(ResourcePriceSaveReqVO reqVO) {
        ResourcePriceDO price = BeanUtils.toBean(reqVO, ResourcePriceDO.class);
        price.setPricePeriod(String.format("[%s,%s)", RANGE_FMT.format(reqVO.getPeriodStart()), RANGE_FMT.format(reqVO.getPeriodEnd())));
        return price;
    }
    
    /**
     * 验证类别是否在机类树节点的允许范围内
     * 
     * @param categoryTreeId 机类树节点ID
     * @param categoryId 类别ID
     */
    private void validateCategoryInAllowedRange(Long categoryTreeId, Long categoryId) {
        Long tenantId = TenantContextHolder.getTenantId();
        
        // 查询该机类树节点是否关联了该类别
        Long count = categoryTreeMappingMapper.selectCount(
            new LambdaQueryWrapper<ResourceCategoryTreeMappingDO>()
                .eq(ResourceCategoryTreeMappingDO::getTenantId, tenantId)
                .eq(ResourceCategoryTreeMappingDO::getCategoryTreeId, categoryTreeId)
                .eq(ResourceCategoryTreeMappingDO::getCategoryId, categoryId)
        );
        
        if (count == 0) {
            throw exception0(BAD_REQUEST.getCode(), 
                String.format("类别ID %d 不在机类树节点 %d 的允许范围内，请先在机类树节点中添加该类别关联", 
                    categoryId, categoryTreeId));
        }
    }
    
    /**
     * 验证计算基数
     * 
     * @param reqVO 请求VO
     * @param categoryTreeId 机类树节点ID
     */
    private void validateCalcBase(ResourceItemSaveReqVO reqVO, Long categoryTreeId) {
        // 1. 如果单位不是 %，calcBase 必须为空
        if (!"%".equals(reqVO.getUnit())) {
            if (reqVO.getCalcBase() != null && !reqVO.getCalcBase().isEmpty()) {
                throw exception0(BAD_REQUEST.getCode(), "只有单位为 % 的工料机项才能设置计算基数");
            }
            return;
        }
        
        // 2. 如果单位是 %，验证 calcBase
        if (reqVO.getCalcBase() == null || reqVO.getCalcBase().isEmpty()) {
            // 允许为空，不强制要求
            return;
        }
        
        // 3. 解析 calcBase
        try {
            String formula = (String) reqVO.getCalcBase().get("formula");
            @SuppressWarnings("unchecked")
            java.util.Map<String, Object> variables = (java.util.Map<String, Object>) reqVO.getCalcBase().get("variables");
            
            // 4. 验证必填字段
            if (formula == null || formula.trim().isEmpty()) {
                throw exception0(BAD_REQUEST.getCode(), "计算基数的公式不能为空");
            }
            
            if (variables == null || variables.isEmpty()) {
                throw exception0(BAD_REQUEST.getCode(), "计算基数的变量映射不能为空");
            }
            
            // 5. 获取允许的类别列表
            List<Long> allowedCategoryIds = getAllowedCategoryIds(categoryTreeId);
            if (allowedCategoryIds.isEmpty()) {
                throw exception0(BAD_REQUEST.getCode(), "当前机类树节点未配置任何类别，无法设置计算基数");
            }
            
            // 6. 验证公式中的变量是否都在允许的类别列表中
            for (java.util.Map.Entry<String, Object> entry : variables.entrySet()) {
                String varName = entry.getKey();
                Long categoryId = null;
                
                if (entry.getValue() instanceof Integer) {
                    categoryId = ((Integer) entry.getValue()).longValue();
                } else if (entry.getValue() instanceof Long) {
                    categoryId = (Long) entry.getValue();
                } else {
                    throw exception0(BAD_REQUEST.getCode(), 
                        String.format("变量 %s 的类别ID格式不正确，必须是整数", varName));
                }
                
                if (!allowedCategoryIds.contains(categoryId)) {
                    throw exception0(BAD_REQUEST.getCode(), 
                        String.format("变量 %s 的类别ID %d 不在允许的类别范围内", varName, categoryId));
                }
                
                // 验证变量名在公式中是否使用
                if (!formula.contains(varName)) {
                    throw exception0(BAD_REQUEST.getCode(), 
                        String.format("变量 %s 在公式中未使用", varName));
                }
            }
            
            // 7. 验证公式语法
            validateFormulaSyntax(formula, variables.keySet());
            
        } catch (ClassCastException e) {
            throw exception0(BAD_REQUEST.getCode(), "计算基数格式不正确：" + e.getMessage());
        }
    }
    
    /**
     * 获取允许的类别ID列表
     * 
     * @param categoryTreeId 机类树节点ID
     * @return 允许的类别ID列表
     */
    private List<Long> getAllowedCategoryIds(Long categoryTreeId) {
        Long tenantId = TenantContextHolder.getTenantId();
        
        List<ResourceCategoryTreeMappingDO> mappings = categoryTreeMappingMapper.selectList(
            new LambdaQueryWrapper<ResourceCategoryTreeMappingDO>()
                .eq(ResourceCategoryTreeMappingDO::getTenantId, tenantId)
                .eq(ResourceCategoryTreeMappingDO::getCategoryTreeId, categoryTreeId)
        );
        
        return mappings.stream()
            .map(ResourceCategoryTreeMappingDO::getCategoryId)
            .collect(java.util.stream.Collectors.toList());
    }
    
    /**
     * 验证公式语法
     * 
     * @param formula 公式
     * @param variableNames 变量名集合
     */
    private void validateFormulaSyntax(String formula, java.util.Set<String> variableNames) {
        // 1. 检查是否包含非法字符
        // 允许：数字、小数点、运算符(+-*/)、括号、空格、中文（变量名）
        if (!formula.matches("[0-9+\\-*/().\\s\\u4e00-\\u9fa5]+")) {
            throw exception0(BAD_REQUEST.getCode(), "公式包含非法字符，只允许数字、运算符(+-*/)、括号和变量名");
        }
        
        // 2. 检查括号是否匹配
        int count = 0;
        for (char c : formula.toCharArray()) {
            if (c == '(') count++;
            if (c == ')') count--;
            if (count < 0) {
                throw exception0(BAD_REQUEST.getCode(), "公式中的括号不匹配：右括号多于左括号");
            }
        }
        if (count != 0) {
            throw exception0(BAD_REQUEST.getCode(), "公式中的括号不匹配：左括号多于右括号");
        }
        
        // 3. 检查公式不能以运算符结尾（除了括号）
        String trimmed = formula.trim();
        if (trimmed.isEmpty()) {
            throw exception0(BAD_REQUEST.getCode(), "公式不能为空");
        }
        
        char lastChar = trimmed.charAt(trimmed.length() - 1);
        if (lastChar == '+' || lastChar == '-' || lastChar == '*' || lastChar == '/') {
            throw exception0(BAD_REQUEST.getCode(), "公式不能以运算符结尾");
        }
        
        // 4. 检查公式不能以运算符开头（除了负号和括号）
        char firstChar = trimmed.charAt(0);
        if (firstChar == '+' || firstChar == '*' || firstChar == '/') {
            throw exception0(BAD_REQUEST.getCode(), "公式不能以运算符 " + firstChar + " 开头");
        }
        
        // 5. 检查是否有连续的运算符（除了负号）
        for (int i = 0; i < trimmed.length() - 1; i++) {
            char c1 = trimmed.charAt(i);
            char c2 = trimmed.charAt(i + 1);
            if (isOperator(c1) && isOperator(c2)) {
                // 允许 +- 或 -- 或 *- 或 /- （负号）
                if (c2 != '-') {
                    throw exception0(BAD_REQUEST.getCode(), 
                        String.format("公式中存在连续的运算符：%c%c", c1, c2));
                }
            }
        }
        
        // 6. 提取公式中的所有中文变量，检查是否都已定义
        java.util.Set<String> formulaVariables = extractChineseVariables(formula);
        for (String formulaVar : formulaVariables) {
            if (!variableNames.contains(formulaVar)) {
                throw exception0(BAD_REQUEST.getCode(), 
                    String.format("公式中的变量 '%s' 未定义，请先在变量映射中添加", formulaVar));
            }
        }
        
        // 7. 检查所有定义的变量是否都在公式中使用
        for (String varName : variableNames) {
            if (!formula.contains(varName)) {
                throw exception0(BAD_REQUEST.getCode(), 
                    String.format("变量 %s 在公式中未使用", varName));
            }
        }
    }
    
    /**
     * 提取公式中的所有中文变量
     * 
     * @param formula 公式
     * @return 中文变量集合
     */
    private java.util.Set<String> extractChineseVariables(String formula) {
        java.util.Set<String> variables = new java.util.HashSet<>();
        StringBuilder currentVar = new StringBuilder();
        
        for (int i = 0; i < formula.length(); i++) {
            char c = formula.charAt(i);
            
            // 如果是中文字符，累积到当前变量
            if (c >= '\u4e00' && c <= '\u9fa5') {
                currentVar.append(c);
            } else {
                // 遇到非中文字符，如果当前变量不为空，则保存
                if (currentVar.length() > 0) {
                    variables.add(currentVar.toString());
                    currentVar = new StringBuilder();
                }
            }
        }
        
        // 处理最后一个变量
        if (currentVar.length() > 0) {
            variables.add(currentVar.toString());
        }
        
        return variables;
    }
    
    /**
     * 判断字符是否为运算符
     */
    private boolean isOperator(char c) {
        return c == '+' || c == '-' || c == '*' || c == '/';
    }

    @Override
    public ResourceItemRespVO getItemByCode(String code) {
        if (code == null || code.trim().isEmpty()) {
            return null;
        }
        ResourceItemDO resourceItem = resourceItemMapper.selectByCode(code);
        if (resourceItem == null) {
            return null;
        }
        return BeanUtils.toBean(resourceItem, ResourceItemRespVO.class);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void swapSortOrder(Long nodeId1, Long nodeId2) {
        ResourceItemDO item1 = resourceItemMapper.selectById(nodeId1);
        ResourceItemDO item2 = resourceItemMapper.selectById(nodeId2);
        if (item1 == null || item2 == null) {
            throw exception0(BAD_REQUEST.getCode(), "工料机项不存在");
        }
        // 交换排序值
        Integer tempSort = item1.getSortOrder();
        item1.setSortOrder(item2.getSortOrder());
        item2.setSortOrder(tempSort);
        resourceItemMapper.updateById(item1);
        resourceItemMapper.updateById(item2);
    }
}
