package com.cf.imes.module.executor.service.veneer;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.PageDTO;
import com.cf.imes.framework.common.exception.ServiceException;
import com.cf.imes.framework.common.pojo.PageResult;
import com.cf.imes.framework.security.core.LoginUser;
import com.cf.imes.framework.security.core.util.SecurityFrameworkUtils;
import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerPlanAddPlateReqVO;
import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerPlanCreateReqVO;
import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerPlanGroupReqVO;
import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerPlanPageReqVO;
import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerPlanPageRespVO;
import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerPlanPlateRespVO;
import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerPlanProduceReqVO;
import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerPlanRemovePlateReqVO;
import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerPlanSortReqVO;
import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerPlanSortRespVO;
import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerPlanUpdateReqVO;
import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerPlanWithdrawProduceReqVO;
import com.cf.imes.module.executor.dal.dataobject.goods.GoodsDO;
import com.cf.imes.module.executor.dal.dataobject.order.OrderDO;
import com.cf.imes.module.executor.dal.dataobject.veneer.OrderPlateVeneerDO;
import com.cf.imes.module.executor.dal.dataobject.veneer.OrderVeneerPlanDO;
import com.cf.imes.module.executor.dal.dataobject.veneer.VeneerDO;
import com.cf.imes.module.executor.dal.dataobject.veneer.VeneerPlannedPlateCountDO;
import com.cf.imes.module.executor.dal.dataobject.veneer.VeneerPlanStatisticsDO;
import com.cf.imes.module.executor.dal.mysql.goods.GoodsMapper;
import com.cf.imes.module.executor.dal.mysql.order.OrderMapper;
import com.cf.imes.module.executor.dal.mysql.veneer.VeneerPlanMapper;
import com.cf.imes.module.executor.dal.mysql.veneer.VeneerMapper;
import com.cf.imes.module.executor.enums.VeneerPlanStatusEnum;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

import static com.cf.imes.module.executor.enums.ErrorCodeConstants.GOODS_PLATE_UPDATE_CONCURRENCY_ERROR;
import static com.cf.imes.module.executor.enums.ErrorCodeConstants.ORDER_PLATE_UPDATE_CONCURRENCY_ERROR;
import static com.cf.imes.module.executor.enums.ErrorCodeConstants.VENEER_PLAN_AREA_DATA_INVALID;
import static com.cf.imes.module.executor.enums.ErrorCodeConstants.VENEER_PLAN_CREATE_FAILED;
import static com.cf.imes.module.executor.enums.ErrorCodeConstants.VENEER_PLAN_DELETE_LIMIT_EXCEEDED;
import static com.cf.imes.module.executor.enums.ErrorCodeConstants.VENEER_PLAN_DELETE_NOT_ALLOWED;
import static com.cf.imes.module.executor.enums.ErrorCodeConstants.VENEER_PLAN_DELETE_NOT_EXIST;
import static com.cf.imes.module.executor.enums.ErrorCodeConstants.VENEER_PLAN_DELETE_RECORD_REQUIRED;
import static com.cf.imes.module.executor.enums.ErrorCodeConstants.VENEER_PLAN_DELETE_STATUS_CHANGED;
import static com.cf.imes.module.executor.enums.ErrorCodeConstants.VENEER_PLAN_GROUP_DUPLICATE;
import static com.cf.imes.module.executor.enums.ErrorCodeConstants.VENEER_PLAN_GROUP_REQUIRED;
import static com.cf.imes.module.executor.enums.ErrorCodeConstants.VENEER_PLAN_GROUP_SOURCE_NO_REQUIRED;
import static com.cf.imes.module.executor.enums.ErrorCodeConstants.VENEER_PLAN_NOT_EXIST;
import static com.cf.imes.module.executor.enums.ErrorCodeConstants.VENEER_PLAN_OPERATION_STATUS_CHANGED;
import static com.cf.imes.module.executor.enums.ErrorCodeConstants.VENEER_PLAN_PLATE_RECORD_REQUIRED;
import static com.cf.imes.module.executor.enums.ErrorCodeConstants.VENEER_PLAN_PLATE_SOURCE_MISMATCH;
import static com.cf.imes.module.executor.enums.ErrorCodeConstants.VENEER_PLAN_PLATE_STATUS_CHANGED;
import static com.cf.imes.module.executor.enums.ErrorCodeConstants.VENEER_PLAN_PROCESS_REQUIRED;
import static com.cf.imes.module.executor.enums.ErrorCodeConstants.VENEER_PLAN_RECORD_DUPLICATE;
import static com.cf.imes.module.executor.enums.ErrorCodeConstants.VENEER_PLAN_RECORD_REQUIRED;
import static com.cf.imes.module.executor.enums.ErrorCodeConstants.VENEER_PLAN_RECORD_STATUS_CHANGED;
import static com.cf.imes.module.executor.enums.ErrorCodeConstants.VENEER_PLAN_SORT_RECORD_DUPLICATE;
import static com.cf.imes.module.executor.enums.ErrorCodeConstants.VENEER_PLAN_SORT_RECORD_REQUIRED;
import static com.cf.imes.module.executor.enums.ErrorCodeConstants.VENEER_PLAN_SORT_STATUS_CHANGED;
import static com.cf.imes.module.executor.enums.ErrorCodeConstants.VENEER_PLAN_SOURCE_NO_DUPLICATE;
import static com.cf.imes.module.executor.enums.ErrorCodeConstants.VENEER_PLAN_SOURCE_NO_MISMATCH;
import static com.cf.imes.module.executor.enums.ErrorCodeConstants.VENEER_PLAN_SOURCE_NO_NOT_EXIST;

/**
 * 贴皮排单 Service 实现。
 */
@Service
public class VeneerPlanServiceImpl implements VeneerPlanService {

    /**
     * 贴皮记录查询和更新的单批处理数量。
     */
    private static final int PLAN_BATCH_SIZE = 1000;

    /**
     * 贴皮排单批量插入数量。
     */
    private static final int PLAN_INSERT_BATCH_SIZE = 100;

    /**
     * 订单及订单材质贴皮数量统计的单批处理数量。
     */
    private static final int PLAN_STAT_BATCH_SIZE = 100;

    /**
     * 单次允许删除的贴皮排单最大数量。
     */
    private static final int PLAN_DELETE_LIMIT = 20;

    /**
     * 贴皮排单优先级单批更新数量。
     */
    private static final int PLAN_SORT_BATCH_SIZE = 500;

    /**
     * 允许修改加工方案及贴皮明细的排单状态。
     */
    private static final List<Integer> PLAN_EDITABLE_STATUSES = List.of(
            VeneerPlanStatusEnum.NOCUTTING.getStatus(),
            VeneerPlanStatusEnum.OPTIMIZED.getStatus());

    /**
     * 平方毫米转换为平方米的除数。
     */
    private static final BigDecimal SQUARE_MILLIMETER_PER_SQUARE_METER = new BigDecimal("1000000");

    /**
     * 外扩余量需要向长、宽的两侧分别增加。
     */
    private static final BigDecimal OUTER_MARGIN_DOUBLE = new BigDecimal("2");

    @Resource
    private VeneerPlanMapper veneerPlanMapper;

    @Resource
    private IdentifierGenerator identifierGenerator;

    @Resource
    private OrderMapper orderMapper;

    @Resource
    private GoodsMapper goodsMapper;

    @Resource
    private VeneerMapper veneerMapper;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void createPlan(VeneerPlanCreateReqVO req) {
        Map<String, List<Long>> groupIdMap = normalizeGroups(req.getGroups());
        Map<String, Long> sourceVeneerIdMap = loadSourceVeneerIdMap(groupIdMap.keySet());
        List<Long> veneerIds = groupIdMap.values().stream().flatMap(List::stream).toList();

        // 将所有分组ID统一分批查询，避免逐组查询产生N+1，同时控制IN条件长度。
        List<OrderPlateVeneerDO> veneers = new ArrayList<>();
        for (List<Long> idBatch : CollUtil.split(veneerIds, PLAN_BATCH_SIZE)) {
            veneers.addAll(veneerPlanMapper.selectPendingVeneerList(idBatch));
        }
        if (veneers.size() != veneerIds.size()) {
            throw new ServiceException(VENEER_PLAN_RECORD_STATUS_CHANGED);
        }

        Map<Long, OrderPlateVeneerDO> veneerMap = new HashMap<>(veneers.size());
        for (OrderPlateVeneerDO veneer : veneers) {
            veneerMap.put(veneer.getId(), veneer);
        }

        LoginUser loginUser = Objects.requireNonNull(SecurityFrameworkUtils.getLoginUser());
        LocalDateTime planTime = LocalDateTime.now();
        long sort = System.currentTimeMillis();
        List<OrderVeneerPlanDO> plans = new ArrayList<>(groupIdMap.size());

        // 前端已按贴皮商品编号分组；后端逐组校验归属，并为每个分组创建一个排单。
        for (Map.Entry<String, List<Long>> entry : groupIdMap.entrySet()) {
            String sourceVeneerNo = entry.getKey();
            Long sourceVeneerId = sourceVeneerIdMap.get(sourceVeneerNo);
            List<Long> groupIds = entry.getValue();
            List<OrderPlateVeneerDO> group = new ArrayList<>(groupIds.size());
            for (Long veneerId : groupIds) {
                OrderPlateVeneerDO veneer = veneerMap.get(veneerId);
                if (veneer == null) {
                    throw new ServiceException(VENEER_PLAN_RECORD_STATUS_CHANGED);
                }
                if (!StrUtil.equals(sourceVeneerNo, veneer.getSourceVeneerNo())) {
                    throw new ServiceException(
                            VENEER_PLAN_SOURCE_NO_MISMATCH, veneerId, sourceVeneerNo);
                }
                group.add(veneer);
            }

            Long planId = (Long) identifierGenerator.nextId(null);

            // 更新时再次限制 plan_id=0，并校验影响行数，阻止并发重复排单。
            for (List<Long> idBatch : CollUtil.split(groupIds, PLAN_BATCH_SIZE)) {
                int updated = veneerPlanMapper.updateVeneerPlanned(idBatch, planId);
                if (updated != idBatch.size()) {
                    throw new ServiceException(VENEER_PLAN_RECORD_STATUS_CHANGED);
                }
            }

            plans.add(OrderVeneerPlanDO.builder()
                    .id(planId)
                    .organId(loginUser.getOrganId())
                    .status(VeneerPlanStatusEnum.NOCUTTING.getStatus())
                    .isScheduled(false)
                    .sort(++sort)
                    .planTime(planTime)
                    .sourceVeneerId(sourceVeneerId)
                    .veneerNum(group.size())
                    .veneerArea(calculateVeneerArea(group))
                    .operator(loginUser.getNickname())
                    .version(0)
                    .build());
        }

        if (Boolean.FALSE.equals(veneerPlanMapper.insertBatch(plans, PLAN_INSERT_BATCH_SIZE))) {
            throw new ServiceException(VENEER_PLAN_CREATE_FAILED);
        }

        // 排单完成后以数据库当前状态重新统计，避免使用本次请求数量增量更新导致计数漂移。
        List<Long> affectedOrderIds = veneers.stream()
                .map(OrderPlateVeneerDO::getOrderId)
                .distinct()
                .sorted()
                .toList();
        List<Long> affectedOrderGoodsIds = veneers.stream()
                .map(OrderPlateVeneerDO::getOrderGoodsId)
                .distinct()
                .sorted()
                .toList();
        refreshPlannedVeneerPlateCount(affectedOrderIds, affectedOrderGoodsIds);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updatePlan(VeneerPlanUpdateReqVO req) {
        if (req == null || req.getProcessId() == null) {
            throw new ServiceException(VENEER_PLAN_PROCESS_REQUIRED);
        }
        OrderVeneerPlanDO plan = validatePlanStatuses(req.getId(), PLAN_EDITABLE_STATUSES);
        int version = plan.getVersion();
        boolean processChanged = !Objects.equals(plan.getProcessId(), req.getProcessId());

        int updated = veneerPlanMapper.update(new LambdaUpdateWrapper<OrderVeneerPlanDO>()
                .eq(OrderVeneerPlanDO::getId, plan.getId())
                .in(OrderVeneerPlanDO::getStatus, PLAN_EDITABLE_STATUSES)
                .eq(OrderVeneerPlanDO::getVersion, version)
                .set(processChanged, OrderVeneerPlanDO::getPreviousProcessId,
                        plan.getProcessId())
                .set(OrderVeneerPlanDO::getProcessId, req.getProcessId())
                .set(OrderVeneerPlanDO::getProcessName, req.getProcessName())
                .set(OrderVeneerPlanDO::getVersion, version + 1));
        if (updated != 1) {
            throw new ServiceException(VENEER_PLAN_OPERATION_STATUS_CHANGED, plan.getId());
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void producePlan(VeneerPlanProduceReqVO req) {
        Long planId = req == null ? null : req.getId();
        OrderVeneerPlanDO plan = validatePlanExists(planId);
        if (Boolean.TRUE.equals(plan.getIsScheduled())) {
            throw new ServiceException(VENEER_PLAN_OPERATION_STATUS_CHANGED, planId);
        }
        int version = plan.getVersion();

        int updated = veneerPlanMapper.update(new LambdaUpdateWrapper<OrderVeneerPlanDO>()
                .eq(OrderVeneerPlanDO::getId, planId)
                .eq(OrderVeneerPlanDO::getIsScheduled, false)
                .eq(OrderVeneerPlanDO::getVersion, version)
                .set(OrderVeneerPlanDO::getIsScheduled, true)
                .set(OrderVeneerPlanDO::getVersion, version + 1));
        if (updated != 1) {
            throw new ServiceException(VENEER_PLAN_OPERATION_STATUS_CHANGED, planId);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void withdrawProduce(VeneerPlanWithdrawProduceReqVO req) {
        Long planId = req == null ? null : req.getId();
        OrderVeneerPlanDO plan = validatePlanExists(planId);
        if (!Boolean.TRUE.equals(plan.getIsScheduled())
                || VeneerPlanStatusEnum.OPENING.equals(plan.getStatus())
                || VeneerPlanStatusEnum.OPENED.equals(plan.getStatus())) {
            throw new ServiceException(VENEER_PLAN_OPERATION_STATUS_CHANGED, planId);
        }
        int version = plan.getVersion();

        int updated = veneerPlanMapper.update(new LambdaUpdateWrapper<OrderVeneerPlanDO>()
                .eq(OrderVeneerPlanDO::getId, planId)
                .eq(OrderVeneerPlanDO::getIsScheduled, true)
                .notIn(OrderVeneerPlanDO::getStatus,
                        VeneerPlanStatusEnum.OPENING.getStatus(),
                        VeneerPlanStatusEnum.OPENED.getStatus())
                .eq(OrderVeneerPlanDO::getVersion, version)
                .set(OrderVeneerPlanDO::getIsScheduled, false)
                .set(OrderVeneerPlanDO::getVersion, version + 1));
        if (updated != 1) {
            throw new ServiceException(VENEER_PLAN_OPERATION_STATUS_CHANGED, planId);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void addPlanPlate(VeneerPlanAddPlateReqVO req) {
        Long planId = req == null ? null : req.getPlanId();
        OrderVeneerPlanDO plan = validatePlanStatuses(planId, PLAN_EDITABLE_STATUSES);
        List<Long> veneerIds = normalizePlanVeneerIds(
                req == null ? null : req.getVeneerIds());

        // 分批加载全部待排贴皮，避免大IN条件，并校验贴皮商品与目标排单一致。
        List<OrderPlateVeneerDO> veneers = new ArrayList<>();
        for (List<Long> idBatch : CollUtil.split(veneerIds, PLAN_BATCH_SIZE)) {
            veneers.addAll(veneerPlanMapper.selectPendingVeneerList(idBatch));
        }
        if (veneers.size() != veneerIds.size()) {
            throw new ServiceException(VENEER_PLAN_PLATE_STATUS_CHANGED);
        }
        for (OrderPlateVeneerDO veneer : veneers) {
            if (!Objects.equals(plan.getSourceVeneerId(), veneer.getSourceVeneerId())) {
                throw new ServiceException(VENEER_PLAN_PLATE_SOURCE_MISMATCH, veneer.getId());
            }
        }

        // 更新时再次限制待排状态并校验数量，防止并发重复加入排单。
        for (List<Long> idBatch : CollUtil.split(veneerIds, PLAN_BATCH_SIZE)) {
            int updated = veneerPlanMapper.updateVeneerPlanned(idBatch, planId);
            if (updated != idBatch.size()) {
                throw new ServiceException(VENEER_PLAN_PLATE_STATUS_CHANGED);
            }
        }

        refreshPlanStatistics(plan);
        refreshAffectedPlannedVeneerPlateCount(veneers);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void removePlanPlate(VeneerPlanRemovePlateReqVO req) {
        Long planId = req == null ? null : req.getPlanId();
        OrderVeneerPlanDO plan = validatePlanStatuses(planId, PLAN_EDITABLE_STATUSES);
        List<Long> veneerIds = normalizePlanVeneerIds(
                req == null ? null : req.getVeneerIds());

        // 仅允许移除当前排单中的有效贴皮，并保留归属范围用于后续重新统计。
        List<OrderPlateVeneerDO> veneers = new ArrayList<>();
        for (List<Long> idBatch : CollUtil.split(veneerIds, PLAN_BATCH_SIZE)) {
            veneers.addAll(veneerPlanMapper.selectPlannedVeneerList(idBatch, planId));
        }
        if (veneers.size() != veneerIds.size()) {
            throw new ServiceException(VENEER_PLAN_PLATE_STATUS_CHANGED);
        }

        for (List<Long> idBatch : CollUtil.split(veneerIds, PLAN_BATCH_SIZE)) {
            int updated = veneerPlanMapper.resetPlanVeneer(idBatch, planId);
            if (updated != idBatch.size()) {
                throw new ServiceException(VENEER_PLAN_PLATE_STATUS_CHANGED);
            }
        }

        VeneerPlanStatisticsDO statistics = veneerPlanMapper.selectPlanStatistics(planId);
        if (statistics == null || statistics.getVeneerNum() == null) {
            throw new ServiceException(VENEER_PLAN_PLATE_STATUS_CHANGED);
        }
        if (statistics.getVeneerNum() == 0) {
            int deleted = veneerPlanMapper.physicalDeleteEmptyPlan(
                    planId,
                    VeneerPlanStatusEnum.OPENING.getStatus(),
                    VeneerPlanStatusEnum.OPENED.getStatus(),
                    plan.getVersion());
            if (deleted != 1) {
                throw new ServiceException(VENEER_PLAN_PLATE_STATUS_CHANGED);
            }
        } else {
            updatePlanStatistics(plan, statistics);
        }
        refreshAffectedPlannedVeneerPlateCount(veneers);
    }

    @Override
    public PageResult<VeneerPlanPageRespVO> getPlanPage(VeneerPlanPageReqVO req) {
        IPage<VeneerPlanPageRespVO> page = veneerPlanMapper.selectPlanPage(
                new PageDTO<>(req.getPageNo(), req.getPageSize()),
                req);
        return new PageResult<>(page.getRecords(), page.getTotal());
    }

    @Override
    public List<VeneerPlanPlateRespVO> getPlanPlateList(Long planId) {
        if (planId == null) {
            return List.of();
        }
        return veneerPlanMapper.selectPlanPlateList(planId);
    }

    @Override
    public List<VeneerPlanSortRespVO> getPlanSortPage() {
        return veneerPlanMapper.selectPlanSortList(
                VeneerPlanStatusEnum.OPENED.getStatus());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void setPlanSort(List<VeneerPlanSortReqVO> req) {
        List<VeneerPlanSortReqVO> normalizedSortList = normalizePlanSortList(req);

        // 更新条件排除已开料状态并递增version；任意排单状态发生变化时整次排序回滚。
        for (List<VeneerPlanSortReqVO> batch
                : CollUtil.split(normalizedSortList, PLAN_SORT_BATCH_SIZE)) {
            int updated = veneerPlanMapper.batchUpdatePlanSort(
                    batch, VeneerPlanStatusEnum.OPENED.getStatus());
            if (updated != batch.size()) {
                throw new ServiceException(VENEER_PLAN_SORT_STATUS_CHANGED);
            }
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deletePlan(List<Long> planIds) {
        List<Long> normalizedPlanIds = normalizeDeletePlanIds(planIds);
        List<OrderVeneerPlanDO> plans = veneerPlanMapper.selectByIds(normalizedPlanIds);
        if (plans.size() != normalizedPlanIds.size()) {
            throw new ServiceException(VENEER_PLAN_DELETE_NOT_EXIST);
        }
        for (OrderVeneerPlanDO plan : plans) {
            if (VeneerPlanStatusEnum.OPENING.equals(plan.getStatus())
                    || VeneerPlanStatusEnum.OPENED.equals(plan.getStatus())) {
                throw new ServiceException(VENEER_PLAN_DELETE_NOT_ALLOWED, plan.getId());
            }
        }

        // 删除前读取受影响范围，恢复贴皮后仍可据此重新统计订单及订单材质数量。
        List<OrderPlateVeneerDO> affectedScopes =
                veneerPlanMapper.selectPlanAffectedScope(normalizedPlanIds);
        int expectedVeneerCount = 0;
        for (OrderVeneerPlanDO plan : plans) {
            if (plan.getVeneerNum() == null || plan.getVeneerNum() <= 0) {
                throw new ServiceException(VENEER_PLAN_DELETE_STATUS_CHANGED);
            }
            expectedVeneerCount += plan.getVeneerNum();
        }

        // 直接按排单ID恢复关联贴皮，避免将大量板件贴皮ID拼接到IN条件中。
        int resetCount = veneerPlanMapper.resetVeneerPlan(normalizedPlanIds);
        if (resetCount != expectedVeneerCount) {
            throw new ServiceException(VENEER_PLAN_DELETE_STATUS_CHANGED);
        }

        int deletedCount = veneerPlanMapper.physicalDeletePlan(
                normalizedPlanIds,
                VeneerPlanStatusEnum.OPENING.getStatus(),
                VeneerPlanStatusEnum.OPENED.getStatus());
        if (deletedCount != normalizedPlanIds.size()) {
            throw new ServiceException(VENEER_PLAN_DELETE_STATUS_CHANGED);
        }

        List<Long> affectedOrderIds = affectedScopes.stream()
                .map(OrderPlateVeneerDO::getOrderId)
                .distinct()
                .sorted()
                .toList();
        List<Long> affectedOrderGoodsIds = affectedScopes.stream()
                .map(OrderPlateVeneerDO::getOrderGoodsId)
                .distinct()
                .sorted()
                .toList();
        refreshPlannedVeneerPlateCount(affectedOrderIds, affectedOrderGoodsIds);
    }

    /**
     * 清洗并校验待删除贴皮排单ID列表。
     *
     * @param planIds 原始贴皮排单ID列表
     * @return 去空、去重并排序后的贴皮排单ID列表
     */
    private List<Long> normalizeDeletePlanIds(List<Long> planIds) {
        if (CollUtil.isEmpty(planIds) || planIds.stream().anyMatch(Objects::isNull)) {
            throw new ServiceException(VENEER_PLAN_DELETE_RECORD_REQUIRED);
        }
        List<Long> normalizedPlanIds = planIds.stream().distinct().sorted().toList();
        if (normalizedPlanIds.size() > PLAN_DELETE_LIMIT) {
            throw new ServiceException(
                    VENEER_PLAN_DELETE_LIMIT_EXCEEDED, PLAN_DELETE_LIMIT);
        }
        return normalizedPlanIds;
    }

    /**
     * 校验并整理贴皮排单优先级设置参数。
     *
     * @param req 原始优先级设置列表
     * @return 参数校验通过后的优先级设置列表
     */
    private List<VeneerPlanSortReqVO> normalizePlanSortList(List<VeneerPlanSortReqVO> req) {
        if (CollUtil.isEmpty(req)) {
            throw new ServiceException(VENEER_PLAN_SORT_RECORD_REQUIRED);
        }
        Set<Long> planIds = new HashSet<>(req.size());
        for (VeneerPlanSortReqVO sort : req) {
            if (sort == null || sort.getPlanId() == null || sort.getSort() == null) {
                throw new ServiceException(VENEER_PLAN_SORT_RECORD_REQUIRED);
            }
            if (!planIds.add(sort.getPlanId())) {
                throw new ServiceException(VENEER_PLAN_SORT_RECORD_DUPLICATE, sort.getPlanId());
            }
        }
        return req;
    }

    /**
     * 查询并校验贴皮排单存在。
     *
     * @param planId 贴皮排单ID
     * @return 当前贴皮排单
     */
    private OrderVeneerPlanDO validatePlanExists(Long planId) {
        if (planId == null) {
            throw new ServiceException(VENEER_PLAN_NOT_EXIST, planId);
        }
        OrderVeneerPlanDO plan = veneerPlanMapper.selectById(planId);
        if (plan == null) {
            throw new ServiceException(VENEER_PLAN_NOT_EXIST, planId);
        }
        return plan;
    }

    /**
     * 查询贴皮排单并校验其状态属于允许范围。
     *
     * @param planId 贴皮排单ID
     * @param expectedStatuses 当前操作允许的状态
     * @return 当前贴皮排单
     */
    private OrderVeneerPlanDO validatePlanStatuses(
            Long planId, List<Integer> expectedStatuses) {
        OrderVeneerPlanDO plan = validatePlanExists(planId);
        if (!expectedStatuses.contains(plan.getStatus())) {
            throw new ServiceException(VENEER_PLAN_OPERATION_STATUS_CHANGED, planId);
        }
        return plan;
    }

    /**
     * 清洗新增或移除排单的贴皮记录ID。
     *
     * @param veneerIds 原始板件贴皮记录ID列表
     * @return 去重并升序排列后的记录ID列表
     */
    private List<Long> normalizePlanVeneerIds(List<Long> veneerIds) {
        if (CollUtil.isEmpty(veneerIds) || veneerIds.stream().anyMatch(Objects::isNull)) {
            throw new ServiceException(VENEER_PLAN_PLATE_RECORD_REQUIRED);
        }
        List<Long> normalizedIds = veneerIds.stream()
                .distinct()
                .sorted()
                .toList();
        if (CollUtil.isEmpty(normalizedIds)) {
            throw new ServiceException(VENEER_PLAN_PLATE_RECORD_REQUIRED);
        }
        return normalizedIds;
    }

    /**
     * 从数据库重新统计并更新贴皮排单汇总信息。
     *
     * @param plan 更新前读取的贴皮排单
     */
    private void refreshPlanStatistics(OrderVeneerPlanDO plan) {
        VeneerPlanStatisticsDO statistics = veneerPlanMapper.selectPlanStatistics(plan.getId());
        if (statistics == null || statistics.getVeneerNum() == null
                || statistics.getVeneerArea() == null || statistics.getVeneerNum() <= 0) {
            throw new ServiceException(VENEER_PLAN_PLATE_STATUS_CHANGED);
        }
        updatePlanStatistics(plan, statistics);
    }

    /**
     * 使用状态及version乐观锁覆盖贴皮排单汇总信息。
     *
     * @param plan 更新前读取的贴皮排单
     * @param statistics 数据库当前聚合结果
     */
    private void updatePlanStatistics(
            OrderVeneerPlanDO plan, VeneerPlanStatisticsDO statistics) {
        int version = plan.getVersion();
        int updated = veneerPlanMapper.update(new LambdaUpdateWrapper<OrderVeneerPlanDO>()
                .eq(OrderVeneerPlanDO::getId, plan.getId())
                .in(OrderVeneerPlanDO::getStatus, PLAN_EDITABLE_STATUSES)
                .eq(OrderVeneerPlanDO::getVersion, version)
                .set(OrderVeneerPlanDO::getVeneerNum, statistics.getVeneerNum())
                .set(OrderVeneerPlanDO::getVeneerArea, statistics.getVeneerArea())
                .set(OrderVeneerPlanDO::getVersion, version + 1));
        if (updated != 1) {
            throw new ServiceException(VENEER_PLAN_PLATE_STATUS_CHANGED);
        }
    }

    /**
     * 根据本次新增或移除贴皮的归属范围重新统计订单及订单材质数量。
     *
     * @param veneers 本次操作的板件贴皮记录
     */
    private void refreshAffectedPlannedVeneerPlateCount(List<OrderPlateVeneerDO> veneers) {
        List<Long> orderIds = veneers.stream()
                .map(OrderPlateVeneerDO::getOrderId)
                .distinct()
                .sorted()
                .toList();
        List<Long> orderGoodsIds = veneers.stream()
                .map(OrderPlateVeneerDO::getOrderGoodsId)
                .distinct()
                .sorted()
                .toList();
        refreshPlannedVeneerPlateCount(orderIds, orderGoodsIds);
    }

    /**
     * 按贴皮商品编号批量查询组织内贴皮库主键，并校验编号唯一性和完整性。
     *
     * @param sourceVeneerNos 本次排单涉及的贴皮商品编号集合
     * @return 贴皮商品编号与贴皮库主键的映射
     */
    private Map<String, Long> loadSourceVeneerIdMap(Set<String> sourceVeneerNos) {
        List<VeneerDO> sourceVeneers = new ArrayList<>();
        List<String> sourceVeneerNoList = new ArrayList<>(sourceVeneerNos);
        for (List<String> sourceVeneerNoBatch
                : CollUtil.split(sourceVeneerNoList, PLAN_BATCH_SIZE)) {
            sourceVeneers.addAll(veneerMapper.selectList(
                    new LambdaQueryWrapper<VeneerDO>()
                            .select(VeneerDO::getId, VeneerDO::getVeneerNo)
                            .in(VeneerDO::getVeneerNo, sourceVeneerNoBatch)));
        }
        Map<String, Long> sourceVeneerIdMap = new HashMap<>(sourceVeneers.size());
        for (VeneerDO sourceVeneer : sourceVeneers) {
            Long previousId = sourceVeneerIdMap.putIfAbsent(
                    sourceVeneer.getVeneerNo(), sourceVeneer.getId());
            if (previousId != null) {
                throw new ServiceException(
                        VENEER_PLAN_SOURCE_NO_DUPLICATE, sourceVeneer.getVeneerNo());
            }
        }
        for (String sourceVeneerNo : sourceVeneerNos) {
            if (!sourceVeneerIdMap.containsKey(sourceVeneerNo)) {
                throw new ServiceException(VENEER_PLAN_SOURCE_NO_NOT_EXIST, sourceVeneerNo);
            }
        }
        return sourceVeneerIdMap;
    }

    /**
     * 根据板件贴皮当前排单状态，重新统计订单和订单材质的已排贴皮板件数量。
     *
     * @param orderIds 订单ID列表
     * @param orderGoodsIds {@code order_goods} 主键列表
     */
    private void refreshPlannedVeneerPlateCount(List<Long> orderIds, List<Long> orderGoodsIds) {
        refreshOrderPlannedVeneerPlateCount(orderIds);
        refreshGoodsPlannedVeneerPlateCount(orderGoodsIds);
    }

    /**
     * 分批重新统计并覆盖订单的已排贴皮板件数量，更新时使用 version 进行乐观锁校验。
     *
     * @param orderIds 订单ID列表
     */
    private void refreshOrderPlannedVeneerPlateCount(List<Long> orderIds) {
        for (List<Long> orderIdBatch : CollUtil.split(orderIds, PLAN_STAT_BATCH_SIZE)) {
            Map<Long, OrderDO> orderMap = new HashMap<>();
            for (OrderDO order : orderMapper.selectByIds(orderIdBatch)) {
                orderMap.put(order.getId(), order);
            }
            Map<Long, Integer> countMap = convertPlannedVeneerPlateCount(
                    veneerPlanMapper.selectOrderPlannedVeneerPlateCount(orderIdBatch));

            for (Long orderId : orderIdBatch) {
                OrderDO order = orderMap.get(orderId);
                if (order == null) {
                    throw new ServiceException(ORDER_PLATE_UPDATE_CONCURRENCY_ERROR, orderId);
                }
                int version = order.getVersion();
                int updated = orderMapper.update(new LambdaUpdateWrapper<OrderDO>()
                        .eq(OrderDO::getId, orderId)
                        .eq(OrderDO::getVersion, version)
                        .set(OrderDO::getPlannedVeneerPlateNum,
                                countMap.getOrDefault(orderId, 0))
                        .set(OrderDO::getVersion, version + 1));
                if (updated == 0) {
                    throw new ServiceException(ORDER_PLATE_UPDATE_CONCURRENCY_ERROR, orderId);
                }
            }
        }
    }

    /**
     * 分批重新统计并覆盖订单材质的已排贴皮板件数量，更新时使用 version 进行乐观锁校验。
     *
     * @param orderGoodsIds {@code order_goods} 主键列表
     */
    private void refreshGoodsPlannedVeneerPlateCount(List<Long> orderGoodsIds) {
        for (List<Long> orderGoodsIdBatch : CollUtil.split(orderGoodsIds, PLAN_STAT_BATCH_SIZE)) {
            Map<Long, GoodsDO> goodsMap = new HashMap<>();
            for (GoodsDO goods : goodsMapper.selectByIds(orderGoodsIdBatch)) {
                goodsMap.put(goods.getId(), goods);
            }
            Map<Long, Integer> countMap = convertPlannedVeneerPlateCount(
                    veneerPlanMapper.selectGoodsPlannedVeneerPlateCount(orderGoodsIdBatch));

            for (Long orderGoodsId : orderGoodsIdBatch) {
                GoodsDO goods = goodsMap.get(orderGoodsId);
                if (goods == null) {
                    throw new ServiceException(GOODS_PLATE_UPDATE_CONCURRENCY_ERROR, orderGoodsId);
                }
                int version = goods.getVersion();
                int updated = goodsMapper.update(new LambdaUpdateWrapper<GoodsDO>()
                        .eq(GoodsDO::getId, orderGoodsId)
                        .eq(GoodsDO::getVersion, version)
                        .set(GoodsDO::getPlannedVeneerPlateNum,
                                countMap.getOrDefault(orderGoodsId, 0))
                        .set(GoodsDO::getVersion, version + 1));
                if (updated == 0) {
                    throw new ServiceException(GOODS_PLATE_UPDATE_CONCURRENCY_ERROR, orderGoodsId);
                }
            }
        }
    }

    /**
     * 将数据库聚合结果转换为目标ID与已排贴皮板件数量的映射。
     *
     * @param databaseCounts 数据库聚合统计结果
     * @return 目标ID与已排贴皮板件数量的映射
     */
    private Map<Long, Integer> convertPlannedVeneerPlateCount(
            List<VeneerPlannedPlateCountDO> databaseCounts) {
        Map<Long, Integer> countMap = new HashMap<>();
        for (VeneerPlannedPlateCountDO databaseCount : databaseCounts) {
            countMap.put(databaseCount.getTargetId(), databaseCount.getPlannedVeneerPlateNum());
        }
        return countMap;
    }

    /**
     * 校验并整理前端提交的贴皮商品编号分组。
     * 同一贴皮商品编号只能出现一个分组，同一板件贴皮记录也只能属于一个分组。
     *
     * @param groups 前端按贴皮商品编号整理的分组列表
     * @return 以贴皮商品编号为键、板件贴皮记录ID列表为值的有序分组
     */
    private Map<String, List<Long>> normalizeGroups(List<VeneerPlanGroupReqVO> groups) {
        if (CollUtil.isEmpty(groups)) {
            throw new ServiceException(VENEER_PLAN_GROUP_REQUIRED);
        }
        Map<String, List<Long>> groupIdMap = new LinkedHashMap<>(groups.size());
        Set<Long> allVeneerIds = new HashSet<>();
        for (VeneerPlanGroupReqVO group : groups) {
            if (group == null || StrUtil.isBlank(group.getSourceVeneerNo())) {
                throw new ServiceException(VENEER_PLAN_GROUP_SOURCE_NO_REQUIRED);
            }
            String sourceVeneerNo = StrUtil.trim(group.getSourceVeneerNo());
            if (groupIdMap.containsKey(sourceVeneerNo)) {
                throw new ServiceException(VENEER_PLAN_GROUP_DUPLICATE, sourceVeneerNo);
            }
            List<Long> groupIds = normalizeVeneerIds(group.getVeneerIds());
            for (Long veneerId : groupIds) {
                if (!allVeneerIds.add(veneerId)) {
                    throw new ServiceException(VENEER_PLAN_RECORD_DUPLICATE, veneerId);
                }
            }
            groupIdMap.put(sourceVeneerNo, groupIds);
        }
        return groupIdMap;
    }

    /**
     * 清洗并校验贴皮记录ID列表。
     *
     * @param veneerIds 原始贴皮记录ID列表
     * @return 去空、去重并排序后的ID列表
     */
    private List<Long> normalizeVeneerIds(List<Long> veneerIds) {
        if (CollUtil.isEmpty(veneerIds)) {
            throw new ServiceException(VENEER_PLAN_RECORD_REQUIRED);
        }
        List<Long> normalizedIds = veneerIds.stream()
                .filter(Objects::nonNull)
                .distinct()
                .sorted()
                .toList();
        if (CollUtil.isEmpty(normalizedIds)) {
            throw new ServiceException(VENEER_PLAN_RECORD_REQUIRED);
        }
        return normalizedIds;
    }

    /**
     * 根据原板尺寸和外扩余量计算当前贴皮排单的总面积。
     * A/B面作为两条贴皮记录分别累计，结果单位为平方米。
     *
     * @param veneers 同一贴皮商品编号下的贴皮记录
     * @return 保留四位小数的贴皮总面积
     */
    private BigDecimal calculateVeneerArea(List<OrderPlateVeneerDO> veneers) {
        BigDecimal totalSquareMillimeter = BigDecimal.ZERO;
        for (OrderPlateVeneerDO veneer : veneers) {
            BigDecimal length = veneer.getPlateLength();
            BigDecimal width = veneer.getPlateWidth();
            BigDecimal outerMargin = Objects.requireNonNullElse(veneer.getOuterMargin(), BigDecimal.ZERO);
            if (length == null || width == null || length.signum() <= 0 || width.signum() <= 0
                    || outerMargin.signum() < 0) {
                throw new ServiceException(VENEER_PLAN_AREA_DATA_INVALID, veneer.getId());
            }
            BigDecimal finalLength = length.add(outerMargin.multiply(OUTER_MARGIN_DOUBLE));
            BigDecimal finalWidth = width.add(outerMargin.multiply(OUTER_MARGIN_DOUBLE));
            totalSquareMillimeter = totalSquareMillimeter.add(finalLength.multiply(finalWidth));
        }
        return totalSquareMillimeter.divide(
                SQUARE_MILLIMETER_PER_SQUARE_METER, 4, RoundingMode.HALF_UP);
    }
}
