package com.ruoyi.asset.service.impl;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
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.DisposalOrderStatus;
import com.ruoyi.asset.domain.AmsAsset;
import com.ruoyi.asset.domain.AmsDisposalOperateContext;
import com.ruoyi.asset.domain.AmsDisposalOrder;
import com.ruoyi.asset.domain.AmsDisposalOrderItem;
import com.ruoyi.asset.domain.AssetTransitionContext;
import com.ruoyi.asset.mapper.AmsAssetMapper;
import com.ruoyi.asset.mapper.AmsDisposalOrderMapper;
import com.ruoyi.asset.service.IAmsDisposalOrderService;
import com.ruoyi.asset.service.IAssetStatusTransitionService;
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;

/**
 * 报废管理Service业务层处理
 *
 * 核心职责：处理在库（IN_STOCK）或在用（IN_USE）的费用化资产进入报废（DISPOSED）的生命周期。
 * 业务设计亮点：
 * 1. 【防死锁设计】：所有悲观锁定资产的操作，均在应用层先对资产ID进行升序排列，统一加锁顺序。
 * 2. 【并发占用校验】：无论在草稿保存、提交还是确认报废阶段，都校验资产没有被其他活跃报废单占用。
 * 3. 【逻辑删除改造】：主表及明细表均进行逻辑删除改造（del_flag='1'），且仅草稿状态允许删除。
 *
 * @author Yangk
 * @date 2026-06-17
 */
@Service
public class AmsDisposalOrderServiceImpl implements IAmsDisposalOrderService
{
    private static final String DISPOSAL_ORDER_RULE = "DISPOSAL_ORDER";
    private static final String DEL_FLAG_NORMAL = "0";

    @Autowired
    private AmsDisposalOrderMapper amsDisposalOrderMapper;

    @Autowired
    private AmsAssetMapper amsAssetMapper;

    @Autowired
    private ISysCodeRuleService sysCodeRuleService;

    @Autowired
    private IAssetStatusTransitionService assetStatusTransitionService;

    /**
     * 查询报废管理
     *
     * @param orderId 报废管理主键
     * @return 报废管理
     */
    @Override
    public AmsDisposalOrder selectAmsDisposalOrderByOrderId(Long orderId)
    {
        return amsDisposalOrderMapper.selectAmsDisposalOrderByOrderId(orderId);
    }

    /**
     * 查询报废管理列表
     *
     * @param amsDisposalOrder 报废管理
     * @return 报废管理
     */
    @Override
    public List<AmsDisposalOrder> selectAmsDisposalOrderList(AmsDisposalOrder amsDisposalOrder)
    {
        return amsDisposalOrderMapper.selectAmsDisposalOrderList(amsDisposalOrder);
    }

    /**
     * 新增报废管理（草稿）
     *
     * @param amsDisposalOrder 报废管理
     * @return 结果
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public int insertAmsDisposalOrder(AmsDisposalOrder amsDisposalOrder, AmsDisposalOperateContext operateContext)
    {
        validateOperateContext(operateContext);
        // 1. 基础字段与表单校验
        validateOrderRequest(amsDisposalOrder);

        // 2. 申请人和部门必须以后端登录会话为准，避免前端隐藏字段篡改。
        fillApplicantSnapshots(amsDisposalOrder, operateContext);

        // 3. 生成 BF 前缀的唯一报废单号
        amsDisposalOrder.setDisposalNo(sysCodeRuleService.nextCode(DISPOSAL_ORDER_RULE));
        amsDisposalOrder.setOrderStatus(DisposalOrderStatus.DRAFT);
        amsDisposalOrder.setDelFlag(DEL_FLAG_NORMAL);
        amsDisposalOrder.setCreateBy(operateContext.getOperateLoginName());
        amsDisposalOrder.setCreateTime(DateUtils.getNowDate());

        // 重置受控的确认及驳回字段，以防越权篡改
        amsDisposalOrder.setConfirmUserId(null);
        amsDisposalOrder.setConfirmUserName(null);
        amsDisposalOrder.setConfirmTime(null);
        amsDisposalOrder.setRejectReason(null);
        amsDisposalOrder.setDisposalTime(null);

        // 4. 防死锁升序加锁、校验资产并回填明细快照
        fillDetailSnapshotsAndValidate(amsDisposalOrder, null);

        // 5. 保存主表与子明细
        int rows = amsDisposalOrderMapper.insertAmsDisposalOrder(amsDisposalOrder);
        if (rows != 1 || amsDisposalOrder.getOrderId() == null)
        {
            throw new ServiceException("报废单保存失败");
        }
        insertAmsDisposalOrderItem(amsDisposalOrder);
        return rows;
    }

    /**
     * 修改报废管理（草稿）
     *
     * @param amsDisposalOrder 报废管理
     * @return 结果
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public int updateAmsDisposalOrder(AmsDisposalOrder amsDisposalOrder, AmsDisposalOperateContext operateContext)
    {
        validateOperateContext(operateContext);
        if (amsDisposalOrder == null || amsDisposalOrder.getOrderId() == null)
        {
            throw new ServiceException("单据ID不能为空");
        }

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

        // 基础校验
        validateOrderRequest(amsDisposalOrder);

        // 2. 强行还原单号、申请人及受控属性，强制复归为 DRAFT
        amsDisposalOrder.setDisposalNo(current.getDisposalNo());
        amsDisposalOrder.setApplicantId(current.getApplicantId());
        amsDisposalOrder.setApplicantName(current.getApplicantName());
        amsDisposalOrder.setApplyDeptId(current.getApplyDeptId());
        amsDisposalOrder.setApplyDeptName(current.getApplyDeptName());
        amsDisposalOrder.setConfirmUserId(null);
        amsDisposalOrder.setConfirmUserName(null);
        amsDisposalOrder.setConfirmTime(null);
        amsDisposalOrder.setRejectReason(null);
        amsDisposalOrder.setOrderStatus(DisposalOrderStatus.DRAFT);
        amsDisposalOrder.setCreateBy(current.getCreateBy());
        amsDisposalOrder.setCreateTime(current.getCreateTime());
        amsDisposalOrder.setDelFlag(DEL_FLAG_NORMAL);
        amsDisposalOrder.setUpdateBy(operateContext.getOperateLoginName());
        amsDisposalOrder.setUpdateTime(DateUtils.getNowDate());

        // 3. 锁定资产，校验并重新填充快照
        fillDetailSnapshotsAndValidate(amsDisposalOrder, amsDisposalOrder.getOrderId());

        // 4. 逻辑删除旧明细，重新插入新明细以防冗余
        amsDisposalOrderMapper.deleteAmsDisposalOrderItemByOrderId(amsDisposalOrder.getOrderId());
        insertAmsDisposalOrderItem(amsDisposalOrder);

        // 5. 更新主表，采用 CAS 状态校验
        return updateOrderOrThrow(amsDisposalOrder, DisposalOrderStatus.DRAFT, "单据状态已发生改变，更新失败");
    }

    /**
     * 批量逻辑删除报废管理
     *
     * @param orderIds 需要删除的报废管理主键
     * @return 结果
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public int deleteAmsDisposalOrderByOrderIds(String orderIds)
    {
        Long[] sortedIds = Arrays.stream(Convert.toStrArray(orderIds))
                .map(Long::valueOf).sorted().toArray(Long[]::new);
        int rows = 0;
        for (Long orderId : sortedIds)
        {
            // 获取主表悲观锁并核实必须是草稿
            AmsDisposalOrder order = amsDisposalOrderMapper.selectAmsDisposalOrderByOrderIdForUpdate(orderId);
            if (order == null)
            {
                continue;
            }
            if (!DisposalOrderStatus.DRAFT.equals(order.getOrderStatus()))
            {
                throw new ServiceException(StringUtils.format("报废单【{}】非草稿状态，不允许删除", order.getDisposalNo()));
            }
            // 级联打上逻辑删除标志
            amsDisposalOrderMapper.deleteAmsDisposalOrderItemByOrderId(orderId);
            rows += amsDisposalOrderMapper.deleteAmsDisposalOrderByOrderId(orderId);
        }
        return rows;
    }

    /**
     * 逻辑删除单个报废管理信息
     *
     * @param orderId 报废管理主键
     * @return 结果
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public int deleteAmsDisposalOrderByOrderId(Long orderId)
    {
        AmsDisposalOrder order = amsDisposalOrderMapper.selectAmsDisposalOrderByOrderIdForUpdate(orderId);
        if (order == null)
        {
            return 0;
        }
        if (!DisposalOrderStatus.DRAFT.equals(order.getOrderStatus()))
        {
            throw new ServiceException(StringUtils.format("报废单【{}】非草稿状态，不允许删除", order.getDisposalNo()));
        }
        amsDisposalOrderMapper.deleteAmsDisposalOrderItemByOrderId(orderId);
        return amsDisposalOrderMapper.deleteAmsDisposalOrderByOrderId(orderId);
    }

    /**
     * 提交报废申请
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public int submitDisposal(Long orderId, AmsDisposalOperateContext operateContext)
    {
        validateOperateContext(operateContext);

        // 1. 悲观锁锁定主表并校验状态为 DRAFT
        AmsDisposalOrder order = requireOrderForUpdate(orderId, DisposalOrderStatus.DRAFT, "仅草稿状态的报废单允许提交");

        // 2. 再次悲观锁锁定资产，校验资产状态与跨单据占用，防并发
        validateOrderAssetsReady(order);

        // 3. CAS 更新单据状态为待确认
        order.setOrderStatus(DisposalOrderStatus.PENDING_CONFIRM);
        order.setUpdateBy(operateContext.getOperateLoginName());
        order.setUpdateTime(DateUtils.getNowDate());

        return updateOrderOrThrow(order, DisposalOrderStatus.DRAFT, "报废单提交失败，请刷新后重试");
    }

    /**
     * 确认报废处置：PENDING_CONFIRM -> DISPOSED_DONE
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public int confirmDisposal(Long orderId, AmsDisposalOperateContext operateContext)
    {
        validateOperateContext(operateContext);

        // 1. 锁主表校验状态必须为待确认
        AmsDisposalOrder order = requireOrderForUpdate(orderId, DisposalOrderStatus.PENDING_CONFIRM, "仅待确认状态的报废单允许确认报废");

        // 2. 升序加锁校验资产状态及跨单占用
        validateOrderAssetsReady(order);

        Date now = DateUtils.getNowDate();

        // 3. 逐一调用公共状态流转服务 confirmDisposal
        for (AmsDisposalOrderItem item : order.getAmsDisposalOrderItemList())
        {
            AssetTransitionContext context = new AssetTransitionContext();
            context.setSourceOrderId(order.getOrderId());
            context.setSourceOrderNo(order.getDisposalNo());
            context.setSourceItemId(item.getItemId());
            context.setOperateUserId(operateContext.getOperateUserId());
            context.setOperateUserName(StringUtils.trim(operateContext.getOperateUserName()));
            context.setOperateLoginName(operateContext.getOperateLoginName());
            context.setChangeSummary("确认资产报废");
            context.setRemark(item.getDisposalRemark());

            // 变更资产状态为 DISPOSED 写入资产履历，报废不清除 RFID 绑定
            assetStatusTransitionService.confirmDisposal(item.getAssetId(), context);
        }

        // 4. 回填确认属性，变更为已报废状态
        order.setConfirmUserId(operateContext.getOperateUserId());
        order.setConfirmUserName(StringUtils.trim(operateContext.getOperateUserName()));
        order.setConfirmTime(now);
        order.setDisposalTime(now);
        order.setOrderStatus(DisposalOrderStatus.DISPOSED_DONE);
        order.setUpdateBy(operateContext.getOperateLoginName());
        order.setUpdateTime(now);

        return updateOrderOrThrow(order, DisposalOrderStatus.PENDING_CONFIRM, "报废单确认失败，请刷新后重试");
    }

    /**
     * 驳回报废申请：PENDING_CONFIRM -> REJECTED
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public int rejectDisposal(Long orderId, String rejectReason, AmsDisposalOperateContext operateContext)
    {
        validateOperateContext(operateContext);
        if (StringUtils.isEmpty(StringUtils.trim(rejectReason)))
        {
            throw new ServiceException("驳回原因不能为空");
        }
        validateLength(rejectReason, 500, "驳回原因");

        // 1. 锁主表校验状态
        AmsDisposalOrder order = requireOrderForUpdate(orderId, DisposalOrderStatus.PENDING_CONFIRM, "仅待确认状态的报废单允许驳回");

        // 2. 更新单据状态为已驳回，并记录原因。不改资产状态，不写履历
        order.setOrderStatus(DisposalOrderStatus.REJECTED);
        order.setRejectReason(StringUtils.trim(rejectReason));
        order.setUpdateBy(operateContext.getOperateLoginName());
        order.setUpdateTime(DateUtils.getNowDate());

        return updateOrderOrThrow(order, DisposalOrderStatus.PENDING_CONFIRM, "报废单驳回失败，请刷新后重试");
    }

    /**
     * 查询可用于报废的资产列表
     */
    @Override
    public List<AmsAsset> selectAvailableDisposalAssetList(AmsAsset asset, Long currentOrderId)
    {
        return amsDisposalOrderMapper.selectAvailableDisposalAssetList(asset, currentOrderId);
    }

    /**
     * 批量新增报废单明细
     *
     * @param amsDisposalOrder 报废管理对象
     */
    public void insertAmsDisposalOrderItem(AmsDisposalOrder amsDisposalOrder)
    {
        List<AmsDisposalOrderItem> amsDisposalOrderItemList = amsDisposalOrder.getAmsDisposalOrderItemList();
        Long orderId = amsDisposalOrder.getOrderId();
        String disposalNo = amsDisposalOrder.getDisposalNo();
        if (StringUtils.isNotNull(amsDisposalOrderItemList))
        {
            List<AmsDisposalOrderItem> list = new ArrayList<AmsDisposalOrderItem>();
            for (AmsDisposalOrderItem amsDisposalOrderItem : amsDisposalOrderItemList)
            {
                amsDisposalOrderItem.setOrderId(orderId);
                amsDisposalOrderItem.setDisposalNo(disposalNo);
                amsDisposalOrderItem.setDelFlag(DEL_FLAG_NORMAL);
                list.add(amsDisposalOrderItem);
            }
            if (list.size() > 0)
            {
                amsDisposalOrderMapper.batchAmsDisposalOrderItem(list);
            }
        }
    }

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

    private AmsDisposalOrder requireOrderForUpdate(Long orderId, String expectedStatus, String errMsg)
    {
        AmsDisposalOrder order = amsDisposalOrderMapper.selectAmsDisposalOrderByOrderIdForUpdate(orderId);
        if (order == null)
        {
            throw new ServiceException("报废单不存在或已删除");
        }
        if (!expectedStatus.equals(order.getOrderStatus()))
        {
            throw new ServiceException(errMsg);
        }
        return order;
    }

    private int updateOrderOrThrow(AmsDisposalOrder order, String expectedStatus, String errorMessage)
    {
        order.getParams().put("expectedOrderStatus", expectedStatus);
        if (amsDisposalOrderMapper.updateAmsDisposalOrder(order) != 1)
        {
            throw new ServiceException(errorMessage);
        }
        return 1;
    }

    private void validateOrderRequest(AmsDisposalOrder order)
    {
        if (order == null)
        {
            throw new ServiceException("单据数据不能为空");
        }
        if (order.getAmsDisposalOrderItemList() == null || order.getAmsDisposalOrderItemList().isEmpty())
        {
            throw new ServiceException("报废明细资产不能为空");
        }
        validateLength(order.getDisposalMethod(), 64, "处置方式");
        validateLength(order.getRemark(), 500, "备注");
    }

    private void fillApplicantSnapshots(AmsDisposalOrder order, AmsDisposalOperateContext operateContext)
    {
        order.setApplicantId(operateContext.getOperateUserId());
        order.setApplicantName(StringUtils.trim(operateContext.getOperateUserName()));
        order.setApplyDeptId(operateContext.getOperateDeptId());
        order.setApplyDeptName(StringUtils.trim(operateContext.getOperateDeptName()));
    }

    private void fillDetailSnapshotsAndValidate(AmsDisposalOrder order, Long currentOrderId)
    {
        List<AmsDisposalOrderItem> items = order.getAmsDisposalOrderItemList();

        // 1. 应用层资产ID升序排序，以防并发加锁导致的数据库死锁
        List<AmsDisposalOrderItem> sortedItems = new ArrayList<>(items);
        sortedItems.sort(java.util.Comparator.comparing(AmsDisposalOrderItem::getAssetId));

        java.util.Set<Long> assetIds = new java.util.HashSet<>();

        for (AmsDisposalOrderItem item : sortedItems)
        {
            if (item.getAssetId() == null)
            {
                throw new ServiceException("明细资产ID不能为空");
            }
            if (!assetIds.add(item.getAssetId()))
            {
                throw new ServiceException("不能在一个报废单中选择相同的资产");
            }

            // 2. 悲观锁定资产，校验其前置实物状态
            AmsAsset asset = amsAssetMapper.selectAmsAssetByAssetIdForUpdate(item.getAssetId());
            if (asset == null || "1".equals(asset.getDelFlag()))
            {
                throw new ServiceException("资产不存在或已删除");
            }
            if (!AssetStatus.IN_STOCK.equals(asset.getAssetStatus()) && !AssetStatus.IN_USE.equals(asset.getAssetStatus()))
            {
                throw new ServiceException(StringUtils.format("资产【{}】当前状态【{}】不允许进行报废",
                        asset.getAssetCode(), asset.getAssetStatus()));
            }

            // 3. 校验跨单据占用
            int occupied = amsDisposalOrderMapper.countOtherActiveDisposalOrderByAssetId(item.getAssetId(), currentOrderId);
            if (occupied > 0)
            {
                throw new ServiceException(StringUtils.format("资产【{}】已被其他未完成报废单占用", asset.getAssetCode()));
            }

            // 4. 回填最新的物理状态和属性快照，防止过期脏写
            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());
            validateLength(item.getDisposalReason(), 500, "报废原因");
            validateLength(item.getDisposalRemark(), 500, "处置备注");
        }
    }

    private void validateOrderAssetsReady(AmsDisposalOrder order)
    {
        List<AmsDisposalOrderItem> items = order.getAmsDisposalOrderItemList();
        if (items == null || items.isEmpty())
        {
            throw new ServiceException("单据明细资产为空");
        }

        // 按 ID 升序锁定资产
        List<AmsDisposalOrderItem> sortedItems = new ArrayList<>(items);
        sortedItems.sort(java.util.Comparator.comparing(AmsDisposalOrderItem::getAssetId));

        for (AmsDisposalOrderItem item : sortedItems)
        {
            AmsAsset asset = amsAssetMapper.selectAmsAssetByAssetIdForUpdate(item.getAssetId());
            if (asset == null || "1".equals(asset.getDelFlag()))
            {
                throw new ServiceException("资产不存在或已删除");
            }
            if (!AssetStatus.IN_STOCK.equals(asset.getAssetStatus()) && !AssetStatus.IN_USE.equals(asset.getAssetStatus()))
            {
                throw new ServiceException(StringUtils.format("资产【{}】状态已发生改变，不能进行该操作", asset.getAssetCode()));
            }
            int occupied = amsDisposalOrderMapper.countOtherActiveDisposalOrderByAssetId(item.getAssetId(), order.getOrderId());
            if (occupied > 0)
            {
                throw new ServiceException(StringUtils.format("资产【{}】已被其他未完成报废单占用", asset.getAssetCode()));
            }
        }
    }

    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 validateOperateContext(AmsDisposalOperateContext operateContext)
    {
        if (operateContext == null)
        {
            throw new ServiceException("操作上下文不能为空");
        }
        validateOperator(operateContext.getOperateUserId(), operateContext.getOperateUserName(), "操作人");
        validateLoginName(operateContext.getOperateLoginName());
        validateLength(operateContext.getOperateDeptName(), 100, "操作人部门名称");
    }

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