package com.ruoyi.asset.service.impl;

import static org.junit.jupiter.api.Assertions.assertEquals;
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.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import java.util.List;
import com.ruoyi.asset.constant.AssetStatus;
import com.ruoyi.asset.domain.AmsAsset;
import com.ruoyi.asset.domain.AmsAssetCategory;
import com.ruoyi.asset.domain.AmsAssetLocation;
import com.ruoyi.asset.domain.AmsWarehouse;
import com.ruoyi.asset.mapper.AmsAssetMapper;
import com.ruoyi.asset.service.IAmsAssetCategoryService;
import com.ruoyi.asset.service.IAmsAssetLocationService;
import com.ruoyi.asset.service.IAmsWarehouseService;
import com.ruoyi.common.core.domain.entity.SysDictData;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.system.service.ISysDeptService;
import com.ruoyi.system.service.ISysDictDataService;
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 AmsAssetServiceImplTest
{
    @Mock
    private AmsAssetMapper amsAssetMapper;

    @Mock
    private IAmsAssetCategoryService amsAssetCategoryService;

    @Mock
    private IAmsWarehouseService amsWarehouseService;

    @Mock
    private IAmsAssetLocationService amsAssetLocationService;

    @Mock
    private ISysDeptService sysDeptService;

    @Mock
    private ISysUserService sysUserService;

    @Mock
    private ISysDictDataService sysDictDataService;

    @InjectMocks
    private AmsAssetServiceImpl service;

    /** Web绑定资产选择只能通过专用Mapper查询，并固定排除已报废资产 */
    @Test
    void selectBindableAmsAssetListShouldExcludeDisposedAssets()
    {
        AmsAsset query = new AmsAsset();
        query.setAssetCode("ASSET");
        List<AmsAsset> expected = List.of(buildAsset());
        when(amsAssetMapper.selectBindableAmsAssetList(query, AssetStatus.DISPOSED)).thenReturn(expected);

        assertEquals(expected, service.selectBindableAmsAssetList(query));

        verify(amsAssetMapper).selectBindableAmsAssetList(query, AssetStatus.DISPOSED);
    }

    /** 新建资产时，无论前端传入什么状态，必须强制为在库并清除使用归属 */
    @Test
    void insertAmsAssetShouldForceStockAndClearUseOwnership()
    {
        AmsAsset incoming = buildAsset();
        incoming.setAssetStatus(AssetStatus.DISPOSED);
        incoming.setUseDeptId(3L);
        incoming.setUseUserId(4L);
        incoming.setTagId(9L);
        incoming.setTagCode("TAG-001");
        incoming.setEpcCode("EPC-001");
        stubMasterData();
        when(amsAssetMapper.insertAmsAsset(any(AmsAsset.class))).thenReturn(1);

        service.insertAmsAsset(incoming);

        ArgumentCaptor<AmsAsset> captor = ArgumentCaptor.forClass(AmsAsset.class);
        verify(amsAssetMapper).insertAmsAsset(captor.capture());
        assertEquals(AssetStatus.IN_STOCK, captor.getValue().getAssetStatus());
        assertNull(captor.getValue().getUseDeptId());
        assertNull(captor.getValue().getUseUserId());
        assertNull(captor.getValue().getTagId());
        assertNull(captor.getValue().getTagCode());
        assertNull(captor.getValue().getEpcCode());
    }

    /** 有业务引用的资产，编辑时修改归属字段应被拒绝 */
    @Test
    void updateAmsAssetShouldRejectOwnershipChangeAfterBusinessReference()
    {
        AmsAsset current = buildAsset();
        current.setUseDeptId(3L);
        current.setUseUserId(4L);
        AmsAsset incoming = buildAsset();
        incoming.setWarehouseId(2L);
        when(amsAssetMapper.selectAmsAssetByAssetId(1L)).thenReturn(current);
        when(amsAssetMapper.countAssetReferences(1L)).thenReturn(1);

        ServiceException exception = assertThrows(ServiceException.class,
                () -> service.updateAmsAsset(incoming));

        assertTrue(exception.getMessage().contains("业务流转"));
        verify(amsAssetMapper, never()).updateAmsAsset(any(AmsAsset.class));
    }

    /** 未进入业务流程的在库资产编辑时，应保持状态为在库并清除使用归属 */
    @Test
    void updateAmsAssetShouldPreserveStatusAndClearStockUseOwnershipBeforeFirstBusiness()
    {
        AmsAsset current = buildAsset();
        current.setAssetStatus(AssetStatus.IN_STOCK);
        AmsAsset incoming = buildAsset();
        incoming.setAssetStatus(AssetStatus.DISPOSED);
        incoming.setUseDeptId(3L);
        incoming.setUseUserId(4L);
        when(amsAssetMapper.selectAmsAssetByAssetId(1L)).thenReturn(current);
        when(amsAssetMapper.countAssetReferences(1L)).thenReturn(0);
        when(amsAssetMapper.updateAmsAsset(any(AmsAsset.class))).thenReturn(1);
        stubMasterData();

        service.updateAmsAsset(incoming);

        ArgumentCaptor<AmsAsset> captor = ArgumentCaptor.forClass(AmsAsset.class);
        verify(amsAssetMapper).updateAmsAsset(captor.capture());
        assertEquals(AssetStatus.IN_STOCK, captor.getValue().getAssetStatus());
        assertNull(captor.getValue().getUseDeptId());
        assertNull(captor.getValue().getUseUserId());
    }

    /** 有业务引用的资产编辑时，保留历史归属快照且不重新验证历史主数据是否有效 */
    @Test
    void updateAmsAssetShouldPreserveReferencedOwnershipWithoutRevalidatingHistoricalMasterData()
    {
        AmsAsset current = buildAsset();
        current.setWarehouseCode("WH-001");
        current.setWarehouseName("历史仓库");
        current.setLocationCode("LOC-001");
        current.setLocationName("历史位置");
        current.setUseDeptId(3L);
        current.setUseDeptName("历史部门");
        current.setUseUserId(4L);
        current.setUseUserName("历史使用人");
        current.setTagId(9L);
        current.setTagCode("TAG-001");
        current.setEpcCode("EPC-001");
        AmsAsset incoming = buildAsset();
        incoming.setUseDeptId(3L);
        incoming.setUseUserId(4L);
        incoming.setTagId(10L);
        incoming.setTagCode("TAG-OTHER");
        incoming.setEpcCode("EPC-OTHER");
        when(amsAssetMapper.selectAmsAssetByAssetId(1L)).thenReturn(current);
        when(amsAssetMapper.countAssetReferences(1L)).thenReturn(1);
        when(amsAssetMapper.updateAmsAsset(any(AmsAsset.class))).thenReturn(1);
        AmsAssetCategory category = new AmsAssetCategory();
        category.setCategoryId(1L);
        category.setCategoryCode("CAT-001");
        category.setCategoryName("设备");
        category.setEnabled("Y");
        when(amsAssetCategoryService.selectAmsAssetCategoryByCategoryId(1L)).thenReturn(category);
        SysDictData status = new SysDictData();
        status.setDictValue(AssetStatus.IN_STOCK);
        when(sysDictDataService.selectDictDataList(any(SysDictData.class))).thenReturn(List.of(status));

        service.updateAmsAsset(incoming);

        ArgumentCaptor<AmsAsset> captor = ArgumentCaptor.forClass(AmsAsset.class);
        verify(amsAssetMapper).updateAmsAsset(captor.capture());
        assertEquals("历史仓库", captor.getValue().getWarehouseName());
        assertEquals("历史位置", captor.getValue().getLocationName());
        assertEquals("历史部门", captor.getValue().getUseDeptName());
        assertEquals("历史使用人", captor.getValue().getUseUserName());
        assertEquals(9L, captor.getValue().getTagId());
        assertEquals("TAG-001", captor.getValue().getTagCode());
        assertEquals("EPC-001", captor.getValue().getEpcCode());
        verify(amsWarehouseService, never()).selectAmsWarehouseByWarehouseId(any());
        verify(amsAssetLocationService, never()).selectAmsAssetLocationByLocationId(any());
        verify(sysDeptService, never()).selectDeptList(any());
        verify(sysUserService, never()).selectUserById(any());
    }

    /** 删除资产应先锁定记录，避免与RFID绑定并发后产生单边关系 */
    @Test
    void deleteAmsAssetShouldLockAndRejectBoundAsset()
    {
        AmsAsset asset = buildAsset();
        asset.setTagId(9L);
        asset.setTagCode("TAG-001");
        asset.setEpcCode("EPC-001");
        when(amsAssetMapper.selectAmsAssetByAssetIdForUpdate(1L)).thenReturn(asset);

        ServiceException exception = assertThrows(ServiceException.class,
                () -> service.deleteAmsAssetByAssetId(1L));

        assertTrue(exception.getMessage().contains("已绑定"));
        verify(amsAssetMapper, never()).deleteAmsAssetByAssetId(any());
    }

    /** 未使用资产锁定校验后允许删除 */
    @Test
    void deleteAmsAssetShouldDeleteLockedUnusedAsset()
    {
        AmsAsset asset = buildAsset();
        when(amsAssetMapper.selectAmsAssetByAssetIdForUpdate(1L)).thenReturn(asset);
        when(amsAssetMapper.countAssetReferences(1L)).thenReturn(0);
        when(amsAssetMapper.deleteAmsAssetByAssetId(1L)).thenReturn(1);

        assertEquals(1, service.deleteAmsAssetByAssetId(1L));

        verify(amsAssetMapper).selectAmsAssetByAssetIdForUpdate(1L);
    }

    /** 构建常用主数据的 Mock 返回值（类别、仓库、位置、状态字典） */
    private void stubMasterData()
    {
        AmsAssetCategory category = new AmsAssetCategory();
        category.setCategoryId(1L);
        category.setCategoryCode("CAT-001");
        category.setCategoryName("设备");
        category.setEnabled("Y");
        AmsWarehouse warehouse = new AmsWarehouse();
        warehouse.setWarehouseId(1L);
        warehouse.setWarehouseCode("WH-001");
        warehouse.setWarehouseName("一号仓");
        warehouse.setEnabled("Y");
        AmsAssetLocation location = new AmsAssetLocation();
        location.setLocationId(10L);
        location.setLocationCode("LOC-001");
        location.setLocationName("A区");
        location.setWarehouseId(1L);
        location.setEnabled("Y");
        SysDictData status = new SysDictData();
        status.setDictValue(AssetStatus.IN_STOCK);
        when(amsAssetCategoryService.selectAmsAssetCategoryByCategoryId(1L)).thenReturn(category);
        when(amsWarehouseService.selectAmsWarehouseByWarehouseId(1L)).thenReturn(warehouse);
        when(amsAssetLocationService.selectAmsAssetLocationByLocationId(10L)).thenReturn(location);
        when(sysDictDataService.selectDictDataList(any(SysDictData.class))).thenReturn(List.of(status));
    }

    /** 构建基础的测试资产对象，默认在库状态 */
    private AmsAsset buildAsset()
    {
        AmsAsset asset = new AmsAsset();
        asset.setAssetId(1L);
        asset.setAssetCode("ASSET-001");
        asset.setAssetName("测试资产");
        asset.setCategoryId(1L);
        asset.setWarehouseId(1L);
        asset.setLocationId(10L);
        asset.setAssetStatus(AssetStatus.IN_STOCK);
        return asset;
    }
}
