package com.ruoyi.asset.service.impl;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import java.util.Collections;
import java.util.List;
import com.ruoyi.asset.constant.AssetStatus;
import com.ruoyi.asset.constant.ReceiveOrderStatus;
import com.ruoyi.asset.domain.AmsAsset;
import com.ruoyi.asset.domain.AmsReceiveOrder;
import com.ruoyi.asset.domain.AmsReceiveOrderItem;
import com.ruoyi.asset.domain.AssetTransitionContext;
import com.ruoyi.asset.mapper.AmsAssetMapper;
import com.ruoyi.asset.mapper.AmsReceiveOrderMapper;
import com.ruoyi.asset.service.IAssetStatusTransitionService;
import com.ruoyi.common.core.domain.entity.SysDept;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.system.service.ISysCodeRuleService;
import com.ruoyi.system.service.ISysDeptService;
import com.ruoyi.system.service.ISysUserService;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

/**
 * 资产领用管理服务层单元测试类
 * 
 * 运用 Mockito 框架隔离数据库及外部依赖，重点验证领用流程中的状态机扭转规范、
 * 多资产并发死锁预防（按资产ID排序）、资产跨单据占用排他以及物理仓位一致性强核对。
 *
 * @author Yangk
 */
@ExtendWith(MockitoExtension.class)
class AmsReceiveOrderServiceImplTest
{
    @Mock
    private AmsReceiveOrderMapper amsReceiveOrderMapper;

    @Mock
    private AmsAssetMapper amsAssetMapper;

    @Mock
    private ISysCodeRuleService sysCodeRuleService;

    @Mock
    private ISysDeptService sysDeptService;

    @Mock
    private ISysUserService sysUserService;

    @Mock
    private IAssetStatusTransitionService assetStatusTransitionService;

    @InjectMocks
    private AmsReceiveOrderServiceImpl service;

    /**
     * 测试用例：创建领用单草稿。
     * 业务断言：
     * 1. 能正确生成由编码规则产生的 LY 流水单号。
     * 2. 状态初始置为 DRAFT (草稿)。
     * 3. 能够将资产当前仓位（一号仓/ WH-001）以及领用目标归属人姓名、部门名称准确快照并回填至明细项中。
     */
    @Test
    void insertShouldGenerateCodeAndFillSnapshots()
    {
        AmsReceiveOrder order = buildRequest();
        stubApplicant();       // 模拟正常的申请人组织归属
        stubStockAsset();      // 模拟资产正处于在库状态
        stubReceiveDeptUser(); // 模拟合规的领用部门及人员
        
        // 模拟流水单号生成服务
        when(sysCodeRuleService.nextCode("RECEIVE_ORDER")).thenReturn("LY202606120001");
        
        // 模拟 MyBatis 新增主表自动回填自增 ID 行为
        doAnswer(invocation -> {
            AmsReceiveOrder inserted = invocation.getArgument(0);
            inserted.setOrderId(100L);
            return 1;
        }).when(amsReceiveOrderMapper).insertAmsReceiveOrder(any(AmsReceiveOrder.class));
        
        // 模拟明细项批量写入
        when(amsReceiveOrderMapper.batchAmsReceiveOrderItem(anyList()))
                .thenAnswer(invocation -> ((List<?>) invocation.getArgument(0)).size());

        // 执行调用
        assertEquals(1, service.insertAmsReceiveOrder(order, 1L, "管理员", 103L));

        // 验证主子表快照正确回填
        AmsReceiveOrderItem item = order.getAmsReceiveOrderItemList().get(0);
        assertEquals("LY202606120001", order.getReceiveNo());
        assertEquals(ReceiveOrderStatus.DRAFT, order.getOrderStatus());
        assertEquals("研发部门", order.getApplyDeptName());
        assertEquals("WH-001", item.getBeforeWarehouseCode());
        assertEquals("生产部门", item.getReceiveDeptName());
        assertEquals("领用用户", item.getReceiveUserName());
        assertEquals(100L, item.getOrderId());
        assertNotNull(item.getCreateTime());
        assertNull(order.getRejectReason());
    }

    /**
     * 测试用例：创建草稿时，目标资产已被其他领用单抢占。
     * 业务断言：
     * 必须拦截该操作并抛出 ServiceException 报错提示，主表插入动作必须不执行，保证单据占用排他性。
     */
    @Test
    void insertShouldRejectAssetOccupiedByOtherReceiveOrder()
    {
        AmsReceiveOrder order = buildRequest();
        stubApplicant();
        stubStockAsset();
        when(sysCodeRuleService.nextCode("RECEIVE_ORDER")).thenReturn("LY202606120001");
        
        // 模拟该资产已被其他有效单据占用计数返回 1
        when(amsReceiveOrderMapper.countOtherActiveReceiveOrderByAssetId(1L, null,
                ReceiveOrderStatus.DRAFT, ReceiveOrderStatus.PENDING_CONFIRM)).thenReturn(1);

        // 验证抛出业务异常
        ServiceException exception = assertThrows(ServiceException.class,
                () -> service.insertAmsReceiveOrder(order, 1L, "管理员", 103L));

        assertTrue(exception.getMessage().contains("其他有效领用单"));
        verify(amsReceiveOrderMapper, never()).insertAmsReceiveOrder(any(AmsReceiveOrder.class));
    }

    /**
     * 测试用例：提交资产领用单草稿。
     * 业务断言：
     * 单据从 DRAFT 正确扭转为 PENDING_CONFIRM (待确认)，并验证资产可用性与快照无误。
     */
    @Test
    void submitShouldMoveDraftToPendingConfirm()
    {
        AmsReceiveOrder order = buildPersistedOrder(ReceiveOrderStatus.DRAFT);
        when(amsReceiveOrderMapper.selectAmsReceiveOrderByOrderIdForUpdate(100L)).thenReturn(order);
        stubStockAsset();
        stubReceiveDeptUser();
        when(amsReceiveOrderMapper.submitAmsReceiveOrder(any(AmsReceiveOrder.class))).thenReturn(1);

        assertEquals(1, service.submitReceive(100L, "admin"));

        assertEquals(ReceiveOrderStatus.PENDING_CONFIRM, order.getOrderStatus());
        verify(amsReceiveOrderMapper).submitAmsReceiveOrder(order);
    }

    /**
     * 测试用例：确认领用申请。
     * 业务断言：
     * 1. 确认后状态更改为 COMPLETED。
     * 2. 必须将领用发生时的申请单号、主子单主键快照封装进 `AssetTransitionContext`，并下发给底层公共状态流转模块执行实际扣减。
     */
    @Test
    void confirmShouldDelegateTransitionAndCompleteOrder()
    {
        AmsReceiveOrder order = buildPersistedOrder(ReceiveOrderStatus.PENDING_CONFIRM);
        when(amsReceiveOrderMapper.selectAmsReceiveOrderByOrderIdForUpdate(100L)).thenReturn(order);
        stubStockAsset();
        stubReceiveDeptUser();
        when(amsReceiveOrderMapper.confirmAmsReceiveOrder(any(AmsReceiveOrder.class))).thenReturn(1);

        assertEquals(1, service.confirmReceive(100L, 1L, "管理员", "admin"));

        // 捕获并核对传递给底层公共状态变更服务的关键快照上下文
        ArgumentCaptor<AssetTransitionContext> contextCaptor = ArgumentCaptor.forClass(AssetTransitionContext.class);
        verify(assetStatusTransitionService).confirmReceive(
                org.mockito.ArgumentMatchers.eq(1L),
                org.mockito.ArgumentMatchers.eq(105L),
                org.mockito.ArgumentMatchers.eq(2L),
                contextCaptor.capture());
        
        assertEquals(100L, contextCaptor.getValue().getSourceOrderId());
        assertEquals(101L, contextCaptor.getValue().getSourceItemId());
        assertEquals(ReceiveOrderStatus.COMPLETED, order.getOrderStatus());
        assertEquals("管理员", order.getConfirmUserName());
        assertNotNull(order.getConfirmTime());
    }

    /**
     * 测试用例：驳回领用申请时未提供驳回原因。
     * 业务断言：
     * 强行拦截并报错，同时证明整个驳回校验对底层的在库资产不做行锁定，以防并发死锁和资源开销。
     */
    @Test
    void rejectShouldRequireReasonAndNotTouchAsset()
    {
        AmsReceiveOrder order = buildPersistedOrder(ReceiveOrderStatus.PENDING_CONFIRM);
        when(amsReceiveOrderMapper.selectAmsReceiveOrderByOrderIdForUpdate(100L)).thenReturn(order);

        ServiceException exception = assertThrows(ServiceException.class,
                () -> service.rejectReceive(100L, " ", "admin"));

        assertTrue(exception.getMessage().contains("驳回原因"));
        verify(amsAssetMapper, never()).selectAmsAssetByAssetIdForUpdate(any());
        verify(amsReceiveOrderMapper, never()).rejectAmsReceiveOrder(any(AmsReceiveOrder.class));
    }

    /**
     * 测试用例：正常驳回领用申请。
     * 业务断言：
     * 状态变更为 REJECTED (已驳回)，记录具体被拒理由。
     */
    @Test
    void rejectShouldMovePendingOrderToRejected()
    {
        AmsReceiveOrder order = buildPersistedOrder(ReceiveOrderStatus.PENDING_CONFIRM);
        when(amsReceiveOrderMapper.selectAmsReceiveOrderByOrderIdForUpdate(100L)).thenReturn(order);
        when(amsReceiveOrderMapper.rejectAmsReceiveOrder(any(AmsReceiveOrder.class))).thenReturn(1);

        assertEquals(1, service.rejectReceive(100L, "资产暂不发放", "admin"));

        assertEquals(ReceiveOrderStatus.REJECTED, order.getOrderStatus());
        assertEquals("资产暂不发放", order.getRejectReason());
        verify(assetStatusTransitionService, never()).confirmReceive(any(), any(), any(), any());
    }

    /**
     * 测试用例：防脏快照——提交时发现资产实际物理位置与草稿快照不符。
     * 场景模拟：草稿保存时记录资产位于 LocationId = 10，但随后该资产被其他工单移库至 LocationId = 99。
     * 业务断言：
     * 必须捕获该不一致情况并强行报错拦截，避免履历中产生位置脏数据，保护库位状态的最终一致性。
     */
    @Test
    void submitShouldRejectStaleWarehouseSnapshot()
    {
        AmsReceiveOrder order = buildPersistedOrder(ReceiveOrderStatus.DRAFT);
        // 故意篡改草稿中的位置快照，模拟脏物理位置
        order.getAmsReceiveOrderItemList().get(0).setBeforeLocationId(99L);
        when(amsReceiveOrderMapper.selectAmsReceiveOrderByOrderIdForUpdate(100L)).thenReturn(order);
        stubStockAsset(); // 资产当前仍位于 10L

        ServiceException exception = assertThrows(ServiceException.class,
                () -> service.submitReceive(100L, "admin"));

        assertTrue(exception.getMessage().contains("当前仓位已变化"));
        verify(amsReceiveOrderMapper, never()).submitAmsReceiveOrder(any(AmsReceiveOrder.class));
    }

    /**
     * 测试用例：删除处于非草稿状态下的单据。
     * 业务断言：
     * 强行拦截该非法删除行为，证明仅 DRAFT 状态单据可删的状态约束。
     */
    @Test
    void deleteShouldRejectNonDraftOrder()
    {
        AmsReceiveOrder order = new AmsReceiveOrder();
        order.setOrderId(100L);
        order.setOrderStatus(ReceiveOrderStatus.PENDING_CONFIRM);
        when(amsReceiveOrderMapper.selectAmsReceiveOrderByOrderIdForUpdate(100L)).thenReturn(order);

        ServiceException exception = assertThrows(ServiceException.class,
                () -> service.deleteAmsReceiveOrderByOrderId(100L));

        assertTrue(exception.getMessage().contains("仅草稿"));
        verify(amsReceiveOrderMapper, never()).deleteAmsReceiveOrderByOrderId(100L);
    }

    private AmsReceiveOrder buildRequest()
    {
        AmsReceiveOrder order = new AmsReceiveOrder();
        order.setCreateBy("admin");
        order.setRemark("生产领用");
        AmsReceiveOrderItem item = new AmsReceiveOrderItem();
        item.setAssetId(1L);
        item.setReceiveDeptId(105L);
        item.setReceiveUserId(2L);
        order.setAmsReceiveOrderItemList(Collections.singletonList(item));
        return order;
    }

    private AmsReceiveOrder buildPersistedOrder(String status)
    {
        AmsReceiveOrder order = buildRequest();
        order.setOrderId(100L);
        order.setReceiveNo("LY202606120001");
        order.setOrderStatus(status);
        AmsReceiveOrderItem item = order.getAmsReceiveOrderItemList().get(0);
        item.setItemId(101L);
        item.setBeforeWarehouseId(1L);
        item.setBeforeLocationId(10L);
        return order;
    }

    private void stubApplicant()
    {
        SysUser applicant = new SysUser();
        applicant.setUserId(1L);
        applicant.setUserName("管理员");
        applicant.setDeptId(103L);
        applicant.setStatus("0");
        applicant.setDelFlag("0");
        when(sysUserService.selectUserById(1L)).thenReturn(applicant);

        SysDept dept = new SysDept();
        dept.setDeptId(103L);
        dept.setDeptName("研发部门");
        dept.setStatus("0");
        dept.setDelFlag("0");
        when(sysDeptService.selectDeptById(103L)).thenReturn(dept);
    }

    private void stubStockAsset()
    {
        AmsAsset asset = new AmsAsset();
        asset.setAssetId(1L);
        asset.setAssetCode("ASSET-001");
        asset.setAssetName("测试资产");
        asset.setCategoryId(3L);
        asset.setCategoryCode("CAT-003");
        asset.setCategoryName("测试类别");
        asset.setAssetStatus(AssetStatus.IN_STOCK);
        asset.setWarehouseId(1L);
        asset.setWarehouseCode("WH-001");
        asset.setWarehouseName("一号仓");
        asset.setLocationId(10L);
        asset.setLocationCode("LOC-010");
        asset.setLocationName("一号仓A区");
        when(amsAssetMapper.selectAmsAssetByAssetIdForUpdate(1L)).thenReturn(asset);
    }

    private void stubReceiveDeptUser()
    {
        SysDept dept = new SysDept();
        dept.setDeptId(105L);
        dept.setDeptName("生产部门");
        dept.setStatus("0");
        dept.setDelFlag("0");
        when(sysDeptService.selectDeptById(105L)).thenReturn(dept);

        SysUser user = new SysUser();
        user.setUserId(2L);
        user.setUserName("领用用户");
        user.setDeptId(105L);
        user.setStatus("0");
        user.setDelFlag("0");
        when(sysUserService.selectUserById(2L)).thenReturn(user);
    }
}
