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.TransferOrderStatus;
import com.ruoyi.asset.domain.AmsAsset;
import com.ruoyi.asset.domain.AmsAssetLocation;
import com.ruoyi.asset.domain.AmsTransferOrder;
import com.ruoyi.asset.domain.AmsTransferOrderItem;
import com.ruoyi.asset.domain.AmsWarehouse;
import com.ruoyi.asset.domain.AssetTransitionContext;
import com.ruoyi.asset.mapper.AmsAssetMapper;
import com.ruoyi.asset.mapper.AmsTransferOrderMapper;
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 AmsTransferOrderServiceImplTest
{
    @Mock
    private AmsTransferOrderMapper amsTransferOrderMapper;

    @Mock
    private AmsAssetMapper amsAssetMapper;

    @Mock
    private ISysCodeRuleService sysCodeRuleService;

    @Mock
    private IAmsWarehouseService amsWarehouseService;

    @Mock
    private IAmsAssetLocationService amsAssetLocationService;

    @Mock
    private IAssetStatusTransitionService assetStatusTransitionService;

    @InjectMocks
    private AmsTransferOrderServiceImpl service;

    /** 在库资产新增调拨草稿时，应生成单号、回填仓位快照并清空目标使用归属。 */
    @Test
    void insertStockTransferShouldGenerateCodeAndFillSnapshots()
    {
        AmsTransferOrder order = buildRequest(AssetStatus.IN_STOCK);
        AmsTransferOrderItem item = order.getAmsTransferOrderItemList().get(0);
        item.setNewDeptId(103L);
        item.setNewUserId(1L);
        when(sysCodeRuleService.nextCode("TRANSFER_ORDER")).thenReturn("DB202606120001");
        stubStockAsset();
        stubTargetWarehouseLocation();
        doAnswer(invocation -> {
            AmsTransferOrder inserted = invocation.getArgument(0);
            inserted.setOrderId(100L);
            return 1;
        }).when(amsTransferOrderMapper).insertAmsTransferOrder(any(AmsTransferOrder.class));
        when(amsTransferOrderMapper.batchAmsTransferOrderItem(anyList()))
                .thenAnswer(invocation -> ((List<?>) invocation.getArgument(0)).size());

        assertEquals(1, service.insertAmsTransferOrder(order, 1L, "管理员"));

        assertEquals("DB202606120001", order.getTransferNo());
        assertEquals(TransferOrderStatus.DRAFT, order.getOrderStatus());
        assertEquals(1L, order.getApplicantId());
        assertEquals("WH-001", item.getOldWarehouseCode());
        assertEquals("WH-002", item.getNewWarehouseCode());
        assertNull(item.getNewDeptId());
        assertNull(item.getNewUserId());
        assertEquals(100L, item.getOrderId());
        assertNotNull(item.getCreateTime());
    }

    /** 在用资产不能直接创建调拨单。 */
    @Test
    void insertInUseTransferShouldBeRejected()
    {
        AmsTransferOrder order = buildRequest(AssetStatus.IN_USE);
        when(sysCodeRuleService.nextCode("TRANSFER_ORDER")).thenReturn("DB202606120001");
        stubInUseAsset();

        ServiceException exception = assertThrows(ServiceException.class,
                () -> service.insertAmsTransferOrder(order, 1L, "管理员"));

        assertTrue(exception.getMessage().contains("不允许调拨"));
        verify(amsTransferOrderMapper, never()).insertAmsTransferOrder(any(AmsTransferOrder.class));
    }

    /** 被其他草稿或待确认调拨单占用的资产不得再次加入调拨单。 */
    @Test
    void insertShouldRejectAssetOccupiedByOtherActiveTransfer()
    {
        AmsTransferOrder order = buildRequest(AssetStatus.IN_STOCK);
        when(sysCodeRuleService.nextCode("TRANSFER_ORDER")).thenReturn("DB202606120001");
        stubStockAsset();
        when(amsTransferOrderMapper.countOtherActiveTransferOrderByAssetId(1L, null,
                TransferOrderStatus.DRAFT, TransferOrderStatus.PENDING_CONFIRM)).thenReturn(1);

        ServiceException exception = assertThrows(ServiceException.class,
                () -> service.insertAmsTransferOrder(order, 1L, "管理员"));

        assertTrue(exception.getMessage().contains("其他有效调拨单"));
        verify(amsTransferOrderMapper, never()).insertAmsTransferOrder(any(AmsTransferOrder.class));
    }

    /** 草稿提交前校验当前归属未变化，成功后进入待确认状态。 */
    @Test
    void submitShouldValidateAndMoveToPendingConfirm()
    {
        AmsTransferOrder order = buildPersistedOrder(TransferOrderStatus.DRAFT, AssetStatus.IN_STOCK);
        when(amsTransferOrderMapper.selectAmsTransferOrderByOrderIdForUpdate(100L)).thenReturn(order);
        stubStockAsset();
        stubTargetWarehouseLocation();
        when(amsTransferOrderMapper.submitAmsTransferOrder(any(AmsTransferOrder.class))).thenReturn(1);

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

        assertEquals(TransferOrderStatus.PENDING_CONFIRM, order.getOrderStatus());
        verify(amsTransferOrderMapper).submitAmsTransferOrder(order);
    }

    /** 待确认调拨单确认时，应逐条调用公共流转服务并写入确认信息。 */
    @Test
    void confirmShouldDelegateTransitionAndCompleteOrder()
    {
        AmsTransferOrder order = buildPersistedOrder(TransferOrderStatus.PENDING_CONFIRM, AssetStatus.IN_STOCK);
        when(amsTransferOrderMapper.selectAmsTransferOrderByOrderIdForUpdate(100L)).thenReturn(order);
        stubStockAsset();
        stubTargetWarehouseLocation();
        when(amsTransferOrderMapper.confirmAmsTransferOrder(any(AmsTransferOrder.class))).thenReturn(1);

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

        ArgumentCaptor<AssetTransitionContext> contextCaptor = ArgumentCaptor.forClass(AssetTransitionContext.class);
        verify(assetStatusTransitionService).confirmTransfer(
                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(TransferOrderStatus.TRANSFERRED, order.getOrderStatus());
        assertEquals("管理员", order.getConfirmUserName());
        assertNotNull(order.getConfirmTime());
    }

    /** 调拨草稿保存后的原归属发生变化时，提交必须阻断，避免覆盖新归属。 */
    @Test
    void submitShouldRejectStaleOldOwnership()
    {
        AmsTransferOrder order = buildPersistedOrder(TransferOrderStatus.DRAFT, AssetStatus.IN_STOCK);
        order.getAmsTransferOrderItemList().get(0).setOldLocationId(99L);
        when(amsTransferOrderMapper.selectAmsTransferOrderByOrderIdForUpdate(100L)).thenReturn(order);
        stubStockAsset();

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

        assertTrue(exception.getMessage().contains("当前归属已变化"));
        verify(amsTransferOrderMapper, never()).submitAmsTransferOrder(any(AmsTransferOrder.class));
    }

    /** 非草稿调拨单不得删除。 */
    @Test
    void deleteShouldRejectNonDraftOrder()
    {
        AmsTransferOrder order = new AmsTransferOrder();
        order.setOrderId(100L);
        order.setOrderStatus(TransferOrderStatus.PENDING_CONFIRM);
        when(amsTransferOrderMapper.selectAmsTransferOrderByOrderIdForUpdate(100L)).thenReturn(order);

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

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

    private AmsTransferOrder buildRequest(String assetStatus)
    {
        AmsTransferOrder order = new AmsTransferOrder();
        order.setCreateBy("admin");
        order.setTransferReason("调整归属");
        AmsTransferOrderItem item = new AmsTransferOrderItem();
        item.setAssetId(1L);
        item.setAssetStatus(assetStatus);
        item.setNewWarehouseId(2L);
        item.setNewLocationId(20L);
        order.setAmsTransferOrderItemList(Collections.singletonList(item));
        return order;
    }

    private AmsTransferOrder buildPersistedOrder(String orderStatus, String assetStatus)
    {
        AmsTransferOrder order = buildRequest(assetStatus);
        order.setOrderId(100L);
        order.setTransferNo("DB202606120001");
        order.setOrderStatus(orderStatus);
        AmsTransferOrderItem item = order.getAmsTransferOrderItemList().get(0);
        item.setItemId(101L);
        item.setOldWarehouseId(1L);
        item.setOldLocationId(10L);
        return order;
    }

    private void stubStockAsset()
    {
        AmsAsset asset = buildAsset(AssetStatus.IN_STOCK);
        when(amsAssetMapper.selectAmsAssetByAssetIdForUpdate(1L)).thenReturn(asset);
    }

    private void stubInUseAsset()
    {
        AmsAsset asset = buildAsset(AssetStatus.IN_USE);
        asset.setUseDeptId(105L);
        asset.setUseDeptName("测试部门");
        asset.setUseUserId(2L);
        asset.setUseUserName("测试用户");
        when(amsAssetMapper.selectAmsAssetByAssetIdForUpdate(1L)).thenReturn(asset);
    }

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

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

        AmsAssetLocation location = new AmsAssetLocation();
        location.setLocationId(20L);
        location.setLocationCode("LOC-020");
        location.setLocationName("二号仓A区");
        location.setWarehouseId(2L);
        location.setEnabled("Y");
        when(amsAssetLocationService.selectAmsAssetLocationByLocationId(20L)).thenReturn(location);
    }

}
