package com.ruoyi.asset.service.impl;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.ruoyi.asset.constant.AssetStatus;
import com.ruoyi.asset.constant.BorrowOrderStatus;
import com.ruoyi.asset.domain.AmsAsset;
import com.ruoyi.asset.domain.AmsBorrowOrder;
import com.ruoyi.asset.domain.AmsBorrowOrderItem;
import com.ruoyi.asset.domain.AmsWarehouse;
import com.ruoyi.asset.domain.AmsAssetLocation;
import com.ruoyi.asset.domain.AssetTransitionContext;
import com.ruoyi.asset.mapper.AmsAssetMapper;
import com.ruoyi.asset.mapper.AmsBorrowOrderMapper;
import com.ruoyi.asset.service.IAmsBorrowOrderService;
import com.ruoyi.asset.service.IAssetStatusTransitionService;
import com.ruoyi.asset.service.IAmsWarehouseService;
import com.ruoyi.asset.service.IAmsAssetLocationService;
import com.ruoyi.common.core.domain.entity.SysDept;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.text.Convert;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.system.service.ISysCodeRuleService;
import com.ruoyi.system.service.ISysDeptService;
import com.ruoyi.system.service.ISysUserService;

/**
 * 借用归还管理Service业务层处理
 *
 * 核心职责：处理资产从在库（IN_STOCK）到借出（BORROWED），再从借出归位到在库的完整生命周期。
 * 业务设计亮点：
 * 1. 【防死锁设计】：所有悲观锁定资产的操作，均在应用层先对资产ID进行升序排列，统一加锁顺序。
 * 2. 【防脏写快照核对】：在提交和确认借出节点，强制核对最新的物理仓位，防止基于过期仓位快照流转。
 * 3. 【逻辑删除改造】：主表及明细表的删除操作全面逻辑删除，并过滤 del_flag = '0' 记录，仅限草稿状态允许删除。
 *
 * @author Yangk
 * @date 2026-06-16
 */
@Service
public class AmsBorrowOrderServiceImpl implements IAmsBorrowOrderService
{
    private static final String BORROW_ORDER_RULE = "BORROW_ORDER";
    private static final String DEL_FLAG_NORMAL = "0";

    @Autowired
    private AmsBorrowOrderMapper amsBorrowOrderMapper;

    @Autowired
    private AmsAssetMapper amsAssetMapper;

    @Autowired
    private ISysCodeRuleService sysCodeRuleService;

    @Autowired
    private ISysDeptService sysDeptService;

    @Autowired
    private ISysUserService sysUserService;

    @Autowired
    private IAssetStatusTransitionService assetStatusTransitionService;

    @Autowired
    private IAmsWarehouseService amsWarehouseService;

    @Autowired
    private IAmsAssetLocationService amsAssetLocationService;

    /**
     * 查询借用归还管理
     */
    @Override
    public AmsBorrowOrder selectAmsBorrowOrderByOrderId(Long orderId)
    {
        return amsBorrowOrderMapper.selectAmsBorrowOrderByOrderId(orderId);
    }

    /**
     * 查询借用归还管理列表
     */
    @Override
    public List<AmsBorrowOrder> selectAmsBorrowOrderList(AmsBorrowOrder amsBorrowOrder)
    {
        return amsBorrowOrderMapper.selectAmsBorrowOrderList(amsBorrowOrder);
    }

    /**
     * 新增借用归还管理草稿
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int insertAmsBorrowOrder(AmsBorrowOrder amsBorrowOrder)
    {
        // 1. 基础报文校验
        validateOrderRequest(amsBorrowOrder);

        // 2. 校验借用责任人与部门，回填快照
        fillBorrowerSnapshots(amsBorrowOrder);

        // 3. 生成唯一单号，初始化单据受控字段
        amsBorrowOrder.setBorrowNo(sysCodeRuleService.nextCode(BORROW_ORDER_RULE));
        amsBorrowOrder.setConfirmUserId(null);
        amsBorrowOrder.setConfirmUserName(null);
        amsBorrowOrder.setConfirmTime(null);
        amsBorrowOrder.setRejectReason(null);
        amsBorrowOrder.setOrderStatus(BorrowOrderStatus.DRAFT);
        amsBorrowOrder.setDelFlag(DEL_FLAG_NORMAL);
        amsBorrowOrder.setCreateTime(DateUtils.getNowDate());

        // 4. 填充明细资产历史仓位快照，执行资产防重和并发占用校验
        fillOrderSnapshots(amsBorrowOrder, null);

        // 5. 保存主表及子表明细
        int rows = amsBorrowOrderMapper.insertAmsBorrowOrder(amsBorrowOrder);
        if (rows != 1 || amsBorrowOrder.getOrderId() == null)
        {
            throw new ServiceException("借用单保存失败");
        }
        insertAmsBorrowOrderItems(amsBorrowOrder);
        return rows;
    }

    /**
     * 修改借用归还管理草稿
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int updateAmsBorrowOrder(AmsBorrowOrder amsBorrowOrder)
    {
        if (amsBorrowOrder == null || amsBorrowOrder.getOrderId() == null)
        {
            throw new ServiceException("单据ID不能为空");
        }

        // 1. 悲观锁锁定主表记录，强校验状态必须为 DRAFT。非草稿状态严禁修改
        AmsBorrowOrder current = requireOrderForUpdate(amsBorrowOrder.getOrderId(), BorrowOrderStatus.DRAFT,
                "仅草稿状态的借用单允许修改");

        validateOrderRequest(amsBorrowOrder);

        // 2. 强行还原单号等受控属性，清空审批人及驳回信息，强制复归为 DRAFT
        amsBorrowOrder.setBorrowNo(current.getBorrowNo());
        amsBorrowOrder.setBorrowUserId(current.getBorrowUserId());
        amsBorrowOrder.setBorrowUserName(current.getBorrowUserName());
        amsBorrowOrder.setBorrowDeptId(current.getBorrowDeptId());
        amsBorrowOrder.setBorrowDeptName(current.getBorrowDeptName());
        amsBorrowOrder.setConfirmUserId(null);
        amsBorrowOrder.setConfirmUserName(null);
        amsBorrowOrder.setConfirmTime(null);
        amsBorrowOrder.setRejectReason(null);
        amsBorrowOrder.setOrderStatus(BorrowOrderStatus.DRAFT);
        amsBorrowOrder.setUpdateTime(DateUtils.getNowDate());

        // 3. 校验并重新填充资产快照
        fillOrderSnapshots(amsBorrowOrder, amsBorrowOrder.getOrderId());

        // 4. 逻辑删除旧明细，再写入新明细以防冗余
        amsBorrowOrderMapper.deleteAmsBorrowOrderItemByOrderId(amsBorrowOrder.getOrderId());
        insertAmsBorrowOrderItems(amsBorrowOrder);

        // 5. 更新主表，采用受控修改
        if (amsBorrowOrderMapper.updateAmsBorrowOrder(amsBorrowOrder) != 1)
        {
            throw new ServiceException("单据状态已发生改变，更新失败");
        }
        return 1;
    }

    /**
     * 批量逻辑删除借用单草稿
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int deleteAmsBorrowOrderByOrderIds(String orderIds)
    {
        // 1. 将操作ID排序以彻底防范死锁
        Long[] sortedIds = Arrays.stream(Convert.toStrArray(orderIds))
                .map(Long::valueOf).sorted().toArray(Long[]::new);
        int rows = 0;
        for (Long orderId : sortedIds)
        {
            // 2. 获取主表悲观排他锁，验证是否为 DRAFT 草稿单
            AmsBorrowOrder order = amsBorrowOrderMapper.selectAmsBorrowOrderByOrderIdForUpdate(orderId);
            if (order == null)
            {
                continue;
            }
            if (!BorrowOrderStatus.DRAFT.equals(order.getOrderStatus()))
            {
                throw new ServiceException(StringUtils.format("借用单【{}】非草稿状态，不允许删除", order.getBorrowNo()));
            }
            // 3. 子表及主表联级打上逻辑删除标记
            amsBorrowOrderMapper.deleteAmsBorrowOrderItemByOrderId(orderId);
            rows += amsBorrowOrderMapper.deleteAmsBorrowOrderByOrderId(orderId);
        }
        return rows;
    }

    /**
     * 逻辑删除单个借用单草稿
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int deleteAmsBorrowOrderByOrderId(Long orderId)
    {
        AmsBorrowOrder order = amsBorrowOrderMapper.selectAmsBorrowOrderByOrderIdForUpdate(orderId);
        if (order == null)
        {
            return 0;
        }
        if (!BorrowOrderStatus.DRAFT.equals(order.getOrderStatus()))
        {
            throw new ServiceException(StringUtils.format("借用单【{}】非草稿状态，不允许删除", order.getBorrowNo()));
        }
        amsBorrowOrderMapper.deleteAmsBorrowOrderItemByOrderId(orderId);
        return amsBorrowOrderMapper.deleteAmsBorrowOrderByOrderId(orderId);
    }

    /**
     * 过滤查询当前可用于借用的资产列表
     */
    @Override
    public List<AmsAsset> selectAvailableBorrowAssetList(AmsAsset asset, Long currentOrderId)
    {
        return amsBorrowOrderMapper.selectAvailableBorrowAssetList(asset, currentOrderId,
                AssetStatus.IN_STOCK, BorrowOrderStatus.DRAFT, BorrowOrderStatus.PENDING_CONFIRM);
    }

    /**
     * 提交借用单申请
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int submitBorrow(Long orderId, String operateLoginName)
    {
        // 1. 获取主单行锁并校验状态必须为 DRAFT
        AmsBorrowOrder order = requireOrderForUpdate(orderId, BorrowOrderStatus.DRAFT, "仅草稿借用单允许提交");
        validateLoginName(operateLoginName);

        // 2. 核心校验：锁定资产、核查跨单据占用以及物理仓位快照一致性
        validateOrderReadyForFlow(order);

        // 3. 更新主表状态为待确认
        order.setOrderStatus(BorrowOrderStatus.PENDING_CONFIRM);
        order.setUpdateBy(operateLoginName);
        order.setUpdateTime(DateUtils.getNowDate());
        if (amsBorrowOrderMapper.submitAmsBorrowOrder(order) != 1)
        {
            throw new ServiceException("借用单状态已变化，提交失败");
        }
        return 1;
    }

    /**
     * 确认借出
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int confirmBorrow(Long orderId, Long operateUserId, String operateUserName, String operateLoginName)
    {
        // 1. 悲观锁锁定主表记录，强校验必须处于待确认状态
        AmsBorrowOrder order = requireOrderForUpdate(orderId, BorrowOrderStatus.PENDING_CONFIRM,
                "仅待确认状态的借用单允许确认借出");
        validateOperator(operateUserId, operateUserName, "确认人");
        validateLoginName(operateLoginName);

        // 2. 二次核查防并发：重新升序加锁锁定资产，校验仓位和占用一致性
        validateOrderReadyForFlow(order);

        // 3. 逐一调用公共状态流转服务执行 confirmBorrow 借出
        for (AmsBorrowOrderItem item : sortedItems(order))
        {
            AssetTransitionContext context = new AssetTransitionContext();
            context.setSourceOrderId(order.getOrderId());
            context.setSourceOrderNo(order.getBorrowNo());
            context.setSourceItemId(item.getItemId());
            context.setOperateUserId(operateUserId);
            context.setOperateUserName(operateUserName);
            context.setOperateLoginName(operateLoginName);
            context.setChangeSummary("确认资产借出");
            context.setRemark(order.getRemark());

            // 公共流转服务会将资产状态置为 BORROWED，清空当前仓位与归属，写入借出履历
            assetStatusTransitionService.confirmBorrow(item.getAssetId(), context);
        }

        // 4. 更新单据状态为 BORROWING 借用中，保存确认信息快照
        Date now = DateUtils.getNowDate();
        order.setOrderStatus(BorrowOrderStatus.BORROWING);
        order.setConfirmUserId(operateUserId);
        order.setConfirmUserName(operateUserName);
        order.setConfirmTime(now);
        order.setUpdateBy(operateLoginName);
        order.setUpdateTime(now);

        if (amsBorrowOrderMapper.confirmAmsBorrowOrder(order) != 1)
        {
            throw new ServiceException("借用单确认失败，状态已被其他线程更改");
        }
        return 1;
    }

    /**
     * 驳回借用单申请
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int rejectBorrow(Long orderId, String rejectReason, String operateLoginName)
    {
        // 1. 获取待确认主单锁
        AmsBorrowOrder order = requireOrderForUpdate(orderId, BorrowOrderStatus.PENDING_CONFIRM,
                "仅待确认状态的借用单允许驳回");
        validateLoginName(operateLoginName);
        if (StringUtils.isEmpty(StringUtils.trim(rejectReason)))
        {
            throw new ServiceException("驳回原因不能为空");
        }
        validateLength(rejectReason, 500, "驳回原因");

        // 2. 状态直接扭转为已驳回，不改变实物台账，缩短事务冲突
        order.setOrderStatus(BorrowOrderStatus.REJECTED);
        order.setRejectReason(StringUtils.trim(rejectReason));
        order.setUpdateBy(operateLoginName);
        order.setUpdateTime(DateUtils.getNowDate());
        if (amsBorrowOrderMapper.rejectAmsBorrowOrder(order) != 1)
        {
            throw new ServiceException("单据状态已变化，驳回失败");
        }
        return 1;
    }

    /**
     * 明细级归还申请
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int applyReturn(Long orderId, List<AmsBorrowOrderItem> returnItems, String operateLoginName)
    {
        if (returnItems == null || returnItems.isEmpty())
        {
            throw new ServiceException("申请归还的明细行不能为空");
        }
        validateLoginName(operateLoginName);

        // 1. 锁定主单据。必须是借用中（BORROWING）或待归还确认（PENDING_RETURN_CONFIRM）状态
        AmsBorrowOrder order = amsBorrowOrderMapper.selectAmsBorrowOrderByOrderIdForUpdate(orderId);
        if (order == null)
        {
            throw new ServiceException("借用单不存在或已删除");
        }
        if (!BorrowOrderStatus.BORROWING.equals(order.getOrderStatus())
                && !BorrowOrderStatus.PENDING_RETURN_CONFIRM.equals(order.getOrderStatus()))
        {
            throw new ServiceException("仅借用中或待归还确认的单据允许发起归还申请");
        }

        // 2. 先按明细ID锁定真实明细，再按真实资产ID升序锁资产，避免前端篡改 assetId 后跨资产归还
        Map<Long, AmsBorrowOrderItem> requestItemMap = buildRequestItemMap(returnItems);
        List<AmsBorrowOrderItem> lockedItems = lockReturnItems(orderId, requestItemMap.keySet(),
                BorrowOrderStatus.BORROWING, "仅借用中的明细允许申请归还");
        Date now = DateUtils.getNowDate();

        for (AmsBorrowOrderItem currentItem : lockedItems)
        {
            AmsBorrowOrderItem paramItem = requestItemMap.get(currentItem.getItemId());

            // 锁定资产台账行，验证当前状态为 BORROWED
            AmsAsset asset = amsAssetMapper.selectAmsAssetByAssetIdForUpdate(currentItem.getAssetId());
            if (asset == null || "1".equals(asset.getDelFlag()))
            {
                throw new ServiceException("资产不存在或已删除");
            }
            if (!AssetStatus.BORROWED.equals(asset.getAssetStatus()))
            {
                throw new ServiceException(StringUtils.format("资产【{}】当前未处于借用状态，无法申请归还", asset.getAssetCode()));
            }

            // 验证并锁定拟归还的目标仓位及启用状态
            if (paramItem.getReturnWarehouseId() == null || paramItem.getReturnLocationId() == null)
            {
                throw new ServiceException(StringUtils.format("资产【{}】归还的目标仓库或存放位置不能为空", asset.getAssetCode()));
            }
            AmsWarehouse warehouse = amsWarehouseService.selectAmsWarehouseByWarehouseId(paramItem.getReturnWarehouseId());
            if (warehouse == null || !"Y".equals(warehouse.getEnabled()))
            {
                throw new ServiceException("归还目标仓库不存在或已停用");
            }
            AmsAssetLocation location = amsAssetLocationService.selectAmsAssetLocationByLocationId(paramItem.getReturnLocationId());
            if (location == null || !"Y".equals(location.getEnabled()))
            {
                throw new ServiceException("归还目标位置不存在或已停用");
            }
            if (!Objects.equals(location.getWarehouseId(), warehouse.getWarehouseId()))
            {
                throw new ServiceException("归还存放位置不属于当前选择仓库");
            }

            // 填充明细行仓库、位置快照
            currentItem.setReturnWarehouseId(warehouse.getWarehouseId());
            currentItem.setReturnWarehouseCode(warehouse.getWarehouseCode());
            currentItem.setReturnWarehouseName(warehouse.getWarehouseName());
            currentItem.setReturnLocationId(location.getLocationId());
            currentItem.setReturnLocationCode(location.getLocationCode());
            currentItem.setReturnLocationName(location.getLocationName());
            currentItem.setReturnStatus(BorrowOrderStatus.PENDING_RETURN_CONFIRM);
            currentItem.setUpdateBy(operateLoginName);
            currentItem.setUpdateTime(now);

            // 更新子表该明细状态
            if (amsBorrowOrderMapper.updateAmsBorrowOrderItemReturnStatus(currentItem, BorrowOrderStatus.BORROWING) != 1)
            {
                throw new ServiceException("明细状态已被篡改，申请归还失败");
            }
        }

        // 3. 联动修改主表单据状态为待归还确认
        if (amsBorrowOrderMapper.updateAmsBorrowOrderStatus(orderId, BorrowOrderStatus.PENDING_RETURN_CONFIRM,
                operateLoginName, now) != 1)
        {
            throw new ServiceException("借用单主表状态联动失败");
        }
        return 1;
    }

    /**
     * 确认归还明细
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int confirmReturn(Long orderId, List<AmsBorrowOrderItem> returnItems, Long operateUserId,
            String operateUserName, String operateLoginName)
    {
        if (returnItems == null || returnItems.isEmpty())
        {
            throw new ServiceException("确认归还的明细行不能为空");
        }
        validateOperator(operateUserId, operateUserName, "确认人");
        validateLoginName(operateLoginName);

        // 1. 悲观锁锁定主表单据，必须为待归还确认（PENDING_RETURN_CONFIRM）状态
        AmsBorrowOrder order = requireOrderForUpdate(orderId, BorrowOrderStatus.PENDING_RETURN_CONFIRM,
                "仅待归还确认状态的单据允许归还确认");

        // 2. 只确认已经申请归还的明细，确认阶段统一使用明细中已保存的目标仓位
        Map<Long, AmsBorrowOrderItem> requestItemMap = buildRequestItemMap(returnItems);
        List<AmsBorrowOrderItem> lockedItems = lockReturnItems(orderId, requestItemMap.keySet(),
                BorrowOrderStatus.PENDING_RETURN_CONFIRM, "仅待归还确认的明细允许确认归还");
        Date now = DateUtils.getNowDate();

        for (AmsBorrowOrderItem currentItem : lockedItems)
        {
            if (currentItem.getReturnWarehouseId() == null || currentItem.getReturnLocationId() == null)
            {
                throw new ServiceException(StringUtils.format("资产【{}】缺少已申请的归还仓库或位置",
                        currentItem.getAssetCode()));
            }

            // 锁定资产行，验证状态仍为 BORROWED
            AmsAsset asset = amsAssetMapper.selectAmsAssetByAssetIdForUpdate(currentItem.getAssetId());
            if (asset == null || "1".equals(asset.getDelFlag()))
            {
                throw new ServiceException("资产不存在或已删除");
            }
            if (!AssetStatus.BORROWED.equals(asset.getAssetStatus()))
            {
                throw new ServiceException(StringUtils.format("资产【{}】非借用状态，无法确认归还", asset.getAssetCode()));
            }

            // 调用公共状态流转服务 confirmBorrowReturn 归位资产为在库并记录履历
            AssetTransitionContext context = new AssetTransitionContext();
            context.setSourceOrderId(order.getOrderId());
            context.setSourceOrderNo(order.getBorrowNo());
            context.setSourceItemId(currentItem.getItemId());
            context.setOperateUserId(operateUserId);
            context.setOperateUserName(operateUserName);
            context.setOperateLoginName(operateLoginName);
            context.setChangeSummary("确认借用归还");
            context.setRemark(order.getRemark());

            assetStatusTransitionService.confirmBorrowReturn(currentItem.getAssetId(),
                    currentItem.getReturnWarehouseId(), currentItem.getReturnLocationId(), context);

            // 3. 更新该明细状态为已归还
            currentItem.setReturnStatus(BorrowOrderStatus.BORROW_RETURNED);
            currentItem.setActualReturnDate(now);
            currentItem.setUpdateBy(operateLoginName);
            currentItem.setUpdateTime(now);
            if (amsBorrowOrderMapper.updateAmsBorrowOrderItemReturnStatus(currentItem,
                    BorrowOrderStatus.PENDING_RETURN_CONFIRM) != 1)
            {
                throw new ServiceException("明细确认归还更新失败");
            }
        }

        // 4. 判断主表下是否所有明细行的归还状态均为了 BORROW_RETURNED
        List<AmsBorrowOrderItem> allItems = amsBorrowOrderMapper.selectAmsBorrowOrderItemList(orderId);
        boolean allReturned = true;
        boolean hasPending = false;

        for (AmsBorrowOrderItem detail : allItems)
        {
            if (!BorrowOrderStatus.BORROW_RETURNED.equals(detail.getReturnStatus()))
            {
                allReturned = false;
            }
            if (BorrowOrderStatus.PENDING_RETURN_CONFIRM.equals(detail.getReturnStatus()))
            {
                hasPending = true;
            }
        }

        // 5. 联动刷新主单据最新状态
        String nextStatus;
        if (allReturned)
        {
            nextStatus = BorrowOrderStatus.BORROW_RETURNED; // 全部归还完毕
        }
        else if (hasPending)
        {
            nextStatus = BorrowOrderStatus.PENDING_RETURN_CONFIRM; // 还有其它分批归还申请待确认
        }
        else
        {
            nextStatus = BorrowOrderStatus.BORROWING; // 有部分已归还，但还有的处于借用中
        }

        if (amsBorrowOrderMapper.updateAmsBorrowOrderStatus(orderId, nextStatus, operateLoginName, now) != 1)
        {
            throw new ServiceException("借用单主单状态联动失败");
        }
        return 1;
    }

    private Map<Long, AmsBorrowOrderItem> buildRequestItemMap(List<AmsBorrowOrderItem> requestItems)
    {
        Map<Long, AmsBorrowOrderItem> requestItemMap = new HashMap<>();
        for (AmsBorrowOrderItem requestItem : requestItems)
        {
            if (requestItem.getItemId() == null)
            {
                throw new ServiceException("归还明细行ID不能为空");
            }
            if (requestItemMap.put(requestItem.getItemId(), requestItem) != null)
            {
                throw new ServiceException("归还明细行不能重复提交");
            }
        }
        return requestItemMap;
    }

    private List<AmsBorrowOrderItem> lockReturnItems(Long orderId, Set<Long> itemIds, String expectedStatus,
            String statusErrorMessage)
    {
        List<Long> sortedItemIds = new ArrayList<>(itemIds);
        Collections.sort(sortedItemIds);
        List<AmsBorrowOrderItem> lockedItems = new ArrayList<>();
        for (Long itemId : sortedItemIds)
        {
            AmsBorrowOrderItem item = amsBorrowOrderMapper
                    .selectAmsBorrowOrderItemByOrderIdAndItemIdForUpdate(orderId, itemId);
            if (item == null)
            {
                throw new ServiceException("归还明细不存在或不属于当前借用单");
            }
            if (item.getAssetId() == null)
            {
                throw new ServiceException("归还明细缺少资产ID");
            }
            if (!expectedStatus.equals(item.getReturnStatus()))
            {
                throw new ServiceException(statusErrorMessage);
            }
            lockedItems.add(item);
        }
        lockedItems.sort(Comparator.comparing(AmsBorrowOrderItem::getAssetId));
        return lockedItems;
    }

    /**
     * 新增借用单明细信息
     */
    private void insertAmsBorrowOrderItems(AmsBorrowOrder amsBorrowOrder)
    {
        List<AmsBorrowOrderItem> amsBorrowOrderItemList = amsBorrowOrder.getAmsBorrowOrderItemList();
        Long orderId = amsBorrowOrder.getOrderId();
        String borrowNo = amsBorrowOrder.getBorrowNo();
        if (StringUtils.isNotNull(amsBorrowOrderItemList))
        {
            List<AmsBorrowOrderItem> list = new ArrayList<AmsBorrowOrderItem>();
            for (AmsBorrowOrderItem amsBorrowOrderItem : amsBorrowOrderItemList)
            {
                amsBorrowOrderItem.setOrderId(orderId);
                amsBorrowOrderItem.setBorrowNo(borrowNo);
                amsBorrowOrderItem.setDelFlag(DEL_FLAG_NORMAL);
                amsBorrowOrderItem.setCreateTime(DateUtils.getNowDate());
                list.add(amsBorrowOrderItem);
            }
            if (list.size() > 0)
            {
                amsBorrowOrderMapper.batchAmsBorrowOrderItem(list);
            }
        }
    }

    /**
     * 主表行悲观锁定核验
     */
    private AmsBorrowOrder requireOrderForUpdate(Long orderId, String expectedStatus, String errMsg)
    {
        AmsBorrowOrder order = amsBorrowOrderMapper.selectAmsBorrowOrderByOrderIdForUpdate(orderId);
        if (order == null)
        {
            throw new ServiceException("借用单不存在或已删除");
        }
        if (!expectedStatus.equals(order.getOrderStatus()))
        {
            throw new ServiceException(errMsg);
        }
        return order;
    }

    /**
     * 基本的非空参数拦截
     */
    private void validateOrderRequest(AmsBorrowOrder order)
    {
        if (order == null)
        {
            throw new ServiceException("借用单不能为空");
        }
        if (order.getAmsBorrowOrderItemList() == null || order.getAmsBorrowOrderItemList().isEmpty())
        {
            throw new ServiceException("借用单明细不能为空");
        }
        if (order.getExpectedReturnDate() == null)
        {
            throw new ServiceException("预计归还日期不能为空");
        }
        if (DateUtils.getNowDate().after(order.getExpectedReturnDate()))
        {
            throw new ServiceException("预计归还日期不能早于当前系统日期");
        }
        validateLength(order.getRemark(), 500, "备注");
    }

    /**
     * 借用责任人与部门快照校验填充
     */
    private void fillBorrowerSnapshots(AmsBorrowOrder order)
    {
        if (order.getBorrowUserId() == null)
        {
            throw new ServiceException("借用人不能为空");
        }
        SysUser user = sysUserService.selectUserById(order.getBorrowUserId());
        if (user == null || "1".equals(user.getDelFlag()) || "1".equals(user.getStatus()))
        {
            throw new ServiceException("借用人不存在或已被停用");
        }
        order.setBorrowUserName(user.getUserName());

        if (order.getBorrowDeptId() == null)
        {
            throw new ServiceException("借用部门不能为空");
        }
        SysDept dept = sysDeptService.selectDeptById(order.getBorrowDeptId());
        if (dept == null || "1".equals(dept.getDelFlag()) || "1".equals(dept.getStatus()))
        {
            throw new ServiceException("借用部门不存在或已被停用");
        }
        order.setBorrowDeptName(dept.getDeptName());

        if (!Objects.equals(user.getDeptId(), dept.getDeptId()))
        {
            throw new ServiceException("所选借用人与借用部门不匹配");
        }
    }

    /**
     * 保存/更新前的资产快照及并发占用校验
     */
    private void fillOrderSnapshots(AmsBorrowOrder order, Long currentOrderId)
    {
        Set<Long> assetIds = new HashSet<>();
        List<AmsBorrowOrderItem> items = sortedItems(order);
        order.setAmsBorrowOrderItemList(items);

        for (AmsBorrowOrderItem item : items)
        {
            if (item.getAssetId() == null)
            {
                throw new ServiceException("资产ID不能为空");
            }
            if (!assetIds.add(item.getAssetId()))
            {
                throw new ServiceException("同一借用单不能选择重复的资产明细");
            }

            // 悲观锁定该资产，防并发重写
            AmsAsset asset = amsAssetMapper.selectAmsAssetByAssetIdForUpdate(item.getAssetId());
            if (asset == null || "1".equals(asset.getDelFlag()))
            {
                throw new ServiceException("资产不存在或已删除");
            }
            if (!AssetStatus.IN_STOCK.equals(asset.getAssetStatus()))
            {
                throw new ServiceException(StringUtils.format("资产【{}】当前非在库状态，不允许借出", asset.getAssetCode()));
            }
            if (asset.getWarehouseId() == null || asset.getLocationId() == null)
            {
                throw new ServiceException(StringUtils.format("资产【{}】缺少仓库或存放位置", asset.getAssetCode()));
            }

            // 是否存在其它活跃借用单并发占用
            if (amsBorrowOrderMapper.countOtherActiveBorrowOrderByAssetId(asset.getAssetId(), currentOrderId,
                    BorrowOrderStatus.DRAFT, BorrowOrderStatus.PENDING_CONFIRM) > 0)
            {
                throw new ServiceException(StringUtils.format("资产【{}】已被其他未完成借用单占用", asset.getAssetCode()));
            }

            // 回填属性快照
            item.setAssetCode(asset.getAssetCode());
            item.setAssetName(asset.getAssetName());
            item.setCategoryId(asset.getCategoryId());
            item.setCategoryCode(asset.getCategoryCode());
            item.setCategoryName(asset.getCategoryName());
            item.setSpecModel(asset.getSpecModel());
            item.setBrand(asset.getBrand());

            // 借用前仓位快照
            item.setBeforeWarehouseId(asset.getWarehouseId());
            item.setBeforeWarehouseCode(asset.getWarehouseCode());
            item.setBeforeWarehouseName(asset.getWarehouseName());
            item.setBeforeLocationId(asset.getLocationId());
            item.setBeforeLocationCode(asset.getLocationCode());
            item.setBeforeLocationName(asset.getLocationName());

            // 回填单号快照与状态
            item.setBorrowNo(order.getBorrowNo());
            item.setReturnStatus(BorrowOrderStatus.BORROWING);
            item.setDelFlag(DEL_FLAG_NORMAL);
            validateLength(item.getRemark(), 500, "明细备注");
        }
    }

    /**
     * 流程流转（提交、确认）前的二次快照与占用核查
     */
    private void validateOrderReadyForFlow(AmsBorrowOrder order)
    {
        List<AmsBorrowOrderItem> items = sortedItems(order);
        if (items.isEmpty())
        {
            throw new ServiceException("借用单明细项不能为空");
        }
        Set<Long> assetIds = new HashSet<>();

        for (AmsBorrowOrderItem item : items)
        {
            // 重新获取排他锁锁定资产
            AmsAsset asset = amsAssetMapper.selectAmsAssetByAssetIdForUpdate(item.getAssetId());
            if (asset == null || "1".equals(asset.getDelFlag()))
            {
                throw new ServiceException("资产不存在或已删除");
            }
            if (!assetIds.add(asset.getAssetId()))
            {
                throw new ServiceException("同一借用单不能重复选择资产");
            }
            if (!AssetStatus.IN_STOCK.equals(asset.getAssetStatus()))
            {
                throw new ServiceException(StringUtils.format("资产【{}】当前状态不允许借出", asset.getAssetCode()));
            }

            // 核查占用
            if (amsBorrowOrderMapper.countOtherActiveBorrowOrderByAssetId(asset.getAssetId(), order.getOrderId(),
                    BorrowOrderStatus.DRAFT, BorrowOrderStatus.PENDING_CONFIRM) > 0)
            {
                throw new ServiceException(StringUtils.format("资产【{}】已被其他有效借用单占用", asset.getAssetCode()));
            }

            // 校验仓位一致性防脏快照
            if (!Objects.equals(item.getBeforeWarehouseId(), asset.getWarehouseId())
                    || !Objects.equals(item.getBeforeLocationId(), asset.getLocationId()))
            {
                throw new ServiceException(StringUtils.format("资产【{}】物理存放仓位已发生变更，请撤回单据重新编辑", asset.getAssetCode()));
            }
        }
    }

    private List<AmsBorrowOrderItem> sortedItems(AmsBorrowOrder order)
    {
        List<AmsBorrowOrderItem> items = new ArrayList<>(order.getAmsBorrowOrderItemList());
        items.sort(Comparator.comparing(AmsBorrowOrderItem::getAssetId));
        return items;
    }

    private void validateLength(String value, int maxLength, String fieldName)
    {
        if (StringUtils.isNotEmpty(value) && value.length() > maxLength)
        {
            throw new ServiceException(fieldName + "长度不能超过" + maxLength + "个字符");
        }
    }

    private void validateLoginName(String operateLoginName)
    {
        if (StringUtils.isEmpty(operateLoginName))
        {
            throw new ServiceException("当前操作人登录账号不能为空");
        }
        validateLength(operateLoginName, 64, "操作账号");
    }

    private void validateOperator(Long operateUserId, String operateUserName, String fieldPrefix)
    {
        if (operateUserId == null)
        {
            throw new ServiceException(fieldPrefix + "用户ID不能为空");
        }
        if (StringUtils.isEmpty(operateUserName))
        {
            throw new ServiceException(fieldPrefix + "名称不能为空");
        }
        validateLength(operateUserName, 100, fieldPrefix + "名称");
    }
}
