package com.ruoyi.web.modules.cart.service;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.web.modules.account.facade.IMemberFacade;
import com.ruoyi.web.modules.cart.constant.CartConstants;
import com.ruoyi.web.modules.cart.domain.BizMemberCartItem;
import com.ruoyi.web.modules.cart.dto.CartCheckoutPrepareDTO;
import com.ruoyi.web.modules.cart.dto.CartItemAddDTO;
import com.ruoyi.web.modules.cart.dto.CartItemQuantityDTO;
import com.ruoyi.web.modules.cart.dto.CartItemRowDTO;
import com.ruoyi.web.modules.cart.mapper.CartAppMapper;
import com.ruoyi.web.modules.cart.service.impl.CartAppServiceImpl;
import com.ruoyi.web.modules.cart.vo.CartCheckoutPrepareVO;
import com.ruoyi.web.modules.cart.vo.CartCleanInvalidResultVO;
import com.ruoyi.web.modules.cart.vo.CartItemAddResultVO;
import com.ruoyi.web.modules.cart.vo.CartListVO;
import com.ruoyi.web.modules.category.facade.ICategoryFacade;
import com.ruoyi.web.modules.goods.constant.GoodsConstants;
import com.ruoyi.web.modules.goods.domain.BizGoods;
import com.ruoyi.web.modules.goods.facade.IGoodsPurchaseFacade;
import com.ruoyi.web.modules.goods.mapper.BizGoodsAttrMapper;
import com.ruoyi.web.modules.goods.mapper.BizGoodsMapper;
import com.ruoyi.web.modules.goods.vo.GoodsPurchaseVO;
import com.ruoyi.web.modules.store.constant.ShopConstants;

/**
 * C 端购物车 Service 单元测试
 */
@ExtendWith(MockitoExtension.class)
class CartAppServiceImplTest
{
    private static final Long MEMBER_ID = 200L;

    @Mock
    private CartAppMapper cartAppMapper;

    @Mock
    private BizGoodsMapper goodsMapper;

    @Mock
    private BizGoodsAttrMapper goodsAttrMapper;

    @Mock
    private IGoodsPurchaseFacade purchaseFacade;

    @Mock
    private ICategoryFacade categoryFacade;

    @Mock
    private IMemberFacade memberFacade;

    @InjectMocks
    private CartAppServiceImpl cartAppService;

    @Test
    void list_emptyCart()
    {
        when(memberFacade.isMemberEnabled(MEMBER_ID)).thenReturn(true);
        when(cartAppMapper.selectItemRowsByMemberId(MEMBER_ID)).thenReturn(Collections.emptyList());

        CartListVO vo = cartAppService.list(MEMBER_ID);

        assertEquals(0, vo.getItems().size());
        assertEquals(0, vo.getCheckedSummary().getCheckedCount());
    }

    @Test
    void list_returnsFlatItemsWithShop()
    {
        when(memberFacade.isMemberEnabled(MEMBER_ID)).thenReturn(true);
        when(cartAppMapper.selectItemRowsByMemberId(MEMBER_ID)).thenReturn(Arrays.asList(cartRow(2L, 102L),
                cartRow(1L, 101L)));
        when(categoryFacade.isCategoryVisible(10L)).thenReturn(true);

        CartListVO vo = cartAppService.list(MEMBER_ID);

        assertEquals(2, vo.getItems().size());
        assertEquals(2L, vo.getItems().get(0).getCartItemId().longValue());
        assertEquals(102L, vo.getItems().get(0).getShopId().longValue());
        assertEquals("测试店", vo.getItems().get(0).getShopName());
        assertEquals(2, vo.getCheckedSummary().getCheckedCount());
    }

    @Test
    void addItem_insertsNewRow()
    {
        when(memberFacade.isMemberEnabled(MEMBER_ID)).thenReturn(true);
        GoodsPurchaseVO purchase = new GoodsPurchaseVO();
        purchase.setAllowed(true);
        when(purchaseFacade.canPurchase(1L)).thenReturn(purchase);
        BizGoods goods = goods(1L, 101L, 10);
        when(goodsMapper.selectById(1L)).thenReturn(goods);
        when(cartAppMapper.selectByMemberGoodsSpec(MEMBER_ID, 1L, "")).thenReturn(null);
        when(goodsAttrMapper.selectByGoodsId(1L)).thenReturn(Collections.emptyList());
        when(cartAppMapper.insert(any(BizMemberCartItem.class))).thenAnswer(invocation -> {
            BizMemberCartItem item = invocation.getArgument(0);
            item.setCartItemId(99L);
            return 1;
        });

        CartItemAddDTO dto = new CartItemAddDTO();
        dto.setGoodsId(1L);
        dto.setQuantity(2);
        CartItemAddResultVO result = cartAppService.addItem(MEMBER_ID, dto);

        assertEquals(99L, result.getCartItemId());
        assertEquals(2, result.getQuantity());
        verify(cartAppMapper).insert(any(BizMemberCartItem.class));
    }

    @Test
    void addItem_mergeBlocksWhenExceedStock()
    {
        when(memberFacade.isMemberEnabled(MEMBER_ID)).thenReturn(true);
        GoodsPurchaseVO purchase = new GoodsPurchaseVO();
        purchase.setAllowed(true);
        when(purchaseFacade.canPurchase(1L)).thenReturn(purchase);
        when(goodsMapper.selectById(1L)).thenReturn(goods(1L, 101L, 5));
        BizMemberCartItem existing = new BizMemberCartItem();
        existing.setCartItemId(8L);
        existing.setQuantity(4);
        when(cartAppMapper.selectByMemberGoodsSpec(MEMBER_ID, 1L, "")).thenReturn(existing);

        CartItemAddDTO dto = new CartItemAddDTO();
        dto.setGoodsId(1L);
        dto.setQuantity(2);

        ServiceException ex = assertThrows(ServiceException.class, () -> cartAppService.addItem(MEMBER_ID, dto));
        assertEquals(CartConstants.MSG_STOCK_INSUFFICIENT, ex.getMessage());
        verify(cartAppMapper, never()).updateQuantity(any(), any(), any());
    }

    @Test
    void prepareCheckout_crossShopRejected()
    {
        when(memberFacade.isMemberEnabled(MEMBER_ID)).thenReturn(true);
        CartItemRowDTO row1 = cartRow(1L, 101L);
        CartItemRowDTO row2 = cartRow(2L, 102L);
        when(cartAppMapper.selectItemRowsByIdsAndMember(eq(MEMBER_ID), any()))
                .thenReturn(Arrays.asList(row1, row2));

        CartCheckoutPrepareDTO dto = new CartCheckoutPrepareDTO();
        dto.setCartItemIds(Arrays.asList(1L, 2L));

        ServiceException ex = assertThrows(ServiceException.class,
                () -> cartAppService.prepareCheckout(MEMBER_ID, dto));
        assertEquals(CartConstants.MSG_CROSS_SHOP, ex.getMessage());
    }

    @Test
    void prepareCheckout_success()
    {
        when(memberFacade.isMemberEnabled(MEMBER_ID)).thenReturn(true);
        CartItemRowDTO row1 = cartRow(1L, 101L);
        CartItemRowDTO row2 = cartRow(2L, 101L);
        row2.setCartItemId(2L);
        row2.setQuantity(1);
        when(cartAppMapper.selectItemRowsByIdsAndMember(eq(MEMBER_ID), any()))
                .thenReturn(Arrays.asList(row1, row2));
        when(categoryFacade.isCategoryVisible(10L)).thenReturn(true);
        GoodsPurchaseVO purchase = new GoodsPurchaseVO();
        purchase.setAllowed(true);
        when(purchaseFacade.canPurchase(any())).thenReturn(purchase);

        CartCheckoutPrepareDTO dto = new CartCheckoutPrepareDTO();
        dto.setCartItemIds(Arrays.asList(1L, 2L));

        CartCheckoutPrepareVO vo = cartAppService.prepareCheckout(MEMBER_ID, dto);

        assertEquals(101L, vo.getShopId());
        assertEquals(2, vo.getItems().size());
        assertEquals(new BigDecimal("30.00"), vo.getGoodsAmount());
    }

    @Test
    void cleanInvalid_removesInvalidRows()
    {
        when(memberFacade.isMemberEnabled(MEMBER_ID)).thenReturn(true);
        CartItemRowDTO valid = cartRow(1L, 101L);
        CartItemRowDTO invalid = cartRow(2L, 101L);
        invalid.setGoodsStatus(GoodsConstants.STATUS_OFF_SHELF);
        when(cartAppMapper.selectItemRowsByMemberId(MEMBER_ID)).thenReturn(Arrays.asList(valid, invalid));
        when(categoryFacade.isCategoryVisible(10L)).thenReturn(true);

        CartCleanInvalidResultVO result = cartAppService.cleanInvalid(MEMBER_ID);

        assertEquals(1, result.getRemovedCount());
        verify(cartAppMapper).deleteByIdsAndMember(eq(MEMBER_ID), eq(Collections.singletonList(2L)));
    }

    @Test
    void updateQuantity_exceedsStock()
    {
        when(memberFacade.isMemberEnabled(MEMBER_ID)).thenReturn(true);
        BizMemberCartItem item = new BizMemberCartItem();
        item.setCartItemId(1L);
        item.setGoodsId(1L);
        when(cartAppMapper.selectByIdAndMember(1L, MEMBER_ID)).thenReturn(item);
        when(goodsMapper.selectById(1L)).thenReturn(goods(1L, 101L, 3));

        CartItemQuantityDTO dto = new CartItemQuantityDTO();
        dto.setQuantity(5);

        ServiceException ex = assertThrows(ServiceException.class,
                () -> cartAppService.updateQuantity(MEMBER_ID, 1L, dto));
        assertEquals(CartConstants.MSG_STOCK_INSUFFICIENT, ex.getMessage());
    }

    private static BizGoods goods(Long goodsId, Long shopId, int stock)
    {
        BizGoods goods = new BizGoods();
        goods.setGoodsId(goodsId);
        goods.setShopId(shopId);
        goods.setStock(stock);
        goods.setSalePrice(new BigDecimal("10.00"));
        goods.setGoodsStatus(GoodsConstants.STATUS_ON_SALE);
        return goods;
    }

    private static CartItemRowDTO cartRow(Long cartItemId, Long shopId)
    {
        CartItemRowDTO row = new CartItemRowDTO();
        row.setCartItemId(cartItemId);
        row.setShopId(shopId);
        row.setGoodsId(1L);
        row.setGoodsName("商品A");
        row.setMainPic("/pic.jpg");
        row.setSpecText("默认");
        row.setSalePrice(new BigDecimal("10.00"));
        row.setQuantity(2);
        row.setChecked(CartConstants.CHECKED_YES);
        row.setUpdateTime(new Date());
        row.setGoodsStatus(GoodsConstants.STATUS_ON_SALE);
        row.setGoodsDelFlag(GoodsConstants.DEL_NORMAL);
        row.setShopStatus(ShopConstants.STATUS_OPEN);
        row.setShopDelFlag(ShopConstants.DEL_NORMAL);
        row.setCategoryId(10L);
        row.setStock(10);
        row.setShopName("测试店");
        return row;
    }
}
