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

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.PageDTO;
import com.cf.imes.framework.common.enums.PlatePlaceHoleEnum;
import com.cf.imes.framework.common.exception.ServiceException;
import com.cf.imes.framework.common.pojo.PageResult;
import com.cf.imes.framework.common.util.date.LocalDateTimeUtils;
import com.cf.imes.framework.common.util.json.JsonUtils;
import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerDecomposeCreateReqVO;
import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerDecomposeGoodsPageReqVO;
import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerDecomposeGoodsRespVO;
import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerDecomposeOrderPageReqVO;
import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerDecomposeOrderRespVO;
import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerDecomposePlateDataRespVO;
import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerDecomposePlatePageReqVO;
import com.cf.imes.module.executor.controller.admin.veneer.vo.VeneerDecomposePlateRespVO;
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.ordermodel.OrderModelDO;
import com.cf.imes.module.executor.dal.dataobject.plate.PlateDO;
import com.cf.imes.module.executor.dal.dataobject.veneer.OrderPlateVeneerDO;
import com.cf.imes.module.executor.dal.dataobject.veneer.VeneerDecomposedPlateCountDO;
import com.cf.imes.module.executor.dal.dataobject.veneer.VeneerDO;
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.OrderPlateVeneerMapper;
import com.cf.imes.module.executor.dal.mysql.veneer.VeneerDecomposeMapper;
import com.cf.imes.module.executor.dal.mysql.veneer.VeneerMapper;
import com.cf.imes.module.executor.enums.VeneerPlateSurfaceEnum;
import com.cf.imes.module.executor.enums.VeneerSideEnum;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import static com.cf.imes.framework.common.pojo.PageParam.PAGE_SIZE_NONE;
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_DECOMPOSE_PLATE_STATUS_CHANGED;
import static com.cf.imes.module.executor.enums.ErrorCodeConstants.VENEER_DECOMPOSE_SCOPE_REQUIRED;
import static com.cf.imes.module.executor.enums.ErrorCodeConstants.VENEER_DECOMPOSE_SIDE_REQUIRED;
import static com.cf.imes.module.executor.enums.ErrorCodeConstants.VENEER_NOT_EXIST;

/**
 * 贴皮分解 Service 实现。
 */
@Service
public class VeneerDecomposeServiceImpl implements VeneerDecomposeService {

    /**
     * 板件查询、状态更新及贴皮记录插入的单批处理数量。
     */
    private static final int DECOMPOSE_BATCH_SIZE = 1000;

    /**
     * 订单及订单板材分解数量重新统计的单批处理数量。
     */
    private static final int DECOMPOSE_STAT_BATCH_SIZE = 100;

    @Resource
    private VeneerDecomposeMapper veneerDecomposeMapper;

    @Resource
    private OrderPlateVeneerMapper orderPlateVeneerMapper;

    @Resource
    private VeneerMapper veneerMapper;

    @Resource
    private OrderMapper orderMapper;

    @Resource
    private GoodsMapper goodsMapper;

    @Override
    public PageResult<VeneerDecomposeOrderRespVO> getOrderPage(VeneerDecomposeOrderPageReqVO req) {
        PageDTO<VeneerDecomposeOrderRespVO> page = new PageDTO<>(req.getPageNo(), req.getPageSize());
        IPage<Long> rootPage = veneerDecomposeMapper.selectRootOrderIdList(
                page, req, LocalDateTimeUtils.generateTimeSection(req.getOrderDate()));
        List<Long> rootIds = rootPage.getRecords();
        if (CollUtil.isEmpty(rootIds)) {
            return new PageResult<>(List.of(), rootPage.getTotal());
        }
        IPage<VeneerDecomposeOrderRespVO> result = veneerDecomposeMapper.selectOrderList(
                new PageDTO<>(req.getPageNo(), PAGE_SIZE_NONE), rootIds);
        List<VeneerDecomposeOrderRespVO> rootOrders = result.getRecords();
        fillOrderChildren(rootOrders, veneerDecomposeMapper.selectOrderChildList(
                rootIds, req, LocalDateTimeUtils.generateTimeSection(req.getOrderDate())));
        return new PageResult<>(rootOrders, rootPage.getTotal());
    }

    /**
     * 按父订单ID分组并回填子订单，分页数量始终以根订单为准。
     *
     * @param rootOrders 当前页根订单
     * @param childOrders 当前页根订单下符合贴皮分解条件的子订单
     */
    private void fillOrderChildren(List<VeneerDecomposeOrderRespVO> rootOrders,
                                   List<VeneerDecomposeOrderRespVO> childOrders) {
        if (CollUtil.isEmpty(rootOrders) || CollUtil.isEmpty(childOrders)) {
            return;
        }
        Map<Long, List<VeneerDecomposeOrderRespVO>> childMap = new HashMap<>();
        for (VeneerDecomposeOrderRespVO childOrder : childOrders) {
            childMap.computeIfAbsent(childOrder.getParentId(), key -> new ArrayList<>()).add(childOrder);
        }
        for (VeneerDecomposeOrderRespVO rootOrder : rootOrders) {
            rootOrder.setChild(childMap.get(rootOrder.getOrderId()));
        }
    }

    @Override
    public PageResult<VeneerDecomposeGoodsRespVO> getGoodsPage(VeneerDecomposeGoodsPageReqVO req) {
        IPage<VeneerDecomposeGoodsRespVO> result = veneerDecomposeMapper.selectGoodsPage(
                new PageDTO<>(req.getPageNo(), req.getPageSize()), req);
        return new PageResult<>(result.getRecords(), result.getTotal());
    }

    @Override
    public VeneerDecomposePlateDataRespVO getOrderPlateList(VeneerDecomposePlatePageReqVO req) {
        if (CollUtil.isEmpty(req.getOrderIds())) {
            return buildPlateData(List.of());
        }
        return buildPlateData(veneerDecomposeMapper.selectOrderPlateList(req));
    }

    @Override
    public VeneerDecomposePlateDataRespVO getGoodsPlateList(VeneerDecomposePlatePageReqVO req) {
        if (CollUtil.isEmpty(req.getGoodsIds())) {
            return buildPlateData(List.of());
        }
        return buildPlateData(veneerDecomposeMapper.selectGoodsPlateList(req));
    }

    /**
     * 合并板件重复记录，并从解压后的造型 JSON 生成板件造型列表。
     *
     * @param rawPlates 数据库查询得到的板件原始列表
     * @return 板件及板件造型数据
     */
    private VeneerDecomposePlateDataRespVO buildPlateData(List<VeneerDecomposePlateRespVO> rawPlates) {
        List<VeneerDecomposePlateRespVO> plates = mergePlateDetails(rawPlates);
        List<OrderModelDO> plateModels = plates.stream()
                .map(VeneerDecomposePlateRespVO::getModelData)
                .filter(StrUtil::isNotBlank)
                .map(modelData -> JsonUtils.parseObject(modelData, OrderModelDO.class))
                .toList();
        return VeneerDecomposePlateDataRespVO.builder()
                .plateList(plates)
                .plateModels(plateModels)
                .build();
    }

    /**
     * 一块板可关联多个加工组，查询结果按板件主键合并。
     * 加工组名称去重后拼接，既满足前端板件筛选字段，
     * 又保证一块板只返回一条记录。
     *
     * @param plates 数据库查询得到的板件原始列表
     * @return 按板件主键合并后的列表
     */
    private List<VeneerDecomposePlateRespVO> mergePlateDetails(List<VeneerDecomposePlateRespVO> plates) {
        if (CollUtil.isEmpty(plates)) {
            return plates;
        }
        Map<Long, VeneerDecomposePlateRespVO> plateMap = new LinkedHashMap<>();
        Map<Long, Set<String>> groupNameMap = new HashMap<>();
        for (VeneerDecomposePlateRespVO plate : plates) {
            plateMap.putIfAbsent(plate.getPlateId(), plate);
            addName(groupNameMap, plate.getPlateId(), plate.getProcessGroupName());
        }

        for (Map.Entry<Long, VeneerDecomposePlateRespVO> entry : plateMap.entrySet()) {
            Long plateId = entry.getKey();
            VeneerDecomposePlateRespVO plate = entry.getValue();
            plate.setProcessGroupName(joinNames(groupNameMap.get(plateId)));
        }
        return new ArrayList<>(plateMap.values());
    }

    /**
     * 将非空名称加入指定板件的有序去重集合。
     *
     * @param nameMap 板件名称集合映射
     * @param plateId 板件ID
     * @param name 待合并名称
     */
    private void addName(Map<Long, Set<String>> nameMap, Long plateId, String name) {
        if (plateId == null || name == null || name.isBlank()) {
            return;
        }
        nameMap.computeIfAbsent(plateId, key -> new LinkedHashSet<>()).add(name);
    }

    /**
     * 将有序名称集合拼接为前端展示和筛选值。
     *
     * @param names 名称集合
     * @return 逗号分隔名称；无名称时返回 {@code null}
     */
    private String joinNames(Set<String> names) {
        return CollUtil.isEmpty(names) ? null : String.join(",", names);
    }

    /**
     * 执行贴皮分解：一块原板按所选 A/B 面生成一条或两条贴皮记录，原板计数只增加一次。
     *
     * @param req 贴皮分解参数
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void decompose(VeneerDecomposeCreateReqVO req) {
        // orderIds 非空表示按订单分解；否则必须指定板材商品编码。
        if (!req.isByOrder() && (req.getGoodsId() == null || req.getGoodsId().isBlank())) {
            throw new ServiceException(VENEER_DECOMPOSE_SCOPE_REQUIRED);
        }
        // A、B 面允许分别选择贴皮，但至少需要选择一面。
        if (req.getAVeneerId() == null && req.getBVeneerId() == null) {
            throw new ServiceException(VENEER_DECOMPOSE_SIDE_REQUIRED);
        }

        List<String> plateNos = req.getPlateNos().stream().distinct().sorted().toList();
        // 板件编号分批查询，避免一次生成过长的 IN 条件。
        List<PlateDO> plates = new ArrayList<>();
        for (List<String> plateNoBatch : CollUtil.split(plateNos, DECOMPOSE_BATCH_SIZE)) {
            plates.addAll(veneerDecomposeMapper.selectUndecomposedPlateList(req, plateNoBatch));
        }
        // 数量不一致通常表示板件已被其他用户分解，阻止本次请求继续覆盖状态。
        if (plates.size() != plateNos.size()) {
            throw new ServiceException(VENEER_DECOMPOSE_PLATE_STATUS_CHANGED);
        }

        // 汇总受影响订单，分解完成后以数据库当前状态重新统计已分解板件数量。
        List<Long> affectedOrderIds = plates.stream().map(PlateDO::getOrderId).distinct().sorted().toList();

        // 从贴皮库加载 A/B 面贴皮，并将贴皮属性以快照形式写入板件贴皮表。
        Map<Long, VeneerDO> sourceVeneerMap = loadSourceVeneers(req);
        List<OrderPlateVeneerDO> plateVeneers = new ArrayList<>();
        for (PlateDO plate : plates) {
            if (req.getAVeneerId() != null) {
                plateVeneers.add(buildPlateVeneer(
                        plate, sourceVeneerMap.get(req.getAVeneerId()),
                        VeneerSideEnum.A, req.getOuterMargin()));
            }
            if (req.getBVeneerId() != null) {
                plateVeneers.add(buildPlateVeneer(
                        plate, sourceVeneerMap.get(req.getBVeneerId()),
                        VeneerSideEnum.B, req.getOuterMargin()));
            }
        }

        // 原板状态分批更新；租户插件追加 organ_id 后由 MyCat 按组织路由。
        List<Long> plateIds = plates.stream().map(PlateDO::getId).toList();
        for (List<Long> plateIdBatch : CollUtil.split(plateIds, DECOMPOSE_BATCH_SIZE)) {
            // 条件中包含 is_decomposed=false，通过每批更新行数进行第二次并发校验。
            int updated = veneerDecomposeMapper.updatePlateDecomposed(plateIdBatch);
            if (updated != plateIdBatch.size()) {
                throw new ServiceException(VENEER_DECOMPOSE_PLATE_STATUS_CHANGED);
            }
        }

        // A/B 贴皮记录分批插入，控制单条 INSERT 的参数数量和报文大小。
        for (List<OrderPlateVeneerDO> plateVeneerBatch
                : CollUtil.split(plateVeneers, DECOMPOSE_BATCH_SIZE)) {
            orderPlateVeneerMapper.insertBatch(plateVeneerBatch);
        }

        // 以 order_plate 当前状态重新统计，不使用本次请求数量做增量计算。
        List<Long> affectedOrderGoodsIds = plates.stream()
                .map(PlateDO::getGoodsId).distinct().sorted().toList();
        refreshDecomposedPlateCount(affectedOrderIds, affectedOrderGoodsIds);
    }

    /**
     * 根据原板件当前分解状态重新统计订单和订单板材的已分解板件数量。
     *
     * @param orderIds 订单ID列表
     * @param orderGoodsIds {@code order_goods} 主键列表
     */
    @Override
    public void refreshDecomposedPlateCount(List<Long> orderIds, List<Long> orderGoodsIds) {
        refreshOrderDecomposedPlateCount(orderIds);
        refreshGoodsDecomposedPlateCount(orderGoodsIds);
    }

    /**
     * 从 order_plate 重新统计并覆盖订单的已分解贴皮板件数量。
     * 每批处理 100 个订单，更新时使用 version 进行乐观锁校验。
     *
     * @param orderIds 需要重新统计的订单ID列表
     */
    private void refreshOrderDecomposedPlateCount(List<Long> orderIds) {
        for (List<Long> orderIdBatch : CollUtil.split(orderIds, DECOMPOSE_STAT_BATCH_SIZE)) {
            // 先读取当前版本，后续任何并发修改都会使版本条件更新失败。
            Map<Long, OrderDO> orderMap = new HashMap<>();
            for (OrderDO order : orderMapper.selectByIds(orderIdBatch)) {
                orderMap.put(order.getId(), order);
            }
            List<VeneerDecomposedPlateCountDO> databaseCounts =
                    veneerDecomposeMapper.selectOrderDecomposedPlateCount(orderIdBatch);
            Map<Long, Integer> countMap = convertDecomposedPlateCount(databaseCounts);
            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 update = orderMapper.update(new LambdaUpdateWrapper<OrderDO>()
                        .eq(OrderDO::getId, orderId)
                        .eq(OrderDO::getVersion, version)
                        .set(OrderDO::getDecomposedVeneerPlateNum, countMap.getOrDefault(orderId, 0))
                        .set(OrderDO::getVersion, version + 1));
                if (update == 0) {
                    throw new ServiceException(ORDER_PLATE_UPDATE_CONCURRENCY_ERROR, orderId);
                }
            }
        }
    }

    /**
     * 从 order_plate 重新统计并覆盖订单板材的已分解贴皮板件数量。
     * 每批处理 100 条订单板材，更新时使用 version 进行乐观锁校验。
     *
     * @param orderGoodsIds 需要重新统计的 {@code order_goods} 主键列表
     */
    private void refreshGoodsDecomposedPlateCount(List<Long> orderGoodsIds) {
        for (List<Long> orderGoodsIdBatch : CollUtil.split(orderGoodsIds, DECOMPOSE_STAT_BATCH_SIZE)) {
            // 先读取当前版本，防止用旧统计结果覆盖其他业务刚完成的更新。
            Map<Long, GoodsDO> goodsMap = new HashMap<>();
            for (GoodsDO goods : goodsMapper.selectByIds(orderGoodsIdBatch)) {
                goodsMap.put(goods.getId(), goods);
            }
            List<VeneerDecomposedPlateCountDO> databaseCounts =
                    veneerDecomposeMapper.selectGoodsDecomposedPlateCount(orderGoodsIdBatch);
            Map<Long, Integer> countMap = convertDecomposedPlateCount(databaseCounts);
            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 update = goodsMapper.update(new LambdaUpdateWrapper<GoodsDO>()
                        .eq(GoodsDO::getId, orderGoodsId)
                        .eq(GoodsDO::getVersion, version)
                        .set(GoodsDO::getDecomposedVeneerPlateNum,
                                countMap.getOrDefault(orderGoodsId, 0))
                        .set(GoodsDO::getVersion, version + 1));
                if (update == 0) {
                    throw new ServiceException(GOODS_PLATE_UPDATE_CONCURRENCY_ERROR, orderGoodsId);
                }
            }
        }
    }

    /**
     * 将数据库聚合结果转换为目标ID与已分解数量的映射。
     * 未出现在映射中的目标由调用方按 0 处理。
     *
     * @param databaseCounts 数据库聚合统计结果
     * @return 目标ID与已分解板件数量的映射
     */
    private Map<Long, Integer> convertDecomposedPlateCount(
            List<VeneerDecomposedPlateCountDO> databaseCounts) {
        Map<Long, Integer> countMap = new HashMap<>();
        for (VeneerDecomposedPlateCountDO databaseCount : databaseCounts) {
            countMap.put(databaseCount.getTargetId(), databaseCount.getDecomposedPlateNum());
        }
        return countMap;
    }

    /**
     * 加载 A/B 面选中的贴皮库数据，并按贴皮库主键构建映射。
     *
     * @param req 贴皮分解参数
     * @return 贴皮库主键与贴皮数据的映射
     */
    private Map<Long, VeneerDO> loadSourceVeneers(VeneerDecomposeCreateReqVO req) {
        // A、B 可能选择同一贴皮，先去重，避免重复查询贴皮库。
        Set<Long> veneerIds = new HashSet<>();
        if (req.getAVeneerId() != null) {
            veneerIds.add(req.getAVeneerId());
        }
        if (req.getBVeneerId() != null) {
            veneerIds.add(req.getBVeneerId());
        }
        Map<Long, VeneerDO> result = new HashMap<>();
        for (Long veneerId : veneerIds) {
            VeneerDO veneer = veneerMapper.selectById(veneerId);
            if (veneer == null) {
                throw new ServiceException(VENEER_NOT_EXIST);
            }
            result.put(veneerId, veneer);
        }
        return result;
    }

    /**
     * 根据原板件、贴皮逻辑面及贴皮库快照构建一条板件贴皮记录。
     *
     * @param plate 原板件
     * @param sourceVeneer 选中的贴皮库数据
     * @param veneerSide 贴皮逻辑面枚举
     * @param outerMargin 外扩余量
     * @return 待保存的板件贴皮记录
     */
    private OrderPlateVeneerDO buildPlateVeneer(PlateDO plate, VeneerDO sourceVeneer,
                                                 VeneerSideEnum veneerSide, BigDecimal outerMargin) {
        int plateSurface = resolvePlateSurface(plate.getHoleArrange(), veneerSide);
        return OrderPlateVeneerDO.builder()
                .organId(plate.getOrganId())
                .orderId(plate.getOrderId())
                .orderGoodsId(plate.getGoodsId())
                .orderPlateId(plate.getId())
                .plateNo(plate.getPlateNo())
                .plateName(plate.getName())
                .plateLength(plate.getHeight())
                .plateWidth(plate.getWidth())
                .plateThickness(plate.getThickness())
                .holeArrange(plate.getHoleArrange())
                .veneerSide(veneerSide.getSide())
                .plateSurface(plateSurface)
                .veneerNo(plate.getPlateNo() + veneerSide.getSide())
                .outerMargin(outerMargin)
                .planId(0L)
                .sourceVeneerId(sourceVeneer.getId())
                .sourceVeneerNo(sourceVeneer.getVeneerNo())
                .veneerName(sourceVeneer.getVeneerName())
                .productLevel(sourceVeneer.getProductLevel())
                .color(ObjectUtil.defaultIfNull(sourceVeneer.getColor(), ""))
                .material(ObjectUtil.defaultIfNull(sourceVeneer.getMaterial(), ""))
                .build();
    }

    /**
     * 根据 WebCAD 排版面和贴皮逻辑面确定实际板件面。
     * 反面排版时 A 面对应反面、B 面对应正面；正面或随意面排版时关系相反。
     *
     * @param holeArrange WebCAD 排版面
     * @param veneerSide 贴皮逻辑面枚举
     * @return 实际板件面
     */
    private int resolvePlateSurface(Integer holeArrange, VeneerSideEnum veneerSide) {
        boolean reverseArrange = PlatePlaceHoleEnum.BACK.equals(holeArrange);
        if (VeneerSideEnum.A == veneerSide) {
            return reverseArrange
                    ? VeneerPlateSurfaceEnum.BACK.getSurface()
                    : VeneerPlateSurfaceEnum.FRONT.getSurface();
        }
        return reverseArrange
                ? VeneerPlateSurfaceEnum.FRONT.getSurface()
                : VeneerPlateSurfaceEnum.BACK.getSurface();
    }
}
