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.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.lenient;
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.InboundOrderStatus;
import com.ruoyi.asset.domain.AmsAsset;
import com.ruoyi.asset.domain.AmsAssetLocation;
import com.ruoyi.asset.domain.AmsInboundOrder;
import com.ruoyi.asset.domain.AmsInboundOrderItem;
import com.ruoyi.asset.domain.AmsWarehouse;
import com.ruoyi.asset.domain.AssetTransitionContext;
import com.ruoyi.asset.mapper.AmsAssetMapper;
import com.ruoyi.asset.mapper.AmsInboundOrderMapper;
import com.ruoyi.asset.service.IAmsAssetLocationService;
import com.ruoyi.asset.service.IAmsWarehouseService;
import com.ruoyi.asset.service.IAssetStatusTransitionService;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.system.service.ISysCodeRuleService;
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;

@ExtendWith(MockitoExtension.class)
class AmsInboundOrderServiceImplTest
{
    @Mock
    private AmsInboundOrderMapper amsInboundOrderMapper;

    @Mock
    private AmsAssetMapper amsAssetMapper;

    @Mock
    private ISysCodeRuleService sysCodeRuleService;

    @Mock
    private IAmsWarehouseService amsWarehouseService;

    @Mock
    private IAmsAssetLocationService amsAssetLocationService;

    @Mock
    private IAssetStatusTransitionService assetStatusTransitionService;

    @InjectMocks
    private AmsInboundOrderServiceImpl service;

    /** 新增草稿时由服务层生成单号，并从主数据回填所有快照。 */
    @Test
    void insertShouldGenerateCodeAndFillSnapshots()
    {
        AmsInboundOrder order = buildDraftRequest();
        when(sysCodeRuleService.nextCode("INBOUND_ORDER")).thenReturn("RK202606100001");
        stubMasterData();
        when(amsInboundOrderMapper.countOtherDraftInboundOrderByAssetId(1L, null, InboundOrderStatus.DRAFT))
                .thenReturn(0);
        doAnswer(invocation -> {
            AmsInboundOrder inserted = invocation.getArgument(0);
            inserted.setOrderId(100L);
            return 1;
        }).when(amsInboundOrderMapper).insertAmsInboundOrder(any(AmsInboundOrder.class));
        when(amsInboundOrderMapper.batchAmsInboundOrderItem(anyList()))
                .thenAnswer(invocation -> ((List<?>) invocation.getArgument(0)).size());

        int rows = service.insertAmsInboundOrder(order);

        assertEquals(1, rows);
        assertEquals(100L, order.getOrderId());
        assertEquals("RK202606100001", order.getInboundNo());
        assertEquals(InboundOrderStatus.DRAFT, order.getOrderStatus());
        assertEquals("WH-002", order.getWarehouseCode());
        assertEquals("二号仓", order.getWarehouseName());
        AmsInboundOrderItem item = order.getAmsInboundOrderItemList().get(0);
        assertEquals(100L, item.getOrderId());
        assertEquals("ASSET-001", item.getAssetCode());
        assertEquals("测试资产", item.getAssetName());
        assertEquals("LOC-020", item.getLocationCode());
        assertEquals(1, item.getInboundQuantity());
        assertEquals("0", item.getDelFlag());
        assertNotNull(item.getCreateTime());
    }

    /** 同一待入库资产被其他有效草稿占用时必须拒绝保存。 */
    @Test
    void insertShouldRejectAssetReservedByOtherDraft()
    {
        AmsInboundOrder order = buildDraftRequest();
        when(sysCodeRuleService.nextCode("INBOUND_ORDER")).thenReturn("RK202606100001");
        stubMasterData();
        when(amsInboundOrderMapper.countOtherDraftInboundOrderByAssetId(1L, null, InboundOrderStatus.DRAFT))
                .thenReturn(1);

        ServiceException exception = assertThrows(ServiceException.class,
                () -> service.insertAmsInboundOrder(order));

        assertTrue(exception.getMessage().contains("其他草稿"));
        verify(amsInboundOrderMapper, never()).insertAmsInboundOrder(any(AmsInboundOrder.class));
    }

    /** 分页候选查询固定筛选待入库资产并排除其他草稿占用。 */
    @Test
    void selectAvailableInboundAssetListShouldDelegateFixedStatuses()
    {
        AmsAsset query = new AmsAsset();
        List<AmsAsset> expected = List.of(new AmsAsset());
        when(amsInboundOrderMapper.selectAvailableInboundAssetList(query, 100L,
                AssetStatus.PENDING_INBOUND, InboundOrderStatus.DRAFT)).thenReturn(expected);

        assertEquals(expected, service.selectAvailableInboundAssetList(query, 100L));
    }

    /** 已完成入库单必须拒绝修改，避免绕过页面直接篡改单据。 */
    @Test
    void updateShouldRejectCompletedOrder()
    {
        AmsInboundOrder completed = new AmsInboundOrder();
        completed.setOrderId(100L);
        completed.setOrderStatus(InboundOrderStatus.INBOUND_DONE);
        when(amsInboundOrderMapper.selectAmsInboundOrderByOrderIdForUpdate(100L)).thenReturn(completed);

        AmsInboundOrder request = new AmsInboundOrder();
        request.setOrderId(100L);
        ServiceException exception = assertThrows(ServiceException.class,
                () -> service.updateAmsInboundOrder(request));

        assertTrue(exception.getMessage().contains("仅草稿"));
        verify(amsInboundOrderMapper, never()).updateAmsInboundOrder(any(AmsInboundOrder.class));
    }

    /** 确认入库必须逐条调用公共状态流转服务，并完成单据确认信息。 */
    @Test
    void confirmShouldDelegateTransitionAndCompleteOrder()
    {
        AmsInboundOrder order = new AmsInboundOrder();
        order.setOrderId(100L);
        order.setInboundNo("RK202606100001");
        order.setWarehouseId(2L);
        order.setOrderStatus(InboundOrderStatus.DRAFT);
        AmsInboundOrderItem item = new AmsInboundOrderItem();
        item.setItemId(101L);
        item.setAssetId(1L);
        item.setLocationId(20L);
        order.setAmsInboundOrderItemList(Collections.singletonList(item));
        when(amsInboundOrderMapper.selectAmsInboundOrderByOrderIdForUpdate(100L)).thenReturn(order);
        when(amsInboundOrderMapper.confirmAmsInboundOrder(any(AmsInboundOrder.class))).thenReturn(1);

        int rows = service.confirmInbound(100L, 1L, "管理员", "admin");

        assertEquals(1, rows);
        ArgumentCaptor<AssetTransitionContext> contextCaptor = ArgumentCaptor.forClass(AssetTransitionContext.class);
        verify(assetStatusTransitionService).confirmInbound(
                org.mockito.ArgumentMatchers.eq(1L),
                org.mockito.ArgumentMatchers.eq(2L),
                org.mockito.ArgumentMatchers.eq(20L),
                contextCaptor.capture());
        assertEquals(100L, contextCaptor.getValue().getSourceOrderId());
        assertEquals(101L, contextCaptor.getValue().getSourceItemId());
        assertEquals("admin", contextCaptor.getValue().getOperateLoginName());
        assertEquals(InboundOrderStatus.INBOUND_DONE, order.getOrderStatus());
        assertEquals(1L, order.getInboundUserId());
        assertEquals("管理员", order.getInboundUserName());
        assertNotNull(order.getInboundTime());
    }

    /** 已完成入库单必须拒绝删除。 */
    @Test
    void deleteShouldRejectCompletedOrder()
    {
        AmsInboundOrder completed = new AmsInboundOrder();
        completed.setOrderId(100L);
        completed.setOrderStatus(InboundOrderStatus.INBOUND_DONE);
        when(amsInboundOrderMapper.selectAmsInboundOrderByOrderIdForUpdate(100L)).thenReturn(completed);

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

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

    private AmsInboundOrder buildDraftRequest()
    {
        AmsInboundOrder order = new AmsInboundOrder();
        order.setWarehouseId(2L);
        order.setCreateBy("admin");
        AmsInboundOrderItem item = new AmsInboundOrderItem();
        item.setAssetId(1L);
        item.setLocationId(20L);
        order.setAmsInboundOrderItemList(Collections.singletonList(item));
        return order;
    }

    private void stubMasterData()
    {
        AmsWarehouse warehouse = new AmsWarehouse();
        warehouse.setWarehouseId(2L);
        warehouse.setWarehouseCode("WH-002");
        warehouse.setWarehouseName("二号仓");
        warehouse.setEnabled("Y");
        when(amsWarehouseService.selectAmsWarehouseByWarehouseId(2L)).thenReturn(warehouse);

        AmsAsset asset = new AmsAsset();
        asset.setAssetId(1L);
        asset.setAssetCode("ASSET-001");
        asset.setAssetName("测试资产");
        asset.setCategoryId(3L);
        asset.setCategoryCode("CAT-003");
        asset.setCategoryName("测试类别");
        asset.setSpecModel("SPEC-001");
        asset.setBrand("测试品牌");
        asset.setAssetStatus(AssetStatus.PENDING_INBOUND);
        when(amsAssetMapper.selectAmsAssetByAssetIdForUpdate(1L)).thenReturn(asset);

        AmsAssetLocation location = new AmsAssetLocation();
        location.setLocationId(20L);
        location.setWarehouseId(2L);
        location.setLocationCode("LOC-020");
        location.setLocationName("二号仓A区");
        location.setEnabled("Y");
        // 草稿占用冲突会在位置校验前失败，因此该公共桩允许部分用例不消费。
        lenient().when(amsAssetLocationService.selectAmsAssetLocationByLocationId(20L)).thenReturn(location);
    }
}
