package cn.iocoder.yudao.module.system.service.biz;

import cn.hutool.core.collection.CollectionUtil;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.system.controller.admin.biz.vo.materialcategory.MaterialCategorySaveReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.biz.MaterialCategoryDO;
import cn.iocoder.yudao.module.system.dal.dataobject.biz.MaterialInfoDO;
import cn.iocoder.yudao.module.system.dal.mysql.biz.MaterialCategoryMapper;
import cn.iocoder.yudao.module.system.dal.mysql.biz.MaterialInfoMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;

import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.COMMON_EXIST_ERROR;
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.COMMON_NOT_FOUND;

@Service
@Validated
@Slf4j
public class MaterialCategoryServiceImpl implements MaterialCategoryService {

    @Resource
    private MaterialCategoryMapper materialCategoryMapper;

    @Resource
    private MaterialInfoMapper materialInfoMapper;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long create(MaterialCategorySaveReqVO createReqVO) {
        // 顶级节点兼容：null 统一存 0
        if (createReqVO.getSuperior() == null) {
            createReqVO.setSuperior(0L);
        }
        // 唯一校验（同租户下编号不重复）
        MaterialCategoryDO exists = materialCategoryMapper.selectByTenantAndNumber(createReqVO.getTenantId(), createReqVO.getCategoryNumber());
        if (exists != null) {
            throw exception(COMMON_EXIST_ERROR);
        }

        MaterialCategoryDO category = BeanUtils.toBean(createReqVO, MaterialCategoryDO.class);
        materialCategoryMapper.insert(category);
        return category.getId();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void update(MaterialCategorySaveReqVO updateReqVO) {
        validateExists(updateReqVO.getId());
        if (updateReqVO.getSuperior() == null) {
            updateReqVO.setSuperior(0L);
        }
        // 编号唯一校验（排除自己）
        MaterialCategoryDO exists = materialCategoryMapper.selectByTenantAndNumber(updateReqVO.getTenantId(), updateReqVO.getCategoryNumber());
        if (exists != null && !exists.getId().equals(updateReqVO.getId())) {
            throw exception(COMMON_EXIST_ERROR);
        }

        MaterialCategoryDO updateObj = BeanUtils.toBean(updateReqVO, MaterialCategoryDO.class);
        materialCategoryMapper.updateById(updateObj);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void delete(Long id) {
        MaterialCategoryDO root = materialCategoryMapper.selectById(id);
        if (root == null) {
            throw exception(COMMON_NOT_FOUND);
        }

        // 1) 查出同租户下所有类别，递归拿到要删除的所有 categoryId（包含自己+所有子级）
        List<MaterialCategoryDO> all = materialCategoryMapper.selectList(new LambdaQueryWrapperX<MaterialCategoryDO>()
                .eq(MaterialCategoryDO::getTenantId, root.getTenantId()));
        List<Long> deleteCategoryIds = collectSubtreeIds(id, all);

        if (deleteCategoryIds.isEmpty()) {
            throw exception(COMMON_NOT_FOUND);
        }

        // 2) 删除该子树下的所有物资信息（直接条件删除，避免先查再删的性能损耗）
        materialInfoMapper.delete(new LambdaQueryWrapperX<MaterialInfoDO>()
                .in(MaterialInfoDO::getCategoryId, deleteCategoryIds));

        // 3) 删除类别子树
        materialCategoryMapper.deleteBatchIds(deleteCategoryIds);
    }

    @Override
    public MaterialCategoryDO get(Long id) {
        return materialCategoryMapper.selectById(id);
    }

    @Override
    public List<MaterialCategoryDO> getList(Long[] tenantIds) {
        return materialCategoryMapper.selectListByTenantIds(tenantIds);
    }

    @Override
    public List<MaterialCategoryDO> getTree(Long[] tenantIds) {
        // 顶级节点 superior 约定：null 或 0 都可能。这里兼容两种。

        List<MaterialCategoryDO> topNullList = materialCategoryMapper.selectList(
                new LambdaQueryWrapperX<MaterialCategoryDO>()
                        .isNull(MaterialCategoryDO::getSuperior)
                        .in(MaterialCategoryDO::getTenantId, (Object[]) tenantIds)
        );

        List<MaterialCategoryDO> topZeroList = materialCategoryMapper.selectList(
                new LambdaQueryWrapperX<MaterialCategoryDO>()
                        .eq(MaterialCategoryDO::getSuperior, 0L)
                        .in(MaterialCategoryDO::getTenantId, (Object[]) tenantIds)
        );

        Map<Long, MaterialCategoryDO> map = new LinkedHashMap<>();
        if (topNullList != null) {
            topNullList.forEach(x -> map.put(x.getId(), x));
        }
        if (topZeroList != null) {
            topZeroList.forEach(x -> map.putIfAbsent(x.getId(), x));
        }
        List<MaterialCategoryDO> parentList = new ArrayList<>(map.values());

        // 一次性查出所有节点，避免 N+1
        List<MaterialCategoryDO> all = materialCategoryMapper.selectList(
                new LambdaQueryWrapperX<MaterialCategoryDO>()
                        .in(MaterialCategoryDO::getTenantId, (Object[]) tenantIds)
        );

        return parentList.stream()
                .map(root -> buildTree(root, all))
                .sorted(Comparator.comparing(MaterialCategoryDO::getId))
                .collect(Collectors.toList());
    }

    private MaterialCategoryDO buildTree(MaterialCategoryDO root, List<MaterialCategoryDO> all) {
        root.setChildrenList(getAllSubNodes(root, all));
        return root;
    }

    private List<MaterialCategoryDO> getAllSubNodes(MaterialCategoryDO rootNode, List<MaterialCategoryDO> list) {
        if (CollectionUtil.isEmpty(list)) {
            return null;
        }
        return list.stream()
                .filter(item -> item.getSuperior() != null && item.getSuperior().equals(rootNode.getId()))
                .peek(node -> node.setChildrenList(getAllSubNodes(node, list)))
                .sorted(Comparator.comparing(MaterialCategoryDO::getId))
                .collect(Collectors.toList());
    }

    private List<Long> collectSubtreeIds(Long rootId, List<MaterialCategoryDO> all) {
        if (all == null || all.isEmpty()) {
            return Collections.singletonList(rootId);
        }
        Map<Long, List<MaterialCategoryDO>> childrenMap = all.stream()
                .filter(c -> c.getSuperior() != null)
                .collect(Collectors.groupingBy(MaterialCategoryDO::getSuperior));

        List<Long> result = new ArrayList<>();
        Deque<Long> stack = new ArrayDeque<>();
        stack.push(rootId);
        while (!stack.isEmpty()) {
            Long cur = stack.pop();
            result.add(cur);
            List<MaterialCategoryDO> children = childrenMap.get(cur);
            if (children == null) {
                continue;
            }
            for (MaterialCategoryDO child : children) {
                if (child.getId() != null) {
                    stack.push(child.getId());
                }
            }
        }
        return result;
    }

    private void validateExists(Long id) {
        if (id == null || materialCategoryMapper.selectById(id) == null) {
            throw exception(COMMON_NOT_FOUND);
        }
    }
}
