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

import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.yhy.module.core.enums.ErrorCodeConstants.QUOTA_CATALOG_ITEM_NOT_EXISTS;
import static com.yhy.module.core.enums.ErrorCodeConstants.QUOTA_FEE_CALC_BASE_FORMULA_BRACKET_MISMATCH;
import static com.yhy.module.core.enums.ErrorCodeConstants.QUOTA_FEE_CALC_BASE_FORMULA_EMPTY;
import static com.yhy.module.core.enums.ErrorCodeConstants.QUOTA_FEE_CALC_BASE_FORMULA_INVALID_CHAR;
import static com.yhy.module.core.enums.ErrorCodeConstants.QUOTA_FEE_CALC_BASE_INVALID_PRICE_CODE;
import static com.yhy.module.core.enums.ErrorCodeConstants.QUOTA_FEE_CALC_BASE_VARIABLES_EMPTY;
import static com.yhy.module.core.enums.ErrorCodeConstants.QUOTA_FEE_ITEM_CATALOG_NOT_RATE_MODE;
import static com.yhy.module.core.enums.ErrorCodeConstants.QUOTA_FEE_ITEM_CUSTOM_CODE_DUPLICATE;
import static com.yhy.module.core.enums.ErrorCodeConstants.QUOTA_FEE_ITEM_NOT_EXISTS;
import static com.yhy.module.core.enums.ErrorCodeConstants.QUOTA_FEE_ITEM_NOT_SAME_CATALOG;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import com.yhy.module.core.controller.admin.quota.vo.QuotaFeeItemSaveReqVO;
import com.yhy.module.core.controller.admin.quota.vo.QuotaFeeItemWithRateRespVO;
import com.yhy.module.core.dal.dataobject.quota.QuotaCatalogItemDO;
import com.yhy.module.core.dal.dataobject.quota.QuotaFeeItemDO;
import com.yhy.module.core.dal.dataobject.quota.QuotaRateItemDO;
import com.yhy.module.core.dal.mysql.quota.QuotaCatalogItemMapper;
import com.yhy.module.core.dal.mysql.quota.QuotaFeeItemMapper;
import com.yhy.module.core.dal.mysql.quota.QuotaRateItemMapper;
import com.yhy.module.core.service.quota.QuotaFeeItemService;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

/**
 * 定额取费项 Service 实现类
 */
@Service
@Validated
@Slf4j
public class QuotaFeeItemServiceImpl implements QuotaFeeItemService {

    @Resource
    private QuotaFeeItemMapper quotaFeeItemMapper;

    @Resource
    private QuotaCatalogItemMapper quotaCatalogItemMapper;

    @Resource
    private QuotaRateItemMapper quotaRateItemMapper;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createFeeItem(QuotaFeeItemSaveReqVO createReqVO) {
        // 1. 验证模式节点
        validateRateModeNode(createReqVO.getCatalogItemId());

        // 2. 验证费率项ID（如果提供）
        if (createReqVO.getRateItemId() != null) {
            validateRateItem(createReqVO.getCatalogItemId(), createReqVO.getRateItemId());
        }

        // 3. 验证计算基数
        if (createReqVO.getCalcBase() != null) {
            validateCalcBase(createReqVO.getCalcBase());
        }

        // 4. 验证自定义序号唯一性
        if (StrUtil.isNotBlank(createReqVO.getCustomCode())) {
            validateCustomCodeUnique(null, createReqVO.getCatalogItemId(), createReqVO.getCustomCode());
        }

        // 5. 设置排序值
        if (createReqVO.getSortOrder() == null) {
            Integer maxSortOrder = quotaFeeItemMapper.selectMaxSortOrder(createReqVO.getCatalogItemId());
            createReqVO.setSortOrder(maxSortOrder + 1);
        }

        // 6. 插入数据
        QuotaFeeItemDO feeItem = BeanUtils.toBean(createReqVO, QuotaFeeItemDO.class);
        quotaFeeItemMapper.insert(feeItem);

        return feeItem.getId();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateFeeItem(QuotaFeeItemSaveReqVO updateReqVO) {
        // 1. 验证存在
        validateFeeItemExists(updateReqVO.getId());

        // 2. 验证模式节点
        validateRateModeNode(updateReqVO.getCatalogItemId());

        // 3. 验证费率项ID（如果提供）
        if (updateReqVO.getRateItemId() != null) {
            validateRateItem(updateReqVO.getCatalogItemId(), updateReqVO.getRateItemId());
        }

        // 4. 验证计算基数
        if (updateReqVO.getCalcBase() != null) {
            validateCalcBase(updateReqVO.getCalcBase());
        }

        // 5. 验证自定义序号唯一性
        if (StrUtil.isNotBlank(updateReqVO.getCustomCode())) {
            validateCustomCodeUnique(updateReqVO.getId(), updateReqVO.getCatalogItemId(), updateReqVO.getCustomCode());
        }

        // 6. 更新数据
        QuotaFeeItemDO updateObj = BeanUtils.toBean(updateReqVO, QuotaFeeItemDO.class);
        quotaFeeItemMapper.updateById(updateObj);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteFeeItem(Long id) {
        // 1. 验证存在
        validateFeeItemExists(id);

        // 2. 删除
        quotaFeeItemMapper.deleteById(id);
    }

    @Override
    public QuotaFeeItemDO getFeeItem(Long id) {
        return quotaFeeItemMapper.selectById(id);
    }

    @Override
    public List<QuotaFeeItemDO> getFeeItemList(Long catalogItemId) {
        return quotaFeeItemMapper.selectListByCatalogItemId(catalogItemId);
    }

    @Override
    public List<QuotaFeeItemWithRateRespVO> getFeeItemWithRateList(Long catalogItemId) {
        // 1. 验证模式节点
        validateRateModeNode(catalogItemId);

        // 2. 查询所有费率项节点（不限制层级）
        List<QuotaRateItemDO> rateItems = quotaRateItemMapper.selectListByCatalogItemId(catalogItemId);

        // 3. 查询取费项列表
        List<QuotaFeeItemDO> feeItems = quotaFeeItemMapper.selectListByCatalogItemId(catalogItemId);

        // 4. 构建取费项Map（优先使用 rate_item_id，其次使用 rateCode）
        Map<Long, QuotaFeeItemDO> feeItemByRateItemIdMap = feeItems.stream()
                .filter(item -> item.getRateItemId() != null)
                .collect(Collectors.toMap(
                        QuotaFeeItemDO::getRateItemId,
                        item -> item,
                        (existing, replacement) -> existing
                ));

        Map<String, QuotaFeeItemDO> feeItemByRateCodeMap = feeItems.stream()
                .filter(item -> StrUtil.isNotBlank(item.getRateCode()) && item.getRateItemId() == null)
                .collect(Collectors.toMap(
                        QuotaFeeItemDO::getRateCode,
                        item -> item,
                        (existing, replacement) -> existing
                ));

        // 5. 转换为 VO 并合并取费项数据
        List<QuotaFeeItemWithRateRespVO> allVos = new ArrayList<>();
        for (QuotaRateItemDO rateItem : rateItems) {
            QuotaFeeItemWithRateRespVO vo = new QuotaFeeItemWithRateRespVO();

            // 设置费率项信息
            vo.setRateItemId(rateItem.getId());
            vo.setRateItemName(rateItem.getName());
            vo.setRateItemCustomCode(rateItem.getCustomCode());
            vo.setRateCode(rateItem.getRateCode());
            vo.setParentId(rateItem.getParentId());
            vo.setNodeType(rateItem.getNodeType());
            vo.setCatalogItemId(catalogItemId);

            // 查找对应的取费项（优先使用 rate_item_id，其次使用 rateCode）
            QuotaFeeItemDO feeItem = feeItemByRateItemIdMap.get(rateItem.getId());
            if (feeItem == null && StrUtil.isNotBlank(rateItem.getRateCode())) {
                feeItem = feeItemByRateCodeMap.get(rateItem.getRateCode());
            }

            if (feeItem != null) {
                // 设置取费项信息
                vo.setFeeItemId(feeItem.getId());
                vo.setFeeItemName(feeItem.getName());
                vo.setFeeItemCustomCode(feeItem.getCustomCode());
                vo.setCalcBase(feeItem.getCalcBase());
                vo.setRatePercentage(feeItem.getRateCode());
                vo.setCode(feeItem.getCode());
                vo.setFeeCategory(feeItem.getFeeCategory());
                vo.setBaseDescription(feeItem.getBaseDescription());
                vo.setSortOrder(feeItem.getSortOrder());
                vo.setHidden(feeItem.getHidden());
                vo.setVariable(feeItem.getVariable());
                vo.setCreateTime(feeItem.getCreateTime());
                vo.setUpdateTime(feeItem.getUpdateTime());
                vo.setHasFeeItem(true);
            } else {
                vo.setHasFeeItem(false);
            }

            allVos.add(vo);
        }

        // 6. 构建树形结构
        return buildTree(allVos);
    }

    /**
     * 构建树形结构
     */
    private List<QuotaFeeItemWithRateRespVO> buildTree(List<QuotaFeeItemWithRateRespVO> allVos) {
        // 1. 构建 ID -> VO 的映射
        Map<Long, QuotaFeeItemWithRateRespVO> voMap = allVos.stream()
                .collect(Collectors.toMap(QuotaFeeItemWithRateRespVO::getRateItemId, vo -> vo));

        // 2. 构建树形结构
        List<QuotaFeeItemWithRateRespVO> rootNodes = new ArrayList<>();
        for (QuotaFeeItemWithRateRespVO vo : allVos) {
            if (vo.getParentId() == null) {
                // 根节点
                rootNodes.add(vo);
            } else {
                // 子节点，添加到父节点的 children 中
                QuotaFeeItemWithRateRespVO parent = voMap.get(vo.getParentId());
                if (parent != null) {
                    if (parent.getChildren() == null) {
                        parent.setChildren(new ArrayList<>());
                    }
                    parent.getChildren().add(vo);
                }
            }
        }

        // 3. 对每个节点的子节点按 sortOrder 排序（如果有取费项）
        sortTreeNodes(rootNodes);

        return rootNodes;
    }

    /**
     * 递归排序树节点
     */
    private void sortTreeNodes(List<QuotaFeeItemWithRateRespVO> nodes) {
        if (CollUtil.isEmpty(nodes)) {
            return;
        }

        // 不需要排序，保持费率项的原有顺序（已经按 sortOrder 排序）
        // 费率项在查询时已经按 sortOrder 排序，这里不再重新排序

        // 递归排序子节点
        for (QuotaFeeItemWithRateRespVO node : nodes) {
            if (CollUtil.isNotEmpty(node.getChildren())) {
                sortTreeNodes(node.getChildren());
            }
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void swapSort(Long nodeId1, Long nodeId2) {
        // 1. 查询两个节点（使用行锁）
        QuotaFeeItemDO node1 = quotaFeeItemMapper.selectById(nodeId1);
        QuotaFeeItemDO node2 = quotaFeeItemMapper.selectById(nodeId2);

        if (node1 == null || node2 == null) {
            throw exception(QUOTA_FEE_ITEM_NOT_EXISTS);
        }

        // 2. 验证是否在同一模式下
        if (!node1.getCatalogItemId().equals(node2.getCatalogItemId())) {
            throw exception(QUOTA_FEE_ITEM_NOT_SAME_CATALOG);
        }

        // 3. 交换排序值
        Integer tempSort = node1.getSortOrder();
        node1.setSortOrder(node2.getSortOrder());
        node2.setSortOrder(tempSort);

        // 4. 更新数据库
        quotaFeeItemMapper.updateById(node1);
        quotaFeeItemMapper.updateById(node2);
    }

    @Override
    public void validateFeeItemExists(Long id) {
        if (quotaFeeItemMapper.selectById(id) == null) {
            throw exception(QUOTA_FEE_ITEM_NOT_EXISTS);
        }
    }

    // ==================== 私有方法 ====================

    /**
     * 验证模式节点
     */
    private void validateRateModeNode(Long catalogItemId) {
        QuotaCatalogItemDO catalogItem = quotaCatalogItemMapper.selectById(catalogItemId);
        if (catalogItem == null) {
            throw exception(QUOTA_CATALOG_ITEM_NOT_EXISTS);
        }

        // 验证 node_type 必须是 rate_mode
        Map<String, Object> attributes = catalogItem.getAttributes();
        if (attributes == null || !"rate_mode".equals(attributes.get("node_type"))) {
            throw exception(QUOTA_FEE_ITEM_CATALOG_NOT_RATE_MODE);
        }
    }

    /**
     * 验证费率项
     */
    private void validateRateItem(Long catalogItemId, Long rateItemId) {
        QuotaRateItemDO rateItem = quotaRateItemMapper.selectById(rateItemId);
        if (rateItem == null) {
            throw exception(QUOTA_FEE_ITEM_NOT_EXISTS);
        }

        // 验证费率项必须属于同一模式节点
        if (!rateItem.getCatalogItemId().equals(catalogItemId)) {
            throw exception(QUOTA_FEE_ITEM_NOT_SAME_CATALOG);
        }

        // 注意：不再限制必须是一级节点，所有层级的费率项都可以关联取费项
    }

    /**
     * 验证计算基数
     */
    private void validateCalcBase(Map<String, Object> calcBase) {
        // 1. 验证必填字段
        String formula = (String) calcBase.get("formula");
        if (StrUtil.isBlank(formula)) {
            throw exception(QUOTA_FEE_CALC_BASE_FORMULA_EMPTY);
        }

        @SuppressWarnings("unchecked")
        Map<String, Object> variables = (Map<String, Object>) calcBase.get("variables");
        if (MapUtil.isEmpty(variables)) {
            throw exception(QUOTA_FEE_CALC_BASE_VARIABLES_EMPTY);
        }

        // 2. 验证价格代码必须是固定的4个值
        Set<String> allowedCodes = CollUtil.newHashSet(
                QuotaFeeItemDO.PRICE_CODE_TAX_EXCL_BASE,
                QuotaFeeItemDO.PRICE_CODE_TAX_INCL_BASE,
                QuotaFeeItemDO.PRICE_CODE_TAX_EXCL_COMPILE,
                QuotaFeeItemDO.PRICE_CODE_TAX_INCL_COMPILE
        );

        // 遍历 variables，验证每个变量的格式和价格字段类型
        for (Map.Entry<String, Object> entry : variables.entrySet()) {
            String varName = entry.getKey();
            Object varValue = entry.getValue();
            
            // 验证值必须是 Map 类型
            if (!(varValue instanceof Map)) {
                throw exception(QUOTA_FEE_CALC_BASE_INVALID_PRICE_CODE, "变量 " + varName + " 的值格式错误");
            }
            
            @SuppressWarnings("unchecked")
            Map<String, Object> varInfo = (Map<String, Object>) varValue;
            
            // 验证必须包含 categoryId 和 priceField
            if (!varInfo.containsKey("categoryId") || !varInfo.containsKey("priceField")) {
                throw exception(QUOTA_FEE_CALC_BASE_INVALID_PRICE_CODE, 
                    "变量 " + varName + " 缺少 categoryId 或 priceField 字段");
            }
            
            // 验证 priceField 必须是允许的价格代码
            String priceField = (String) varInfo.get("priceField");
            if (!allowedCodes.contains(priceField)) {
                throw exception(QUOTA_FEE_CALC_BASE_INVALID_PRICE_CODE, priceField);
            }
        }

        // 3. 验证公式语法
        validateFormulaSyntax(formula, variables.keySet());
    }

    /**
     * 验证公式语法
     */
    private void validateFormulaSyntax(String formula, Set<String> variableNames) {
        List<String> sortedVarNames = new ArrayList<>(variableNames);
        sortedVarNames.sort((a, b) -> Integer.compare(b.length(), a.length()));

        int index = 0;
        int parenDepth = 0;
        // Simple tokenizer/validator: enforce operator/operand order and parenthesis balance.
        boolean expectOperand = true;
        boolean hasToken = false;

        while (index < formula.length()) {
            char c = formula.charAt(index);
            if (Character.isWhitespace(c)) {
                index++;
                continue;
            }

            if (c == '(') {
                if (!expectOperand) {
                    throw exception(QUOTA_FEE_CALC_BASE_FORMULA_INVALID_CHAR, "缺少运算符（'(' 前）");
                }
                parenDepth++;
                index++;
                expectOperand = true;
                hasToken = true;
                continue;
            }

            if (c == ')') {
                if (expectOperand) {
                    throw exception(QUOTA_FEE_CALC_BASE_FORMULA_INVALID_CHAR, "缺少操作数（')' 前）");
                }
                parenDepth--;
                if (parenDepth < 0) {
                    throw exception(QUOTA_FEE_CALC_BASE_FORMULA_BRACKET_MISMATCH);
                }
                index++;
                expectOperand = false;
                hasToken = true;
                continue;
            }

            if (isOperator(c)) {
                if (expectOperand) {
                    if (c == '-') {
                        // Unary minus is allowed when an operand is expected.
                        index++;
                        hasToken = true;
                        continue;
                    }
                    throw exception(QUOTA_FEE_CALC_BASE_FORMULA_INVALID_CHAR, "运算符位置不合法: " + c);
                }
                expectOperand = true;
                index++;
                hasToken = true;
                continue;
            }

            if (isNumberStart(c)) {
                if (!expectOperand) {
                    throw exception(QUOTA_FEE_CALC_BASE_FORMULA_INVALID_CHAR, "数字前缺少运算符");
                }
                // Consume a numeric literal (integer or decimal).
                index = consumeNumber(formula, index);
                expectOperand = false;
                hasToken = true;
                continue;
            }

            String matchedVar = matchVariable(formula, index, sortedVarNames);
            if (matchedVar != null) {
                if (!expectOperand) {
                    throw exception(QUOTA_FEE_CALC_BASE_FORMULA_INVALID_CHAR, "变量前缺少运算符");
                }
                index += matchedVar.length();
                expectOperand = false;
                hasToken = true;
                continue;
            }

            throw exception(QUOTA_FEE_CALC_BASE_FORMULA_INVALID_CHAR, String.valueOf(c));
        }

        if (parenDepth != 0) {
            throw exception(QUOTA_FEE_CALC_BASE_FORMULA_BRACKET_MISMATCH);
        }
        if (!hasToken || expectOperand) {
            throw exception(QUOTA_FEE_CALC_BASE_FORMULA_INVALID_CHAR, "公式语法不正确");
        }
    }

    private boolean isOperator(char c) {
        return c == '+' || c == '-' || c == '*' || c == '/';
    }

    private boolean isNumberStart(char c) {
        return Character.isDigit(c) || c == '.';
    }

    private int consumeNumber(String formula, int start) {
        int index = start;
        boolean hasDigit = false;
        boolean hasDot = false;

        while (index < formula.length()) {
            char c = formula.charAt(index);
            if (Character.isDigit(c)) {
                hasDigit = true;
                index++;
                continue;
            }
            if (c == '.' && !hasDot) {
                hasDot = true;
                index++;
                continue;
            }
            break;
        }

        if (!hasDigit) {
            throw exception(QUOTA_FEE_CALC_BASE_FORMULA_INVALID_CHAR, "数字格式不正确");
        }

        return index;
    }

    private String matchVariable(String formula, int index, List<String> sortedVarNames) {
        for (String varName : sortedVarNames) {
            if (varName.isEmpty()) {
                continue;
            }
            if (formula.startsWith(varName, index)) {
                return varName;
            }
        }
        return null;
    }

    /**
     * 验证自定义序号唯一性
     */
    private void validateCustomCodeUnique(Long id, Long catalogItemId, String customCode) {
        QuotaFeeItemDO existingItem = quotaFeeItemMapper.selectByCustomCode(catalogItemId, customCode);
        if (existingItem != null && !existingItem.getId().equals(id)) {
            throw exception(QUOTA_FEE_ITEM_CUSTOM_CODE_DUPLICATE, customCode);
        }
    }
}

