package com.njcn.rdms.module.project.service.product;

import com.google.common.annotations.VisibleForTesting;
import com.njcn.rdms.framework.common.pojo.PageResult;
import com.njcn.rdms.framework.common.util.object.BeanUtils;
import com.njcn.rdms.framework.security.core.util.SecurityFrameworkUtils;
import com.njcn.rdms.module.project.controller.admin.product.vo.requirement.*;
import com.njcn.rdms.module.project.dal.dataobject.audit.BizAuditLogDO;
import com.njcn.rdms.module.project.dal.dataobject.product.ProductRequirementDO;
import com.njcn.rdms.module.project.dal.dataobject.product.ProductRequirementModuleDO;
import com.njcn.rdms.module.project.dal.dataobject.product.ProductRequirementStatusLogDO;
import com.njcn.rdms.module.project.dal.dataobject.status.ObjectStatusModelDO;
import com.njcn.rdms.module.project.dal.dataobject.status.ObjectStatusTransitionDO;
import com.njcn.rdms.module.project.dal.mysql.audit.BizAuditLogMapper;
import com.njcn.rdms.module.project.dal.mysql.product.ProductRequirementMapper;
import com.njcn.rdms.module.project.dal.mysql.product.ProductRequirementModuleMapper;
import com.njcn.rdms.module.project.dal.mysql.product.ProductRequirementStatusLogMapper;
import com.njcn.rdms.module.project.dal.mysql.status.ObjectStatusModelMapper;
import com.njcn.rdms.module.project.dal.mysql.status.ObjectStatusTransitionMapper;
import com.njcn.rdms.module.project.enums.ErrorCodeConstants;
import com.njcn.rdms.module.project.framework.security.annotation.CheckObjectPermission;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.util.*;
import java.util.stream.Collectors;

import static com.njcn.rdms.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.njcn.rdms.framework.common.exception.util.ServiceExceptionUtil.invalidParamException;

/**
 * 产品需求 Service 实现类
 */
@Service
public class ProductRequirementServiceImpl implements ProductRequirementService {

    // 对象类型常量
    private static final String REQUIREMENT_OBJECT_TYPE = "product_requirement";
    private static final String PRODUCT_OBJECT_TYPE = "product";

    // 需求状态常量
    private static final String STATUS_PENDING_CONFIRM = "pending_confirm";
    private static final String STATUS_PENDING_REVIEW = "pending_review";
    private static final String STATUS_PENDING_DISPATCH = "pending_dispatch";
    private static final String STATUS_IMPLEMENTING = "implementing";
    private static final String STATUS_ACCEPTED = "accepted";
    private static final String STATUS_CLOSED = "closed";
    private static final String STATUS_REJECTED = "rejected";
    private static final String STATUS_CANCELLED = "cancelled";

    // 终态状态集合
    private static final List<String> TERMINAL_STATUSES = List.of(STATUS_CLOSED, STATUS_REJECTED, STATUS_CANCELLED);
    // 子需求允许大需求关闭的状态集合
    private static final List<String> CHILD_ALLOW_CLOSE_STATUSES = List.of(STATUS_CLOSED, STATUS_CANCELLED, STATUS_REJECTED, STATUS_ACCEPTED);
    // 允许删除的状态集合（实施中之前的状态）
    private static final List<String> ALLOW_DELETE_STATUSES = List.of(STATUS_PENDING_CONFIRM, STATUS_PENDING_REVIEW, STATUS_PENDING_DISPATCH);

    // 权限常量
    private static final String PRODUCT_CREATE_PERMISSION = "project:product:create";
    private static final String PRODUCT_QUERY_PERMISSION = "project:product:query";
    private static final String PRODUCT_UPDATE_PERMISSION = "project:product:update";
    private static final String PRODUCT_STATUS_PERMISSION = "project:product:status";
    private static final String PRODUCT_DELETE_PERMISSION = "project:product:delete";
    private static final String PRODUCT_SPLIT_PERMISSION = "project:product:split";

    // 审计动作常量
    private static final String ACTION_CREATE = "create";
    private static final String ACTION_UPDATE = "update";
    private static final String ACTION_DELETE = "delete";
    private static final String ACTION_SPLIT = "split";
    private static final String ACTION_CLOSE = "close";
    private static final String BIZ_TYPE_REQUIREMENT = "product_requirement";

    @Resource
    private ProductRequirementMapper requirementMapper;
    @Resource
    private ProductRequirementModuleMapper moduleMapper;
    @Resource
    private ProductRequirementStatusLogMapper statusLogMapper;
    @Resource
    private BizAuditLogMapper bizAuditLogMapper;
    @Resource
    private ObjectStatusTransitionMapper statusTransitionMapper;
    @Resource
    private ObjectStatusModelMapper statusModelMapper;

    // ========== 需求增删改查 ==========

    @Override
    @Transactional(rollbackFor = Exception.class)
    @CheckObjectPermission(objectType = PRODUCT_OBJECT_TYPE, objectId = "#createReqVO.productId",
            permission = PRODUCT_CREATE_PERMISSION)
    public Long createRequirement(ProductRequirementSaveReqVO createReqVO) {
        // 当未选择模块时，自动归属到该产品的"全部需求"模块
        Long moduleId = resolveModuleId(createReqVO.getModuleId(), createReqVO.getProductId());
        // 校验模块是否属于当前产品
        validateModuleBelongsToProduct(moduleId, createReqVO.getProductId());

        ProductRequirementDO requirement = new ProductRequirementDO();
        requirement.setProductId(createReqVO.getProductId());
        requirement.setParentId(0L); // 新增需求默认是顶级需求
        requirement.setModuleId(moduleId);
        requirement.setReviewRequired(createReqVO.getReviewRequired());
        requirement.setTitle(createReqVO.getTitle().trim());
        requirement.setDescription(normalizeNullableText(createReqVO.getDescription()));
        requirement.setCategory(createReqVO.getCategory());
        requirement.setSourceType("manual"); // 手工新增默认来源类型
        requirement.setPriority(createReqVO.getPriority());
        // 根据是否需要评审确定初始状态
        String initialStatus = Objects.equals(createReqVO.getReviewRequired(), 1)
                ? STATUS_PENDING_REVIEW : STATUS_PENDING_DISPATCH;
        requirement.setStatusCode(initialStatus);
        requirement.setProposerId(createReqVO.getProposerId());
        requirement.setCurrentHandlerUserId(createReqVO.getCurrentHandlerUserId());
        requirement.setImplementProjectId(createReqVO.getImplementProjectId());
        requirement.setCompletionDate(createReqVO.getCompletionDate());
        requirement.setSort(createReqVO.getSort() != null ? createReqVO.getSort() : 0);
        requirementMapper.insert(requirement);

        // 写入业务审计日志
        writeBizAuditLog(requirement, ACTION_CREATE, null, initialStatus, null, null);
        return requirement.getId();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @CheckObjectPermission(objectType = PRODUCT_OBJECT_TYPE, objectId = "#updateReqVO.productId",
            permission = PRODUCT_UPDATE_PERMISSION)
    public void updateRequirement(ProductRequirementUpdateReqVO updateReqVO) {
        ProductRequirementDO requirement = validateRequirementExists(updateReqVO.getId());
        // 校验终态不允许编辑
        validateRequirementEditable(requirement);
        // 当未选择模块时，自动归属到该产品的"全部需求"模块
        Long moduleId = resolveModuleId(updateReqVO.getModuleId(), updateReqVO.getProductId());
        // 校验模块是否属于当前产品
        validateModuleBelongsToProduct(moduleId, updateReqVO.getProductId());

        String fromStatus = requirement.getStatusCode();
        requirement.setModuleId(moduleId);
        requirement.setReviewRequired(updateReqVO.getReviewRequired());
        requirement.setTitle(updateReqVO.getTitle().trim());
        requirement.setDescription(normalizeNullableText(updateReqVO.getDescription()));
        requirement.setCategory(updateReqVO.getCategory());
        requirement.setPriority(updateReqVO.getPriority());
        requirement.setProposerId(updateReqVO.getProposerId());
        requirement.setCurrentHandlerUserId(updateReqVO.getCurrentHandlerUserId());
        requirement.setImplementProjectId(updateReqVO.getImplementProjectId());
        requirement.setCompletionDate(updateReqVO.getCompletionDate());
        requirement.setSort(updateReqVO.getSort() != null ? updateReqVO.getSort() : 0);
        requirementMapper.updateById(requirement);

        writeBizAuditLog(requirement, ACTION_UPDATE, fromStatus, requirement.getStatusCode(), null, null);
    }

    @Override
    @CheckObjectPermission(objectType = PRODUCT_OBJECT_TYPE, objectId = "#productId",
            permission = PRODUCT_QUERY_PERMISSION)
    public ProductRequirementRespVO getRequirement(Long id, Long productId) {
        ProductRequirementDO requirement = validateRequirementExists(id);
        return buildRequirementRespVO(requirement);
    }

    @Override
    @CheckObjectPermission(objectType = PRODUCT_OBJECT_TYPE, objectId = "#pageReqVO.productId",
            permission = PRODUCT_QUERY_PERMISSION)
    public PageResult<ProductRequirementRespVO> getRequirementPage(ProductRequirementPageReqVO pageReqVO) {
        // 处理模块ID条件：支持递归查询子模块
        if (pageReqVO.getModuleId() != null) {
            if (isAllRequirementsModule(pageReqVO.getModuleId())) {
                // "全部需求"模块，忽略模块ID条件，查询该产品下所有需求
                pageReqVO.setModuleId(null);
            } else {
                // 非"全部需求"模块，获取该模块及其所有子模块的ID列表
                List<Long> moduleIds = getAllModuleIdsWithChildren(pageReqVO.getModuleId(), pageReqVO.getProductId());
                pageReqVO.setModuleIds(moduleIds);
                pageReqVO.setModuleId(null);
            }
        }
        PageResult<ProductRequirementDO> pageResult = requirementMapper.selectPage(pageReqVO);
        List<ProductRequirementRespVO> list = pageResult.getList().stream()
                .map(this::buildRequirementRespVO)
                .collect(Collectors.toList());
        return new PageResult<>(list, pageResult.getTotal());
    }

    @Override
    @CheckObjectPermission(objectType = PRODUCT_OBJECT_TYPE, objectId = "#pageReqVO.productId",
            permission = PRODUCT_QUERY_PERMISSION)
    public PageResult<ProductRequirementRespVO> getRequirementTree(ProductRequirementPageReqVO pageReqVO) {
        Long moduleId = pageReqVO.getModuleId();
        Long productId = pageReqVO.getProductId();

        // 处理模块过滤条件：仅当选中具体模块时，递归加载子模块ID进行过滤
        if (moduleId != null) {
            pageReqVO.setModuleIds(getAllModuleIdsWithChildren(moduleId, productId));
            pageReqVO.setModuleId(null);
        }
        // 清空parentId，查询所有符合条件的需求（不限层级）
        pageReqVO.setParentId(null);

        // 第一步：查询所有符合搜索条件的需求
        List<ProductRequirementDO> matchedRequirements = requirementMapper.selectList(pageReqVO);
        if (matchedRequirements.isEmpty()) {
            return new PageResult<>(Collections.emptyList(), 0L);
        }

        // 第二步：找出所有匹配需求的根节点ID，同时收集路径上的所有节点ID
        Set<Long> rootIds = new HashSet<>();
        Set<Long> pathNodeIds = new HashSet<>();
        Map<Long, ProductRequirementDO> requirementCache = new HashMap<>();
        for (ProductRequirementDO req : matchedRequirements) {
            requirementCache.put(req.getId(), req);
            pathNodeIds.add(req.getId());
            Long rootId = findRootRequirementIdAndCollectPath(req, requirementCache, pathNodeIds);
            rootIds.add(rootId);
        }

        // 第三步：查询根需求详情并按创建时间倒排
        List<ProductRequirementDO> rootRequirements = requirementMapper.selectBatchIds(rootIds);
        rootRequirements.sort((a, b) -> b.getCreateTime().compareTo(a.getCreateTime()));

        // 第四步：对根节点列表进行内存分页
        int pageNo = pageReqVO.getPageNo() != null ? pageReqVO.getPageNo() : 1;
        int pageSize = pageReqVO.getPageSize() != null ? pageReqVO.getPageSize() : 10;
        int total = rootRequirements.size();
        int fromIndex = (pageNo - 1) * pageSize;
        int toIndex = Math.min(fromIndex + pageSize, total);

        if (fromIndex >= total) {
            return new PageResult<>(Collections.emptyList(), (long) total);
        }

        List<ProductRequirementDO> pagedRootRequirements = rootRequirements.subList(fromIndex, toIndex);

        // 第五步：构建树形结构（只包含路径上的节点）
        List<ProductRequirementRespVO> list = pagedRootRequirements.stream()
                .map(req -> buildRequirementRespVOWithPathChildren(req, pathNodeIds))
                .collect(Collectors.toList());

        return new PageResult<>(list, (long) total);
    }

    /**
     * 向上追溯需求的根节点ID，同时收集路径上的所有节点ID
     * @param requirement 起始需求
     * @param cache 需求缓存（避免重复查询）
     * @param pathNodeIds 路径节点ID集合（输出参数）
     * @return 根节点ID（parentId = 0L 的需求ID）
     */
    private Long findRootRequirementIdAndCollectPath(ProductRequirementDO requirement,
                                                      Map<Long, ProductRequirementDO> cache,
                                                      Set<Long> pathNodeIds) {
        if (requirement.getParentId() == null || requirement.getParentId() == 0L) {
            return requirement.getId();
        }
        // 从缓存中查找父需求，如果没有则查询数据库
        ProductRequirementDO parent = cache.get(requirement.getParentId());
        if (parent == null) {
            parent = requirementMapper.selectById(requirement.getParentId());
            if (parent != null) {
                cache.put(parent.getId(), parent);
            }
        }
        if (parent == null) {
            // 父需求不存在（数据异常），返回当前需求作为根
            return requirement.getId();
        }
        // 收集路径上的节点ID
        pathNodeIds.add(parent.getId());
        return findRootRequirementIdAndCollectPath(parent, cache, pathNodeIds);
    }

    /**
     * 构建需求响应VO（只包含路径上的子需求）
     * @param requirement 需求
     * @param pathNodeIds 路径节点ID集合
     * @return 需求响应VO
     */
    private ProductRequirementRespVO buildRequirementRespVOWithPathChildren(ProductRequirementDO requirement,
                                                                             Set<Long> pathNodeIds) {
        ProductRequirementRespVO respVO = buildRequirementRespVO(requirement);
        // 查询子需求
        List<ProductRequirementDO> allChildren = requirementMapper.selectListByParentId(requirement.getId());
        // 只保留路径上的子需求
        List<ProductRequirementDO> pathChildren = allChildren.stream()
                .filter(child -> pathNodeIds.contains(child.getId()))
                .toList();
        if (!pathChildren.isEmpty()) {
            respVO.setChildren(pathChildren.stream()
                    .map(child -> buildRequirementRespVOWithPathChildren(child, pathNodeIds))
                    .collect(Collectors.toList()));
        }
        return respVO;
    }

    /**
     * 判断指定模块是否为"全部需求"模块（parentId = 0L 的根模块）
     */
    @VisibleForTesting
    boolean isAllRequirementsModule(Long moduleId) {
        if (moduleId == null) {
            return false;
        }
        ProductRequirementModuleDO module = moduleMapper.selectById(moduleId);
        return module != null && module.getParentId() != null && module.getParentId() == 0L;
    }

    /**
     * 递归获取模块及其所有子模块的ID列表
     * @param moduleId 起始模块ID
     * @param productId 产品ID
     * @return 包含自身及所有子模块的ID列表
     */
    @VisibleForTesting
    List<Long> getAllModuleIdsWithChildren(Long moduleId, Long productId) {
        List<Long> moduleIds = new ArrayList<>();
        moduleIds.add(moduleId);
        // 查询该产品下所有模块
        List<ProductRequirementModuleDO> allModules = moduleMapper.selectListByProductId(productId);
        // 递归查找子模块
        collectChildModuleIds(moduleId, allModules, moduleIds);
        return moduleIds;
    }

    /**
     * 递归收集子模块ID
     */
    private void collectChildModuleIds(Long parentId, List<ProductRequirementModuleDO> allModules, List<Long> result) {
        for (ProductRequirementModuleDO module : allModules) {
            if (Objects.equals(module.getParentId(), parentId)) {
                result.add(module.getId());
                collectChildModuleIds(module.getId(), allModules, result);
            }
        }
    }

    /**
     * 递归获取需求及其所有子需求（包含子子需求）
     * @param requirementId 起始需求ID
     * @return 包含自身及所有后代需求的列表
     */
    @VisibleForTesting
    List<ProductRequirementDO> getAllRequirementsWithChildren(Long requirementId) {
        List<ProductRequirementDO> allRequirements = new ArrayList<>();
        ProductRequirementDO requirement = validateRequirementExists(requirementId);
        allRequirements.add(requirement);
        collectChildRequirements(requirementId, allRequirements);
        return allRequirements;
    }

    /**
     * 递归收集子需求
     */
    private void collectChildRequirements(Long parentId, List<ProductRequirementDO> result) {
        List<ProductRequirementDO> children = requirementMapper.selectListByParentId(parentId);
        for (ProductRequirementDO child : children) {
            result.add(child);
            collectChildRequirements(child.getId(), result);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @CheckObjectPermission(objectType = PRODUCT_OBJECT_TYPE, objectId = "#reqVO.productId",
            permission = PRODUCT_STATUS_PERMISSION)
    public void changeRequirementStatus(ProductRequirementStatusActionReqVO reqVO) {
        ProductRequirementDO requirement = validateRequirementExists(reqVO.getId());
        String actionCode = reqVO.getActionCode().trim();
        Long implementProjectId = reqVO.getImplementProjectId();
        String fromStatus = requirement.getStatusCode();

        // 校验状态流转是否合法
        ObjectStatusTransitionDO transition = validateRequirementTransition(fromStatus, actionCode);
        String reason = normalizeNullableText(reqVO.getReason());
        // 校验是否需要填写原因
        validateTransitionReason(transition, reason);
        //下一状态
        String toStatus = transition.getToStatusCode();

        // accept和close动作时，校验所有子需求（包括子子需求）是否处于允许状态
        if ("accept".equals(actionCode) || "close".equals(actionCode)) {
            validateAllChildrenAllowCloseOrAccept(reqVO.getId());
        }
        // close动作时，递归关闭所有已验收的子需求（包括子子需求）
        if ("close".equals(actionCode)) {
            closeAllAcceptedChildren(reqVO.getId(), reason);
        }
        // 带并发控制的状态更新（支持同时更新实现项目ID）
        int updateCount = requirementMapper.updateStatusByIdAndStatusWithProject(requirement.getId(), fromStatus, toStatus, reason, implementProjectId);
        if (updateCount != 1) {
            throw exception(ErrorCodeConstants.REQUIREMENT_STATUS_CONCURRENT_MODIFIED);
        }
        requirement.setStatusCode(toStatus);
        requirement.setLastStatusReason(reason);

        // 写入状态变更日志
        writeRequirementStatusLog(requirement, actionCode, fromStatus, toStatus, reason);
        // 写入业务审计日志
        writeBizAuditLog(requirement, actionCode, fromStatus, toStatus, null, reason);
    }

    /**
     * 校验需求的所有子需求（包括子子需求）是否处于允许关闭或验收的状态
     */
    @VisibleForTesting
    void validateAllChildrenAllowCloseOrAccept(Long requirementId) {
        List<ProductRequirementDO> allChildren = getAllRequirementsWithChildren(requirementId);
        // 排除自身，只校验子需求
        for (ProductRequirementDO req : allChildren) {
            if (!Objects.equals(req.getId(), requirementId)) {
                if (!CHILD_ALLOW_CLOSE_STATUSES.contains(req.getStatusCode())) {
                    throw exception(ErrorCodeConstants.REQUIREMENT_CHILD_NOT_ALLOW_CLOSE);
                }
            }
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @CheckObjectPermission(objectType = PRODUCT_OBJECT_TYPE, objectId = "#productId",
            permission = PRODUCT_DELETE_PERMISSION)
    public void deleteRequirement(Long id, Long productId) {
        ProductRequirementDO requirement = validateRequirementExists(id);
        String fromStatus = requirement.getStatusCode();

        // 校验是否存在子需求
        List<ProductRequirementDO> children = requirementMapper.selectListByParentId(id);
        if (!children.isEmpty()) {
            throw exception(ErrorCodeConstants.REQUIREMENT_HAS_CHILDREN);
        }

        // 校验状态是否允许删除（只有待确认、待评审、待分流状态才能删除）
        if (!ALLOW_DELETE_STATUSES.contains(fromStatus)) {
            throw exception(ErrorCodeConstants.REQUIREMENT_STATUS_NOT_ALLOW_DELETE);
        }

        // 带并发控制的删除（以当前状态作为条件）
        int deleteCount = requirementMapper.deleteByIdAndStatus(id, fromStatus);
        if (deleteCount != 1) {
            throw exception(ErrorCodeConstants.REQUIREMENT_STATUS_CONCURRENT_MODIFIED);
        }

        writeBizAuditLog(requirement, ACTION_DELETE, fromStatus, null, null, null);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @CheckObjectPermission(objectType = PRODUCT_OBJECT_TYPE, objectId = "#reqVO.productId",
            permission = PRODUCT_SPLIT_PERMISSION)
    public Long splitRequirement(ProductRequirementSplitReqVO reqVO) {
        // 校验父需求是否存在
        ProductRequirementDO parentRequirement = validateRequirementExists(reqVO.getParentId());
        // 校验父需求状态是否允许拆分（只能是待分流或实施中）
        validateParentAllowSplit(parentRequirement);

        // 创建子需求
        ProductRequirementDO childRequirement = new ProductRequirementDO();
        childRequirement.setParentId(reqVO.getParentId());
        childRequirement.setProductId(parentRequirement.getProductId()); // 子需求继承父需求的产品
        childRequirement.setModuleId(parentRequirement.getModuleId()); // 子需求继承父需求的模块
        childRequirement.setReviewRequired(reqVO.getReviewRequired()); // 子需求默认不需要评审
        childRequirement.setTitle(reqVO.getTitle().trim());
        childRequirement.setDescription(normalizeNullableText(reqVO.getDescription()));
        childRequirement.setCategory(reqVO.getCategory());
        childRequirement.setSourceType(parentRequirement.getSourceType()); // 继承父需求来源类型
        childRequirement.setPriority(reqVO.getPriority());
        // 子需求初始状态为待分流
        // 根据是否需要评审确定初始状态
        String initialStatus = Objects.equals(reqVO.getReviewRequired(), 1)
                ? STATUS_PENDING_REVIEW : STATUS_PENDING_DISPATCH;
        childRequirement.setStatusCode(initialStatus);
        childRequirement.setProposerId(parentRequirement.getProposerId()); // 继承父需求提出人
        childRequirement.setCurrentHandlerUserId(reqVO.getCurrentHandlerUserId());
        childRequirement.setImplementProjectId(reqVO.getImplementProjectId());
        childRequirement.setCompletionDate(reqVO.getCompletionDate());
        childRequirement.setSort(reqVO.getSort() != null ? reqVO.getSort() : 0);
        requirementMapper.insert(childRequirement);

        // 父需求状态从待分流变为实施中
        if (STATUS_PENDING_DISPATCH.equals(parentRequirement.getStatusCode())) {
            String fromStatus = parentRequirement.getStatusCode();
            int updateCount = requirementMapper.updateStatusByIdAndStatus(
                    parentRequirement.getId(), fromStatus, STATUS_IMPLEMENTING, null);
            if (updateCount == 1) {
                parentRequirement.setStatusCode(STATUS_IMPLEMENTING);
                writeRequirementStatusLog(parentRequirement, ACTION_SPLIT, fromStatus, STATUS_IMPLEMENTING, null);
                writeBizAuditLog(parentRequirement, ACTION_SPLIT, fromStatus, STATUS_IMPLEMENTING, null, null);
            }
        }

        // 写入子需求的业务审计日志
        writeBizAuditLog(childRequirement, ACTION_CREATE, null, initialStatus, null, null);
        return childRequirement.getId();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @CheckObjectPermission(objectType = PRODUCT_OBJECT_TYPE, objectId = "#reqVO.productId",
            permission = PRODUCT_STATUS_PERMISSION)
    public void closeRequirement(ProductRequirementCloseReqVO reqVO) {
        ProductRequirementDO requirement = validateRequirementExists(reqVO.getId());
        String fromStatus = requirement.getStatusCode();
        String reason = reqVO.getReason().trim();

        // 校验当前状态是否为已验收（只有已验收才能关闭）
        if (!STATUS_ACCEPTED.equals(fromStatus)) {
            throw exception(ErrorCodeConstants.REQUIREMENT_STATUS_NOT_ALLOW_CLOSE);
        }

        // 校验所有子需求（包括子子需求）是否允许关闭
        validateAllChildrenAllowCloseOrAccept(reqVO.getId());

        // 递归关闭所有已验收的子需求（包括子子需求）
        closeAllAcceptedChildren(reqVO.getId(), reason);

        // 关闭当前需求
        int updateCount = requirementMapper.updateStatusByIdAndStatus(
                requirement.getId(), fromStatus, STATUS_CLOSED, reason);
        if (updateCount != 1) {
            throw exception(ErrorCodeConstants.REQUIREMENT_STATUS_CONCURRENT_MODIFIED);
        }
        requirement.setStatusCode(STATUS_CLOSED);
        requirement.setLastStatusReason(reason);

        writeRequirementStatusLog(requirement, ACTION_CLOSE, fromStatus, STATUS_CLOSED, reason);
        writeBizAuditLog(requirement, ACTION_CLOSE, fromStatus, STATUS_CLOSED, null, reason);
    }

    /**
     * 递归关闭所有已验收的子需求（包括子子需求）
     */
    private void closeAllAcceptedChildren(Long parentId, String reason) {
        List<ProductRequirementDO> children = requirementMapper.selectListByParentId(parentId);
        for (ProductRequirementDO child : children) {
            // 递归处理子需求的子需求
            closeAllAcceptedChildren(child.getId(), reason);
            // 如果子需求已验收，则关闭
            if (STATUS_ACCEPTED.equals(child.getStatusCode())) {
                int updateCount = requirementMapper.updateStatusByIdAndStatus(
                        child.getId(), STATUS_ACCEPTED, STATUS_CLOSED, reason);
                if (updateCount == 1) {
                    writeRequirementStatusLog(child, ACTION_CLOSE, STATUS_ACCEPTED, STATUS_CLOSED, reason);
                    writeBizAuditLog(child, ACTION_CLOSE, STATUS_ACCEPTED, STATUS_CLOSED, null, reason);
                }
            }
        }
    }

    @Override
    @CheckObjectPermission(objectType = PRODUCT_OBJECT_TYPE, objectId = "#productId",
            permission = PRODUCT_QUERY_PERMISSION)
    public List<ProductRequirementStatusTransitionRespVO> getAllowedTransitions(Long requirementId, Long productId) {
        ProductRequirementDO requirement = validateRequirementExists(requirementId);
        String currentStatus = requirement.getStatusCode();

        // 查询当前状态允许的所有流转
        List<ObjectStatusTransitionDO> transitions = statusTransitionMapper
                .selectListByObjectTypeAndFromStatus(REQUIREMENT_OBJECT_TYPE, currentStatus);

        return transitions.stream().map(transition -> {
            ProductRequirementStatusTransitionRespVO vo = new ProductRequirementStatusTransitionRespVO();
            vo.setActionCode(transition.getActionCode());
            vo.setActionName(transition.getActionName());
            vo.setToStatusCode(transition.getToStatusCode());
            // 查询目标状态名称
            ObjectStatusModelDO statusModel = statusModelMapper
                    .selectByObjectTypeAndStatusCode(REQUIREMENT_OBJECT_TYPE, transition.getToStatusCode());
            vo.setToStatusName(statusModel != null ? statusModel.getStatusName() : transition.getToStatusCode());
            vo.setNeedReason(transition.getNeedReason());
            return vo;
        }).collect(Collectors.toList());
    }

    @Override
    @CheckObjectPermission(objectType = PRODUCT_OBJECT_TYPE, objectId = "#productId",
            permission = PRODUCT_QUERY_PERMISSION)
    public ProductRequirementLifecycleRespVO getRequirementLifecycle(Long requirementId, Long productId) {
        ProductRequirementDO requirement = validateRequirementExists(requirementId);
        String currentStatus = requirement.getStatusCode();

        // 查询当前状态定义
        ObjectStatusModelDO statusModel = statusModelMapper
                .selectByObjectTypeAndStatusCodeEnabled(REQUIREMENT_OBJECT_TYPE, currentStatus);
        if (statusModel == null) {
            throw exception(ErrorCodeConstants.REQUIREMENT_STATUS_MODEL_NOT_EXISTS_OR_DISABLED);
        }

        ProductRequirementLifecycleRespVO lifecycle = new ProductRequirementLifecycleRespVO();
        lifecycle.setStatusCode(statusModel.getStatusCode());
        lifecycle.setStatusName(statusModel.getStatusName());
        lifecycle.setTerminal(statusModel.getTerminalFlag());
        lifecycle.setAllowEdit(statusModel.getAllowEdit());
        lifecycle.setLastStatusReason(requirement.getLastStatusReason());
        lifecycle.setAvailableActions(getAllowedTransitions(requirementId, productId));
        return lifecycle;
    }

    // ========== 模块管理 ==========
    @Override
    @Transactional(rollbackFor = Exception.class)
    @CheckObjectPermission(objectType = PRODUCT_OBJECT_TYPE, objectId = "#reqVO.productId",
            permission = PRODUCT_CREATE_PERMISSION)
    public Long createRequirementModule(ProductRequirementModuleReqVO reqVO) {
        // 校验模块名称在同一产品下是否唯一
        validateModuleNameUnique(reqVO.getProductId(), null, reqVO.getModuleName());

        ProductRequirementModuleDO module = new ProductRequirementModuleDO();
        module.setParentId(reqVO.getParentId() != null ? reqVO.getParentId() : 0L);
        module.setProductId(reqVO.getProductId());
        module.setModuleName(reqVO.getModuleName().trim());
        module.setRemark(normalizeNullableText(reqVO.getRemark()));
        module.setIcon(normalizeNullableText(reqVO.getIcon()));
        module.setSort(reqVO.getSort() != null ? reqVO.getSort() : 0);
        moduleMapper.insert(module);
        return module.getId();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @CheckObjectPermission(objectType = PRODUCT_OBJECT_TYPE, objectId = "#reqVO.productId",
            permission = PRODUCT_UPDATE_PERMISSION)
    public void updateRequirementModule(ProductRequirementModuleReqVO reqVO) {
        if (reqVO.getId() == null) {
            throw invalidParamException("模块编号不能为空");
        }
        ProductRequirementModuleDO module = validateModuleExists(reqVO.getId());
        // 校验模块名称唯一性
        validateModuleNameUnique(reqVO.getProductId(), reqVO.getId(), reqVO.getModuleName());

        module.setModuleName(reqVO.getModuleName().trim());
        module.setRemark(normalizeNullableText(reqVO.getRemark()));
        module.setIcon(normalizeNullableText(reqVO.getIcon()));
        module.setSort(reqVO.getSort() != null ? reqVO.getSort() : 0);
        moduleMapper.updateById(module);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @CheckObjectPermission(objectType = PRODUCT_OBJECT_TYPE, objectId = "#productId",
            permission = PRODUCT_DELETE_PERMISSION)
    public void deleteRequirementModule(Long moduleId, Long productId) {
        validateModuleExists(moduleId);

        // 校验是否存在子模块
        List<ProductRequirementModuleDO> childModules = moduleMapper.selectListByParentId(moduleId);
        if (!childModules.isEmpty()) {
            throw exception(ErrorCodeConstants.REQUIREMENT_MODULE_HAS_CHILDREN);
        }

        // 校验模块下是否存在需求
        List<ProductRequirementDO> requirements = requirementMapper.selectListByModuleId(moduleId);
        if (!requirements.isEmpty()) {
            throw exception(ErrorCodeConstants.REQUIREMENT_MODULE_HAS_REQUIREMENTS);
        }

        // 删除模块
        moduleMapper.deleteById(moduleId);
    }

    @Override
    @CheckObjectPermission(objectType = PRODUCT_OBJECT_TYPE, objectId = "#productId",
            permission = PRODUCT_QUERY_PERMISSION)
    public List<ProductRequirementModuleRespVO> getRequirementModuleTree(Long productId) {
        List<ProductRequirementModuleDO> modules = moduleMapper.selectListByProductId(productId);
        return buildModuleTree(modules, 0L);
    }

    @Override
    public List<ProductRequirementStatusDictRespVO> getRequirementStatusDict() {
        List<ObjectStatusModelDO> statusModels = statusModelMapper.selectListByObjectType(REQUIREMENT_OBJECT_TYPE);
        return statusModels.stream()
                .map(this::buildStatusDictRespVO)
                .collect(Collectors.toList());
    }

    @Override
    public List<ProductRequirementStatusDictRespVO> getRequirementTerminalStatusDict() {
        List<ObjectStatusModelDO> statusModels = statusModelMapper.selectListByObjectType(REQUIREMENT_OBJECT_TYPE);
        return statusModels.stream()
                .filter(ObjectStatusModelDO::getTerminalFlag)
                .map(this::buildStatusDictRespVO)
                .collect(Collectors.toList());
    }

    /**
     * 构建状态字典响应VO
     */
    private ProductRequirementStatusDictRespVO buildStatusDictRespVO(ObjectStatusModelDO statusModel) {
        ProductRequirementStatusDictRespVO respVO = new ProductRequirementStatusDictRespVO();
        respVO.setStatusCode(statusModel.getStatusCode());
        respVO.setStatusName(statusModel.getStatusName());
        respVO.setSort(statusModel.getSort());
        respVO.setInitialFlag(statusModel.getInitialFlag());
        respVO.setTerminalFlag(statusModel.getTerminalFlag());
        return respVO;
    }

    // ========== 私有辅助方法 ==========

    /**
     * 构建需求响应VO（不含子需求）
     */
    private ProductRequirementRespVO buildRequirementRespVO(ProductRequirementDO requirement) {
        ProductRequirementRespVO respVO = BeanUtils.toBean(requirement, ProductRequirementRespVO.class);
        // 查询状态名称
        ObjectStatusModelDO statusModel = statusModelMapper
                .selectByObjectTypeAndStatusCode(REQUIREMENT_OBJECT_TYPE, requirement.getStatusCode());
        if (statusModel != null) {
            respVO.setStatusName(statusModel.getStatusName());
            respVO.setTerminal(statusModel.getTerminalFlag());
        }
        // 设置是否终态
        if (respVO.getTerminal() == null) {
            respVO.setTerminal(TERMINAL_STATUSES.contains(requirement.getStatusCode()));
        }
        return respVO;
    }

    /**
     * 构建模块树
     */
    private List<ProductRequirementModuleRespVO> buildModuleTree(List<ProductRequirementModuleDO> modules, Long parentId) {
        return modules.stream()
                .filter(m -> Objects.equals(m.getParentId(), parentId))
                .map(m -> {
                    ProductRequirementModuleRespVO vo = BeanUtils.toBean(m, ProductRequirementModuleRespVO.class);
                    vo.setChildren(buildModuleTree(modules, m.getId()));
                    return vo;
                })
                .collect(Collectors.toList());
    }

    /**
     * 校验需求是否存在
     */
    @VisibleForTesting
    ProductRequirementDO validateRequirementExists(Long id) {
        if (id == null) {
            throw exception(ErrorCodeConstants.REQUIREMENT_NOT_EXISTS);
        }
        ProductRequirementDO requirement = requirementMapper.selectById(id);
        if (requirement == null) {
            throw exception(ErrorCodeConstants.REQUIREMENT_NOT_EXISTS);
        }
        return requirement;
    }

    /**
     * 校验需求是否允许编辑（终态不允许编辑）
     */
    @VisibleForTesting
    void validateRequirementEditable(ProductRequirementDO requirement) {
        if (TERMINAL_STATUSES.contains(requirement.getStatusCode())) {
            throw exception(ErrorCodeConstants.REQUIREMENT_STATUS_NOT_ALLOW_EDIT);
        }
    }

    /**
     * 校验父需求是否允许拆分
     */
    @VisibleForTesting
    void validateParentAllowSplit(ProductRequirementDO parentRequirement) {
        String status = parentRequirement.getStatusCode();
        if (!STATUS_PENDING_DISPATCH.equals(status) && !STATUS_IMPLEMENTING.equals(status)) {
            throw exception(ErrorCodeConstants.REQUIREMENT_PARENT_NOT_ALLOW_SPLIT);
        }
    }

    /**
     * 校验状态流转是否合法
     */
    @VisibleForTesting
    ObjectStatusTransitionDO validateRequirementTransition(String fromStatusCode, String actionCode) {
        ObjectStatusTransitionDO transition = statusTransitionMapper
                .selectByObjectTypeAndFromStatusAndAction(REQUIREMENT_OBJECT_TYPE, fromStatusCode, actionCode);
        if (transition == null) {
            throw exception(ErrorCodeConstants.REQUIREMENT_STATUS_ACTION_NOT_ALLOWED, actionCode);
        }
        return transition;
    }

    /**
     * 校验状态流转是否需要填写原因
     */
    @VisibleForTesting
    void validateTransitionReason(ObjectStatusTransitionDO transition, String reason) {
        if (Boolean.TRUE.equals(transition.getNeedReason()) && !StringUtils.hasText(reason)) {
            throw exception(ErrorCodeConstants.REQUIREMENT_STATUS_ACTION_REASON_REQUIRED, transition.getActionCode());
        }
    }

    /**
     * 校验模块是否存在
     */
    @VisibleForTesting
    ProductRequirementModuleDO validateModuleExists(Long id) {
        if (id == null) {
            throw exception(ErrorCodeConstants.REQUIREMENT_MODULE_NOT_EXISTS);
        }
        ProductRequirementModuleDO module = moduleMapper.selectById(id);
        if (module == null) {
            throw exception(ErrorCodeConstants.REQUIREMENT_MODULE_NOT_EXISTS);
        }
        return module;
    }

    /**
     * 校验模块名称在同一产品下是否唯一
     */
    @VisibleForTesting
    void validateModuleNameUnique(Long productId, Long moduleId, String moduleName) {
        if (!StringUtils.hasText(moduleName)) {
            return;
        }
        ProductRequirementModuleDO existModule = moduleMapper
                .selectByProductIdAndModuleName(productId, moduleName.trim());
        if (existModule == null) {
            return;
        }
        if (!existModule.getId().equals(moduleId)) {
            throw exception(ErrorCodeConstants.REQUIREMENT_MODULE_NAME_DUPLICATE, moduleName.trim());
        }
    }

    /**
     * 解析模块ID：当未选择模块时，自动归属到该产品的"全部需求"模块
     * "全部需求"模块的标志性特征是 parentId = 0L
     */
    @VisibleForTesting
    Long resolveModuleId(Long moduleId, Long productId) {
        if (moduleId != null) {
            return moduleId;
        }
        // 查询该产品的"全部需求"模块（parentId = 0L 的根模块）
        ProductRequirementModuleDO defaultModule = moduleMapper
                .selectByProductIdAndParentId(productId, 0L);
        if (defaultModule == null) {
            throw exception(ErrorCodeConstants.REQUIREMENT_MODULE_NOT_EXISTS);
        }
        return defaultModule.getId();
    }

    /**
     * 校验模块是否属于当前产品
     */
    @VisibleForTesting
    void validateModuleBelongsToProduct(Long moduleId, Long productId) {
        if (moduleId == null) {
            return; // 全部需求模块不需要校验
        }
        ProductRequirementModuleDO module = moduleMapper.selectById(moduleId);
        if (module == null) {
            throw exception(ErrorCodeConstants.REQUIREMENT_MODULE_NOT_EXISTS);
        }
        if (!Objects.equals(module.getProductId(), productId)) {
            throw exception(ErrorCodeConstants.REQUIREMENT_MODULE_NOT_BELONG_TO_PRODUCT);
        }
    }

    /**
     * 写入需求状态变更日志
     */
    private void writeRequirementStatusLog(ProductRequirementDO requirement, String actionType,
                                           String fromStatus, String toStatus, String reason) {
        ProductRequirementStatusLogDO statusLog = new ProductRequirementStatusLogDO();
        statusLog.setRequirementId(requirement.getId());
        statusLog.setActionType(actionType);
        statusLog.setFromStatus(fromStatus);
        statusLog.setToStatus(toStatus);
        statusLog.setReason(defaultText(reason));
        statusLog.setOperatorUserId(SecurityFrameworkUtils.getLoginUserId());
        statusLog.setOperatorName(defaultText(SecurityFrameworkUtils.getLoginUserNickname()));
        statusLog.setRequirementTitleSnapshot(requirement.getTitle());
        statusLogMapper.insert(statusLog);
    }

    /**
     * 写入业务审计日志
     */
    private void writeBizAuditLog(ProductRequirementDO requirement, String actionType, String fromStatus,
                                  String toStatus, String fieldChanges, String reason) {
        BizAuditLogDO auditLog = new BizAuditLogDO();
        auditLog.setBizType(BIZ_TYPE_REQUIREMENT);
        auditLog.setBizId(requirement.getId());
        auditLog.setActionType(actionType);
        auditLog.setFromStatus(fromStatus);
        auditLog.setToStatus(toStatus);
        auditLog.setFieldChanges(fieldChanges);
        auditLog.setReason(reason);
        auditLog.setOperatorUserId(SecurityFrameworkUtils.getLoginUserId());
        auditLog.setOperatorName(defaultText(SecurityFrameworkUtils.getLoginUserNickname()));
        bizAuditLogMapper.insert(auditLog);
    }

    /**
     * 处理可能为空的文本，去除首尾空格后若为空则返回null
     */
    private String normalizeNullableText(String value) {
        if (!StringUtils.hasText(value)) {
            return null;
        }
        return value.trim();
    }

    /**
     * 处理可能为空的文本，若为空则返回空字符串
     */
    private String defaultText(String value) {
        return StringUtils.hasText(value) ? value : "";
    }

}
