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

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
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.List;
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.category.constant.CategoryConstants;
import com.ruoyi.web.modules.category.domain.BizGoodsCategory;
import com.ruoyi.web.modules.category.facade.ICategoryFacade;
import com.ruoyi.web.modules.category.mapper.BizGoodsCategoryMapper;
import com.ruoyi.web.modules.category.vo.CategoryVisibleChildVO;
import com.ruoyi.web.modules.category.vo.CategoryVisibleVO;
import com.ruoyi.web.modules.home.constant.CategoryAppConstants;
import com.ruoyi.web.modules.home.constant.ShopAppConstants;
import com.ruoyi.web.modules.home.dto.ShopGoodsQuery;
import com.ruoyi.web.modules.home.dto.ShopProfileRowDTO;
import com.ruoyi.web.modules.home.mapper.ShopAppMapper;
import com.ruoyi.web.modules.home.service.impl.ShopAppServiceImpl;
import com.ruoyi.web.modules.home.vo.HomeHotGoodsVO;
import com.ruoyi.web.modules.home.vo.ShopProfileAppVO;

/**
 * C 端店铺主页 Service 单元测试
 */
@ExtendWith(MockitoExtension.class)
class ShopAppServiceImplTest
{
    private static final Long SHOP_ID = 101L;

    private static final Long MEMBER_ID = 200L;

    @Mock
    private ShopAppMapper shopAppMapper;

    @Mock
    private ICategoryFacade categoryFacade;

    @Mock
    private BizGoodsCategoryMapper categoryMapper;

    @Mock
    private IMemberFacade memberFacade;

    @InjectMocks
    private ShopAppServiceImpl shopAppService;

    @Test
    void getProfile_returnsShopInfo()
    {
        when(shopAppMapper.selectProfileByShopId(SHOP_ID)).thenReturn(profileRow());

        ShopProfileAppVO vo = shopAppService.getProfile(SHOP_ID, null);

        assertEquals(SHOP_ID, vo.getShopId());
        assertEquals("测试店", vo.getShopName());
        assertEquals(5, vo.getFansCount());
        assertFalse(vo.getFollowed());
    }

    @Test
    void getProfile_followedWhenMemberHasRelation()
    {
        when(shopAppMapper.selectProfileByShopId(SHOP_ID)).thenReturn(profileRow());
        when(shopAppMapper.countFollow(MEMBER_ID, SHOP_ID)).thenReturn(1);

        ShopProfileAppVO vo = shopAppService.getProfile(SHOP_ID, MEMBER_ID);

        assertTrue(vo.getFollowed());
    }

    @Test
    void getProfile_shopNotFound()
    {
        when(shopAppMapper.selectProfileByShopId(SHOP_ID)).thenReturn(null);

        ServiceException ex = assertThrows(ServiceException.class,
                () -> shopAppService.getProfile(SHOP_ID, null));

        assertEquals(ShopAppConstants.MSG_SHOP_NOT_FOUND, ex.getMessage());
    }

    @Test
    void listCategories_delegatesFacade()
    {
        when(shopAppMapper.selectProfileByShopId(SHOP_ID)).thenReturn(profileRow());
        CategoryVisibleVO l1 = new CategoryVisibleVO();
        l1.setCategoryId(1L);
        l1.setCategoryName("农资");
        when(categoryFacade.listVisibleByShopId(SHOP_ID)).thenReturn(Collections.singletonList(l1));

        List<CategoryVisibleVO> result = shopAppService.listCategories(SHOP_ID);

        assertEquals(1, result.size());
        assertEquals("农资", result.get(0).getCategoryName());
    }

    @Test
    void listLevel2Tabs_returnsVisibleChildren()
    {
        when(shopAppMapper.selectProfileByShopId(SHOP_ID)).thenReturn(profileRow());
        when(categoryMapper.selectByIdIgnoreShop(1L)).thenReturn(shopLevel1(1L));
        CategoryVisibleChildVO tab = new CategoryVisibleChildVO();
        tab.setCategoryId(11L);
        tab.setCategoryName("肥料");
        when(shopAppMapper.selectShopLevel2Tabs(SHOP_ID, 1L)).thenReturn(Collections.singletonList(tab));

        List<CategoryVisibleChildVO> result = shopAppService.listLevel2Tabs(SHOP_ID, 1L);

        assertEquals(1, result.size());
        assertEquals("肥料", result.get(0).getCategoryName());
    }

    @Test
    void listLevel2Tabs_invalidLevel1Rejected()
    {
        when(shopAppMapper.selectProfileByShopId(SHOP_ID)).thenReturn(profileRow());
        when(categoryMapper.selectByIdIgnoreShop(99L)).thenReturn(null);

        ServiceException ex = assertThrows(ServiceException.class,
                () -> shopAppService.listLevel2Tabs(SHOP_ID, 99L));

        assertEquals(CategoryAppConstants.MSG_CATEGORY_INVISIBLE, ex.getMessage());
    }

    @Test
    void listGoods_allShopGoods()
    {
        when(shopAppMapper.selectProfileByShopId(SHOP_ID)).thenReturn(profileRow());
        when(shopAppMapper.selectShopGoods(SHOP_ID, null, null))
                .thenReturn(Collections.singletonList(goods(1L)));

        List<HomeHotGoodsVO> result = shopAppService.listGoods(SHOP_ID, new ShopGoodsQuery());

        assertEquals(1, result.size());
        verify(shopAppMapper).selectShopGoods(SHOP_ID, null, null);
    }

    @Test
    void listGoods_byShopCategory()
    {
        when(shopAppMapper.selectProfileByShopId(SHOP_ID)).thenReturn(profileRow());
        when(categoryMapper.selectByIdIgnoreShop(11L)).thenReturn(shopLevel2(11L, 1L));
        when(categoryFacade.isCategoryVisible(11L)).thenReturn(true);
        when(shopAppMapper.selectShopGoods(SHOP_ID, 11L, null))
                .thenReturn(Collections.singletonList(goods(1L)));

        ShopGoodsQuery query = new ShopGoodsQuery();
        query.setShopCategoryId(11L);
        List<HomeHotGoodsVO> result = shopAppService.listGoods(SHOP_ID, query);

        assertEquals(1, result.size());
    }

    @Test
    void listGoods_byKeywordIgnoresCategory()
    {
        when(shopAppMapper.selectProfileByShopId(SHOP_ID)).thenReturn(profileRow());
        when(shopAppMapper.selectShopGoods(SHOP_ID, null, "化肥"))
                .thenReturn(Collections.singletonList(goods(2L)));

        ShopGoodsQuery query = new ShopGoodsQuery();
        query.setShopCategoryId(11L);
        query.setKeyword("  化肥  ");
        List<HomeHotGoodsVO> result = shopAppService.listGoods(SHOP_ID, query);

        assertEquals(1, result.size());
        verify(shopAppMapper).selectShopGoods(SHOP_ID, null, "化肥");
    }

    @Test
    void listGoods_blankKeywordRejected()
    {
        when(shopAppMapper.selectProfileByShopId(SHOP_ID)).thenReturn(profileRow());
        ShopGoodsQuery query = new ShopGoodsQuery();
        query.setKeyword("   ");

        ServiceException ex = assertThrows(ServiceException.class,
                () -> shopAppService.listGoods(SHOP_ID, query));

        assertEquals(ShopAppConstants.MSG_KEYWORD_REQUIRED, ex.getMessage());
    }

    @Test
    void listGoods_invisibleCategoryRejected()
    {
        when(shopAppMapper.selectProfileByShopId(SHOP_ID)).thenReturn(profileRow());
        when(categoryMapper.selectByIdIgnoreShop(11L)).thenReturn(shopLevel2(11L, 1L));
        when(categoryFacade.isCategoryVisible(11L)).thenReturn(false);
        ShopGoodsQuery query = new ShopGoodsQuery();
        query.setShopCategoryId(11L);

        ServiceException ex = assertThrows(ServiceException.class,
                () -> shopAppService.listGoods(SHOP_ID, query));

        assertEquals(CategoryAppConstants.MSG_CATEGORY_INVISIBLE, ex.getMessage());
    }

    @Test
    void follow_insertsRelationAndIncrementsFans()
    {
        when(shopAppMapper.selectProfileByShopId(SHOP_ID)).thenReturn(profileRow());
        when(memberFacade.isMemberEnabled(MEMBER_ID)).thenReturn(true);
        when(shopAppMapper.countFollow(MEMBER_ID, SHOP_ID)).thenReturn(0);
        when(shopAppMapper.incrementFansCount(SHOP_ID)).thenReturn(1);

        shopAppService.follow(SHOP_ID, MEMBER_ID);

        verify(shopAppMapper).insertFollow(MEMBER_ID, SHOP_ID);
        verify(shopAppMapper).incrementFansCount(SHOP_ID);
        verify(shopAppMapper, never()).insertShopStats(SHOP_ID);
    }

    @Test
    void follow_createsStatsWhenMissing()
    {
        when(shopAppMapper.selectProfileByShopId(SHOP_ID)).thenReturn(profileRow());
        when(memberFacade.isMemberEnabled(MEMBER_ID)).thenReturn(true);
        when(shopAppMapper.countFollow(MEMBER_ID, SHOP_ID)).thenReturn(0);
        when(shopAppMapper.incrementFansCount(SHOP_ID)).thenReturn(0);

        shopAppService.follow(SHOP_ID, MEMBER_ID);

        verify(shopAppMapper).insertShopStats(SHOP_ID);
    }

    @Test
    void follow_idempotentWhenAlreadyFollowed()
    {
        when(shopAppMapper.selectProfileByShopId(SHOP_ID)).thenReturn(profileRow());
        when(memberFacade.isMemberEnabled(MEMBER_ID)).thenReturn(true);
        when(shopAppMapper.countFollow(MEMBER_ID, SHOP_ID)).thenReturn(1);

        shopAppService.follow(SHOP_ID, MEMBER_ID);

        verify(shopAppMapper, never()).insertFollow(MEMBER_ID, SHOP_ID);
    }

    @Test
    void follow_disabledMemberRejected()
    {
        when(shopAppMapper.selectProfileByShopId(SHOP_ID)).thenReturn(profileRow());
        when(memberFacade.isMemberEnabled(MEMBER_ID)).thenReturn(false);

        ServiceException ex = assertThrows(ServiceException.class,
                () -> shopAppService.follow(SHOP_ID, MEMBER_ID));

        assertEquals(ShopAppConstants.MSG_MEMBER_DISABLED, ex.getMessage());
    }

    @Test
    void unfollow_decrementsFans()
    {
        when(shopAppMapper.selectProfileByShopId(SHOP_ID)).thenReturn(profileRow());
        when(shopAppMapper.deleteFollow(MEMBER_ID, SHOP_ID)).thenReturn(1);

        shopAppService.unfollow(SHOP_ID, MEMBER_ID);

        verify(shopAppMapper).decrementFansCount(SHOP_ID);
    }

    @Test
    void unfollow_idempotentWhenNotFollowed()
    {
        when(shopAppMapper.selectProfileByShopId(SHOP_ID)).thenReturn(profileRow());
        when(shopAppMapper.deleteFollow(MEMBER_ID, SHOP_ID)).thenReturn(0);

        shopAppService.unfollow(SHOP_ID, MEMBER_ID);

        verify(shopAppMapper, never()).decrementFansCount(SHOP_ID);
    }

    private static ShopProfileRowDTO profileRow()
    {
        ShopProfileRowDTO row = new ShopProfileRowDTO();
        row.setShopId(SHOP_ID);
        row.setShopName("测试店");
        row.setShopAvatar("/avatar.jpg");
        row.setShopStatus("0");
        row.setFansCount(5);
        return row;
    }

    private static BizGoodsCategory shopLevel1(Long id)
    {
        BizGoodsCategory c = new BizGoodsCategory();
        c.setCategoryId(id);
        c.setShopId(SHOP_ID);
        c.setCategoryLevel(CategoryConstants.LEVEL_ONE);
        c.setShowFlag(CategoryConstants.SHOW_YES);
        c.setDelFlag(CategoryConstants.DEL_FLAG_NORMAL);
        return c;
    }

    private static BizGoodsCategory shopLevel2(Long id, Long parentId)
    {
        BizGoodsCategory c = new BizGoodsCategory();
        c.setCategoryId(id);
        c.setShopId(SHOP_ID);
        c.setParentId(parentId);
        c.setCategoryLevel(CategoryConstants.LEVEL_TWO);
        c.setShowFlag(CategoryConstants.SHOW_YES);
        c.setDelFlag(CategoryConstants.DEL_FLAG_NORMAL);
        return c;
    }

    private static HomeHotGoodsVO goods(Long goodsId)
    {
        HomeHotGoodsVO vo = new HomeHotGoodsVO();
        vo.setGoodsId(goodsId);
        vo.setGoodsSn("G00" + goodsId);
        vo.setGoodsName("商品" + goodsId);
        vo.setMainPic("/pic.jpg");
        vo.setSalePrice(new BigDecimal("10.00"));
        vo.setShopId(SHOP_ID);
        vo.setShopName("测试店");
        return vo;
    }
}
