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.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

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;

import com.ruoyi.asset.constant.DisposalOrderStatus;
import com.ruoyi.asset.domain.AmsAsset;
import com.ruoyi.asset.domain.AmsDisposalOperateContext;
import com.ruoyi.asset.domain.AmsDisposalOrder;
import com.ruoyi.asset.domain.AmsDisposalOrderItem;
import com.ruoyi.asset.domain.AssetTransitionContext;
import com.ruoyi.asset.mapper.AmsAssetMapper;
import com.ruoyi.asset.mapper.AmsDisposalOrderMapper;
import com.ruoyi.asset.service.IAssetStatusTransitionService;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.system.service.ISysCodeRuleService;

/**
 * 报废处置管理服务层单元测试类
 *
 * @author Yangk
 * @date 2026-06-17
 */
@ExtendWith(MockitoExtension.class)
class AmsDisposalOrderServiceImplTest
{
    @Mock
    private AmsDisposalOrderMapper amsDisposalOrderMapper;

    @Mock
    private AmsAssetMapper amsAssetMapper;

    @Mock
    private ISysCodeRuleService sysCodeRuleService;

    @Mock
    private IAssetStatusTransitionService assetStatusTransitionService;

    @InjectMocks
    private AmsDisposalOrderServiceImpl service;

    /**
     * 测试：新增报废单草稿成功
     */
    @Test
    void insertShouldGenerateCodeAndFillSnapshots()
    {
        AmsDisposalOrder order = buildRequest();
        order.setApplicantId(999L);
        order.setApplicantName("前端篡改申请人");
        order.setApplyDeptId(888L);
        order.setApplyDeptName("前端篡改部门");
        stubAsset(1L, "IN_STOCK");

        when(sysCodeRuleService.nextCode("DISPOSAL_ORDER")).thenReturn("BF202606170001");
        when(amsDisposalOrderMapper.countOtherActiveDisposalOrderByAssetId(eq(1L), eq(null))).thenReturn(0);

        doAnswer(invocation -> {
            AmsDisposalOrder inserted = invocation.getArgument(0);
            inserted.setOrderId(100L);
            return 1;
        }).when(amsDisposalOrderMapper).insertAmsDisposalOrder(any(AmsDisposalOrder.class));

        assertEquals(1, service.insertAmsDisposalOrder(order, adminContext()));

        assertEquals("BF202606170001", order.getDisposalNo());
        assertEquals(DisposalOrderStatus.DRAFT, order.getOrderStatus());
        assertEquals(9L, order.getApplicantId());
        assertEquals("管理员", order.getApplicantName());
        assertEquals(3L, order.getApplyDeptId());
        assertEquals("资产管理部", order.getApplyDeptName());
        assertEquals("admin", order.getCreateBy());
        assertNotNull(order.getCreateTime());
        assertNull(order.getConfirmUserId());

        // 校验明细快照被回填
        AmsDisposalOrderItem item = order.getAmsDisposalOrderItemList().get(0);
        assertEquals("A001", item.getAssetCode());
        assertEquals("资产A", item.getAssetName());
        assertEquals("CAT-A", item.getCategoryCode());
        assertEquals("品牌A", item.getBrand());
    }

    /**
     * 测试：新增草稿时资产已被其他报废单占用，应拦截
     */
    @Test
    void insertShouldRejectIfAssetOccupied()
    {
        AmsDisposalOrder order = buildRequest();
        stubAsset(1L, "IN_STOCK");

        when(amsDisposalOrderMapper.countOtherActiveDisposalOrderByAssetId(eq(1L), eq(null))).thenReturn(1);

        assertThrows(ServiceException.class, () -> service.insertAmsDisposalOrder(order, adminContext()));
    }

    /**
     * 测试：新增草稿时资产状态不符合要求，应拦截
     */
    @Test
    void insertShouldRejectIfAssetStatusInvalid()
    {
        AmsDisposalOrder order = buildRequest();
        stubAsset(1L, "DISPOSED");

        assertThrows(ServiceException.class, () -> service.insertAmsDisposalOrder(order, adminContext()));
    }

    /**
     * 测试：修改草稿成功
     */
    @Test
    void updateShouldSuccessWhenStatusIsDraft()
    {
        AmsDisposalOrder current = buildRequest();
        current.setOrderId(100L);
        current.setDisposalNo("BF202606170001");
        current.setOrderStatus(DisposalOrderStatus.DRAFT);
        current.setApplicantId(9L);
        current.setApplicantName("管理员");
        current.setApplyDeptId(3L);
        current.setApplyDeptName("资产管理部");
        current.setCreateBy("admin");
        current.setCreateTime(new Date());

        when(amsDisposalOrderMapper.selectAmsDisposalOrderByOrderIdForUpdate(eq(100L))).thenReturn(current);
        stubAsset(1L, "IN_STOCK");
        when(amsDisposalOrderMapper.countOtherActiveDisposalOrderByAssetId(eq(1L), eq(100L))).thenReturn(0);
        when(amsDisposalOrderMapper.updateAmsDisposalOrder(any(AmsDisposalOrder.class))).thenReturn(1);

        AmsDisposalOrder updateReq = buildRequest();
        updateReq.setOrderId(100L);
        updateReq.setDisposalMethod("新处置方式");
        updateReq.setApplicantId(999L);
        updateReq.setApplicantName("前端篡改申请人");

        assertEquals(1, service.updateAmsDisposalOrder(updateReq, adminContext()));
        assertEquals(9L, updateReq.getApplicantId());
        assertEquals("管理员", updateReq.getApplicantName());
        assertEquals("admin", updateReq.getUpdateBy());
        assertEquals(DisposalOrderStatus.DRAFT, updateReq.getParams().get("expectedOrderStatus"));
        verify(amsDisposalOrderMapper).deleteAmsDisposalOrderItemByOrderId(eq(100L));
    }

    /**
     * 测试：提交报废申请
     */
    @Test
    void submitShouldChangeStatusToPendingConfirm()
    {
        AmsDisposalOrder current = buildRequest();
        current.setOrderId(100L);
        current.setDisposalNo("BF202606170001");
        current.setOrderStatus(DisposalOrderStatus.DRAFT);

        when(amsDisposalOrderMapper.selectAmsDisposalOrderByOrderIdForUpdate(eq(100L))).thenReturn(current);
        stubAsset(1L, "IN_STOCK");
        when(amsDisposalOrderMapper.countOtherActiveDisposalOrderByAssetId(eq(1L), eq(100L))).thenReturn(0);
        when(amsDisposalOrderMapper.updateAmsDisposalOrder(any(AmsDisposalOrder.class))).thenReturn(1);

        assertEquals(1, service.submitDisposal(100L, adminContext()));
        assertEquals(DisposalOrderStatus.PENDING_CONFIRM, current.getOrderStatus());
        assertEquals(DisposalOrderStatus.DRAFT, current.getParams().get("expectedOrderStatus"));
    }

    /**
     * 测试：确认报废成功，扭转资产状态并记录履历
     */
    @Test
    void confirmShouldChangeStatusToDisposedDoneAndCallTransition()
    {
        AmsDisposalOrder current = buildRequest();
        current.setOrderId(100L);
        current.setDisposalNo("BF202606170001");
        current.setOrderStatus(DisposalOrderStatus.PENDING_CONFIRM);

        AmsDisposalOrderItem item = current.getAmsDisposalOrderItemList().get(0);
        item.setItemId(500L);

        when(amsDisposalOrderMapper.selectAmsDisposalOrderByOrderIdForUpdate(eq(100L))).thenReturn(current);
        stubAsset(1L, "IN_STOCK");
        when(amsDisposalOrderMapper.countOtherActiveDisposalOrderByAssetId(eq(1L), eq(100L))).thenReturn(0);
        when(amsDisposalOrderMapper.updateAmsDisposalOrder(any(AmsDisposalOrder.class))).thenReturn(1);

        assertEquals(1, service.confirmDisposal(100L, adminContext()));

        assertEquals(DisposalOrderStatus.DISPOSED_DONE, current.getOrderStatus());
        assertEquals(DisposalOrderStatus.PENDING_CONFIRM, current.getParams().get("expectedOrderStatus"));
        assertNotNull(current.getConfirmTime());
        assertNotNull(current.getDisposalTime());

        ArgumentCaptor<AssetTransitionContext> contextCaptor = ArgumentCaptor.forClass(AssetTransitionContext.class);
        verify(assetStatusTransitionService).confirmDisposal(eq(1L), contextCaptor.capture());

        AssetTransitionContext transitionContext = contextCaptor.getValue();
        assertEquals(100L, transitionContext.getSourceOrderId());
        assertEquals("BF202606170001", transitionContext.getSourceOrderNo());
        assertEquals(500L, transitionContext.getSourceItemId());
        assertEquals(9L, transitionContext.getOperateUserId());
        assertEquals("管理员", transitionContext.getOperateUserName());
    }

    /**
     * 测试：驳回单据成功
     */
    @Test
    void rejectShouldChangeStatusToRejected()
    {
        AmsDisposalOrder current = buildRequest();
        current.setOrderId(100L);
        current.setDisposalNo("BF202606170001");
        current.setOrderStatus(DisposalOrderStatus.PENDING_CONFIRM);

        when(amsDisposalOrderMapper.selectAmsDisposalOrderByOrderIdForUpdate(eq(100L))).thenReturn(current);
        when(amsDisposalOrderMapper.updateAmsDisposalOrder(any(AmsDisposalOrder.class))).thenReturn(1);

        assertEquals(1, service.rejectDisposal(100L, "资料不全", adminContext()));
        assertEquals(DisposalOrderStatus.REJECTED, current.getOrderStatus());
        assertEquals(DisposalOrderStatus.PENDING_CONFIRM, current.getParams().get("expectedOrderStatus"));
        assertEquals("资料不全", current.getRejectReason());
    }

    // ========================================== 辅助构造方法 ==========================================

    private AmsDisposalOrder buildRequest()
    {
        AmsDisposalOrder order = new AmsDisposalOrder();
        order.setDisposalMethod("垃圾分类处置");
        order.setRemark("常规报废");

        AmsDisposalOrderItem item = new AmsDisposalOrderItem();
        item.setAssetId(1L);
        item.setDisposalReason("物理损坏");
        item.setDisposalRemark("不可修复");

        List<AmsDisposalOrderItem> items = new ArrayList<>();
        items.add(item);
        order.setAmsDisposalOrderItemList(items);

        return order;
    }

    private AmsDisposalOperateContext adminContext()
    {
        return new AmsDisposalOperateContext(9L, "管理员", "admin", 3L, "资产管理部");
    }

    private void stubAsset(Long assetId, String status)
    {
        AmsAsset asset = new AmsAsset();
        asset.setAssetId(assetId);
        asset.setAssetCode("A001");
        asset.setAssetName("资产A");
        asset.setCategoryId(2L);
        asset.setCategoryCode("CAT-A");
        asset.setCategoryName("类别A");
        asset.setSpecModel("型号A");
        asset.setBrand("品牌A");
        asset.setAssetStatus(status);
        asset.setDelFlag("0");

        when(amsAssetMapper.selectAmsAssetByAssetIdForUpdate(eq(assetId))).thenReturn(asset);
    }
}
