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

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.times;
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.concurrent.TimeUnit;
import org.junit.jupiter.api.BeforeEach;
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.core.redis.RedisCache;
import com.ruoyi.web.modules.openstats.constant.OpenStatsConstants;
import com.ruoyi.web.modules.openstats.dto.CategoryQtyRow;
import com.ruoyi.web.modules.openstats.dto.MonthCountRow;
import com.ruoyi.web.modules.openstats.dto.RegionAmountRow;
import com.ruoyi.web.modules.openstats.mapper.MallStatsOpenMapper;
import com.ruoyi.web.modules.openstats.service.impl.MallStatsOpenServiceImpl;
import com.ruoyi.web.modules.openstats.support.ReviewWordCloudSupport;
import com.ruoyi.web.modules.openstats.support.StatsCacheKeys;
import com.ruoyi.web.modules.openstats.vo.CategorySalesVO;
import com.ruoyi.web.modules.openstats.vo.HotCategoryRankVO;
import com.ruoyi.web.modules.openstats.vo.OrderTrendVO;
import com.ruoyi.web.modules.openstats.vo.RegionRankVO;
import com.ruoyi.web.modules.openstats.vo.ReviewWordCloudVO;
import com.ruoyi.web.modules.openstats.vo.ShopEntryVO;
import com.ruoyi.web.modules.openstats.vo.StatsOverviewVO;
import com.ruoyi.web.modules.openstats.vo.WordCloudItemVO;

@ExtendWith(MockitoExtension.class)
class MallStatsOpenServiceImplTest
{
    private static final String STAT_DATE = "2026-05-26";

    @Mock
    private MallStatsOpenMapper statsMapper;

    @Mock
    private RedisCache redisCache;

    @Mock
    private ReviewWordCloudSupport wordCloudSupport;

    @InjectMocks
    private MallStatsOpenServiceImpl service;

    @BeforeEach
    void stubCacheMiss()
    {
        when(redisCache.getCacheObject(anyString())).thenReturn(null);
    }

    @Test
    void categorySales_ratioSumNear100()
    {
        when(statsMapper.selectCategoryQtyByFinishDate(STAT_DATE)).thenReturn(Arrays.asList(
                row(1L, "兽药", 500L),
                row(2L, "饲料", 400L),
                row(3L, "农药", 300L)));

        CategorySalesVO vo = service.getCategorySales(STAT_DATE);

        assertEquals(STAT_DATE, vo.getStatDate());
        assertEquals(1200L, vo.getTotalQty());
        assertEquals(3, vo.getItems().size());
        double ratioSum = vo.getItems().stream().mapToDouble(i -> i.getRatio()).sum();
        assertEquals(100.0, ratioSum, 0.05);
        verify(redisCache).setCacheObject(eq(StatsCacheKeys.categorySales(STAT_DATE)), eq(vo),
                eq(OpenStatsConstants.CACHE_TTL_CATEGORY_MIN), eq(TimeUnit.MINUTES));
    }

    @Test
    void hotCategoryRank_onlyTop5()
    {
        when(statsMapper.selectCategoryQtyByFinishDate(STAT_DATE)).thenReturn(Arrays.asList(
                row(1L, "A", 100L),
                row(2L, "B", 90L),
                row(3L, "C", 80L),
                row(4L, "D", 70L),
                row(5L, "E", 60L),
                row(6L, "F", 50L)));

        HotCategoryRankVO vo = service.getHotCategoryRank(STAT_DATE);

        assertEquals(5, vo.getItems().size());
        assertEquals(1, vo.getItems().get(0).getRank());
        assertEquals("A", vo.getItems().get(0).getCategoryName());
        assertTrue(vo.getItems().stream().noneMatch(i -> "F".equals(i.getCategoryName())));
    }

    @Test
    void orderTrend_fillsMissingMonthsWithZero()
    {
        when(statsMapper.selectOrderCountByYear(2026)).thenReturn(Collections.singletonList(monthRow(3, 10L)));

        OrderTrendVO vo = service.getOrderTrend("2026");

        assertEquals(12, vo.getItems().size());
        assertEquals(0L, vo.getItems().get(0).getOrderCount());
        assertEquals(10L, vo.getItems().get(2).getOrderCount());
    }

    @Test
    void shopEntry_yearTotalZeroRatioNull()
    {
        when(statsMapper.selectShopCountByYear(2026)).thenReturn(Collections.emptyList());

        ShopEntryVO vo = service.getShopEntry("2026");

        assertEquals(0L, vo.getYearTotal());
        assertTrue(vo.getItems().stream().allMatch(i -> i.getRatio() == null));
    }

    @Test
    void regionRank_groupsByCity()
    {
        RegionAmountRow r1 = new RegionAmountRow();
        r1.setConsigneeAddress("广东省深圳市南山区 科技园");
        r1.setAmount(new BigDecimal("50000.00"));
        RegionAmountRow r2 = new RegionAmountRow();
        r2.setConsigneeAddress("广东省深圳市福田区 中心");
        r2.setAmount(new BigDecimal("30000.00"));
        when(statsMapper.selectRegionAmountByYear(2026)).thenReturn(Arrays.asList(r1, r2));

        RegionRankVO vo = service.getRegionRank("2026");

        assertEquals(1, vo.getItems().size());
        assertEquals("深圳市", vo.getItems().get(0).getCity());
        assertEquals(8.0, vo.getItems().get(0).getAmountWan(), 0.001);
    }

    @Test
    void reviewWordCloud_usesCacheOnSecondCall()
    {
        ReviewWordCloudVO cached = new ReviewWordCloudVO();
        WordCloudItemVO item = new WordCloudItemVO();
        item.setWord("质量好");
        item.setCount(10L);
        cached.getItems().add(item);
        when(redisCache.getCacheObject(StatsCacheKeys.wordCloud())).thenReturn(null, cached);
        when(statsMapper.selectReviewContentsForWordCloud()).thenReturn(Collections.singletonList("质量很好"));
        when(wordCloudSupport.buildTopWords(any())).thenReturn(cached);

        ReviewWordCloudVO first = service.getReviewWordCloud();
        ReviewWordCloudVO second = service.getReviewWordCloud();

        assertNotNull(first);
        assertEquals(cached, second);
        verify(statsMapper, times(1)).selectReviewContentsForWordCloud();
    }

    @Test
    void overview_containsAllSections()
    {
        when(statsMapper.selectCategoryQtyByFinishDate(anyString())).thenReturn(Collections.emptyList());
        when(statsMapper.selectOrderCountByYear(anyInt())).thenReturn(Collections.emptyList());
        when(statsMapper.selectShopCountByYear(anyInt())).thenReturn(Collections.emptyList());
        when(statsMapper.selectRegionAmountByYear(anyInt())).thenReturn(Collections.emptyList());
        when(statsMapper.selectReviewContentsForWordCloud()).thenReturn(Collections.emptyList());
        when(wordCloudSupport.buildTopWords(any())).thenReturn(new ReviewWordCloudVO());

        StatsOverviewVO overview = service.getOverview(STAT_DATE, "2026");

        assertNotNull(overview.getCategorySales());
        assertNotNull(overview.getHotCategoryRank());
        assertNotNull(overview.getOrderTrend());
        assertNotNull(overview.getShopEntry());
        assertNotNull(overview.getRegionRank());
        assertNotNull(overview.getReviewWordCloud());
        verify(statsMapper, atLeastOnce()).selectCategoryQtyByFinishDate(STAT_DATE);
    }

    private CategoryQtyRow row(Long id, String name, Long qty)
    {
        CategoryQtyRow row = new CategoryQtyRow();
        row.setCategoryId(id);
        row.setCategoryName(name);
        row.setQty(qty);
        return row;
    }

    private MonthCountRow monthRow(int month, long cnt)
    {
        MonthCountRow row = new MonthCountRow();
        row.setMonth(month);
        row.setCnt(cnt);
        return row;
    }
}
