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.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import java.util.Collections;
import java.util.Arrays;
import java.util.List;
import com.ruoyi.asset.constant.AssetStatus;
import com.ruoyi.asset.constant.ReturnOrderStatus;
import com.ruoyi.asset.domain.AmsAsset;
import com.ruoyi.asset.domain.AmsAssetLocation;
import com.ruoyi.asset.domain.AmsReturnAssetCandidate;
import com.ruoyi.asset.domain.AmsReturnOrder;
import com.ruoyi.asset.domain.AmsReturnOrderItem;
import com.ruoyi.asset.domain.AmsWarehouse;
import com.ruoyi.asset.domain.AssetTransitionContext;
import com.ruoyi.asset.mapper.AmsAssetMapper;
import com.ruoyi.asset.mapper.AmsReturnOrderMapper;
import com.ruoyi.asset.service.IAmsAssetLocationService;
import com.ruoyi.asset.service.IAmsWarehouseService;
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;

@ExtendWith(MockitoExtension.class)
class AmsReturnOrderServiceImplTest
{
    @Mock
    private AmsReturnOrderMapper amsReturnOrderMapper;

    @Mock
    private AmsAssetMapper amsAssetMapper;

    @Mock
    private ISysCodeRuleService sysCodeRuleService;

    @Mock
    private IAmsWarehouseService amsWarehouseService;

    @Mock
    private IAmsAssetLocationService amsAssetLocationService;

    @Mock
    private ISysDeptService sysDeptService;

    @Mock
    private ISysUserService sysUserService;

    @Mock
    private IAssetStatusTransitionService assetStatusTransitionService;

    @InjectMocks
    private AmsReturnOrderServiceImpl service;

    /** 新增退库草稿应生成单号，并使用台账及主数据回填快照。 */
    @Test
    void insertShouldGenerateCodeAndFillSnapshots()
    {
        AmsReturnOrder order = buildRequest();
        stubApplicant();
        stubInUseAsset();
        stubTargetWarehouseLocation();
        when(sysCodeRuleService.nextCode("RETURN_ORDER")).thenReturn("TK202606150001");
        doAnswer(invocation -> {
            AmsReturnOrder inserted = invocation.getArgument(0);
            inserted.setOrderId(100L);
            return 1;
        }).when(amsReturnOrderMapper).insertAmsReturnOrder(any(AmsReturnOrder.class));
        when(amsReturnOrderMapper.batchAmsReturnOrderItem(anyList()))
                .thenAnswer(invocation -> ((List<?>) invocation.getArgument(0)).size());

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

        AmsReturnOrderItem item = order.getAmsReturnOrderItemList().get(0);
        assertEquals("TK202606150001", order.getReturnNo());
        assertEquals(ReturnOrderStatus.DRAFT, order.getOrderStatus());
        assertEquals("研发部门", order.getApplyDeptName());
        assertEquals("二号仓", order.getReceiveWarehouseName());
        assertEquals("测试部门", item.getBeforeDeptName());
        assertEquals("测试用户", item.getBeforeUserName());
        assertEquals("二号仓A区", item.getAfterLocationName());
        assertEquals(100L, item.getOrderId());
        assertNotNull(item.getCreateTime());
    }

    /** 退库位置必须属于主表所选接收仓库。 */
    @Test
    void insertShouldRejectLocationOutsideWarehouse()
    {
        AmsReturnOrder order = buildRequest();
        stubApplicant();
        stubInUseAsset();
        stubTargetWarehouseLocation();
        when(sysCodeRuleService.nextCode("RETURN_ORDER")).thenReturn("TK202606150001");
        AmsAssetLocation location = new AmsAssetLocation();
        location.setLocationId(20L);
        location.setWarehouseId(99L);
        location.setEnabled("Y");
        when(amsAssetLocationService.selectAmsAssetLocationByLocationId(20L)).thenReturn(location);

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

        assertTrue(exception.getMessage().contains("不属于接收仓库"));
        verify(amsReturnOrderMapper, never()).insertAmsReturnOrder(any(AmsReturnOrder.class));
    }

    /** 被其他草稿或待确认退库单占用的资产不得再次加入退库单。 */
    @Test
    void insertShouldRejectAssetOccupiedByOtherActiveReturn()
    {
        AmsReturnOrder order = buildRequest();
        stubApplicant();
        stubInUseAsset();
        stubTargetWarehouse();
        when(sysCodeRuleService.nextCode("RETURN_ORDER")).thenReturn("TK202606150001");
        when(amsReturnOrderMapper.countOtherActiveReturnOrderByAssetId(1L, null,
                ReturnOrderStatus.DRAFT, ReturnOrderStatus.PENDING_CONFIRM)).thenReturn(1);

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

        assertTrue(exception.getMessage().contains("其他有效退库单"));
        verify(amsReturnOrderMapper, never()).insertAmsReturnOrder(any(AmsReturnOrder.class));
    }

    /** 同一退库单中的可追溯资产必须来自同一领用前仓库。 */
    @Test
    void insertShouldRejectAssetsFromDifferentOriginWarehouses()
    {
        AmsReturnOrder order = buildRequestWithAssets(1L, 2L);
        stubApplicant();
        stubInUseAsset(1L);
        stubInUseAsset(2L);
        stubTargetWarehouseLocation();
        when(sysCodeRuleService.nextCode("RETURN_ORDER")).thenReturn("TK202606150001");
        when(amsReturnOrderMapper.selectReturnAssetCandidateByAssetId(1L))
                .thenReturn(buildCandidate(1L, 10L));
        when(amsReturnOrderMapper.selectReturnAssetCandidateByAssetId(2L))
                .thenReturn(buildCandidate(2L, 20L));

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

        assertTrue(exception.getMessage().contains("原领用仓库"));
        verify(amsReturnOrderMapper, never()).insertAmsReturnOrder(any(AmsReturnOrder.class));
    }

    /** 缺少领用来源的资产不能与可追溯资产混在同一退库单。 */
    @Test
    void insertShouldRejectMixedTraceableAndMissingOrigin()
    {
        AmsReturnOrder order = buildRequestWithAssets(1L, 2L);
        stubApplicant();
        stubInUseAsset(1L);
        stubInUseAsset(2L);
        stubTargetWarehouseLocation();
        when(sysCodeRuleService.nextCode("RETURN_ORDER")).thenReturn("TK202606150001");
        when(amsReturnOrderMapper.selectReturnAssetCandidateByAssetId(1L))
                .thenReturn(buildCandidate(1L, 10L));

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

        assertTrue(exception.getMessage().contains("来源类型不一致"));
        verify(amsReturnOrderMapper, never()).insertAmsReturnOrder(any(AmsReturnOrder.class));
    }

    /** 草稿提交前校验资产仍在用且使用归属未变化。 */
    @Test
    void submitShouldMoveToPendingConfirm()
    {
        AmsReturnOrder order = buildPersistedOrder(ReturnOrderStatus.DRAFT);
        when(amsReturnOrderMapper.selectAmsReturnOrderByOrderIdForUpdate(100L)).thenReturn(order);
        stubInUseAsset();
        stubTargetWarehouseLocation();
        when(amsReturnOrderMapper.submitAmsReturnOrder(any(AmsReturnOrder.class))).thenReturn(1);

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

        assertEquals(ReturnOrderStatus.PENDING_CONFIRM, order.getOrderStatus());
        verify(amsReturnOrderMapper).submitAmsReturnOrder(order);
    }

    /** 确认退库应委托公共流转服务，并记录确认信息。 */
    @Test
    void confirmShouldDelegateTransitionAndCompleteOrder()
    {
        AmsReturnOrder order = buildPersistedOrder(ReturnOrderStatus.PENDING_CONFIRM);
        when(amsReturnOrderMapper.selectAmsReturnOrderByOrderIdForUpdate(100L)).thenReturn(order);
        stubInUseAsset();
        stubTargetWarehouseLocation();
        when(amsReturnOrderMapper.confirmAmsReturnOrder(any(AmsReturnOrder.class))).thenReturn(1);

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

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

    /** 草稿保存后使用人发生变化时，提交必须阻断。 */
    @Test
    void submitShouldRejectStaleUseOwnership()
    {
        AmsReturnOrder order = buildPersistedOrder(ReturnOrderStatus.DRAFT);
        order.getAmsReturnOrderItemList().get(0).setBeforeUserId(99L);
        when(amsReturnOrderMapper.selectAmsReturnOrderByOrderIdForUpdate(100L)).thenReturn(order);
        stubInUseAsset();
        stubTargetWarehouse();

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

        assertTrue(exception.getMessage().contains("当前使用归属已变化"));
        verify(amsReturnOrderMapper, never()).submitAmsReturnOrder(any(AmsReturnOrder.class));
    }

    /** 非草稿退库单不得删除。 */
    @Test
    void deleteShouldRejectNonDraftOrder()
    {
        AmsReturnOrder order = new AmsReturnOrder();
        order.setOrderId(100L);
        order.setOrderStatus(ReturnOrderStatus.PENDING_CONFIRM);
        when(amsReturnOrderMapper.selectAmsReturnOrderByOrderIdForUpdate(100L)).thenReturn(order);

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

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

    private AmsReturnOrder buildRequest()
    {
        AmsReturnOrder order = new AmsReturnOrder();
        order.setCreateBy("admin");
        order.setReceiveWarehouseId(2L);
        AmsReturnOrderItem item = new AmsReturnOrderItem();
        item.setAssetId(1L);
        item.setAfterLocationId(20L);
        order.setAmsReturnOrderItemList(Collections.singletonList(item));
        return order;
    }

    private AmsReturnOrder buildRequestWithAssets(Long... assetIds)
    {
        AmsReturnOrder order = new AmsReturnOrder();
        order.setCreateBy("admin");
        order.setReceiveWarehouseId(2L);
        order.setAmsReturnOrderItemList(Arrays.stream(assetIds).map(assetId -> {
            AmsReturnOrderItem item = new AmsReturnOrderItem();
            item.setAssetId(assetId);
            item.setAfterLocationId(20L);
            return item;
        }).toList());
        return order;
    }

    private AmsReturnOrder buildPersistedOrder(String status)
    {
        AmsReturnOrder order = buildRequest();
        order.setOrderId(100L);
        order.setReturnNo("TK202606150001");
        order.setOrderStatus(status);
        AmsReturnOrderItem item = order.getAmsReturnOrderItemList().get(0);
        item.setItemId(101L);
        item.setBeforeDeptId(105L);
        item.setBeforeUserId(2L);
        item.setAfterWarehouseId(2L);
        return order;
    }

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

        SysUser user = new SysUser();
        user.setUserId(1L);
        user.setUserName("管理员");
        user.setDeptId(103L);
        user.setStatus("0");
        user.setDelFlag("0");
        when(sysUserService.selectUserById(1L)).thenReturn(user);
    }

    private void stubInUseAsset()
    {
        stubInUseAsset(1L);
    }

    private void stubInUseAsset(Long assetId)
    {
        AmsAsset asset = new AmsAsset();
        asset.setAssetId(assetId);
        asset.setAssetCode("ASSET-" + assetId);
        asset.setAssetName("测试资产");
        asset.setCategoryId(3L);
        asset.setCategoryCode("CAT-003");
        asset.setCategoryName("测试类别");
        asset.setAssetStatus(AssetStatus.IN_USE);
        asset.setUseDeptId(105L);
        asset.setUseDeptName("测试部门");
        asset.setUseUserId(2L);
        asset.setUseUserName("测试用户");
        when(amsAssetMapper.selectAmsAssetByAssetIdForUpdate(assetId)).thenReturn(asset);
    }

    private AmsReturnAssetCandidate buildCandidate(Long warehouseId, Long locationId)
    {
        AmsReturnAssetCandidate candidate = new AmsReturnAssetCandidate();
        candidate.setOriginWarehouseId(warehouseId);
        candidate.setOriginWarehouseName("原仓库" + warehouseId);
        candidate.setOriginWarehouseEnabled("Y");
        candidate.setOriginLocationId(locationId);
        candidate.setOriginLocationName("原位置" + locationId);
        candidate.setOriginLocationEnabled("Y");
        return candidate;
    }

    private void stubTargetWarehouseLocation()
    {
        stubTargetWarehouse();

        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);
    }

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