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

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.utils.StringUtils;
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.IMallStatsOpenService;
import com.ruoyi.web.modules.openstats.support.AddressCityParser;
import com.ruoyi.web.modules.openstats.support.ReviewWordCloudSupport;
import com.ruoyi.web.modules.openstats.support.StatsCacheKeys;
import com.ruoyi.web.modules.openstats.vo.CategorySalesItemVO;
import com.ruoyi.web.modules.openstats.vo.CategorySalesVO;
import com.ruoyi.web.modules.openstats.vo.HotCategoryRankItemVO;
import com.ruoyi.web.modules.openstats.vo.HotCategoryRankVO;
import com.ruoyi.web.modules.openstats.vo.OrderTrendItemVO;
import com.ruoyi.web.modules.openstats.vo.OrderTrendVO;
import com.ruoyi.web.modules.openstats.vo.RegionRankItemVO;
import com.ruoyi.web.modules.openstats.vo.RegionRankVO;
import com.ruoyi.web.modules.openstats.vo.ReviewWordCloudVO;
import com.ruoyi.web.modules.openstats.vo.ShopEntryItemVO;
import com.ruoyi.web.modules.openstats.vo.ShopEntryVO;
import com.ruoyi.web.modules.openstats.vo.StatsOverviewVO;

@Service
public class MallStatsOpenServiceImpl implements IMallStatsOpenService
{
    private static final ZoneId ZONE_SHANGHAI = ZoneId.of("Asia/Shanghai");

    private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern(OpenStatsConstants.DATE_PATTERN);

    @Autowired
    private MallStatsOpenMapper statsMapper;

    @Autowired
    private RedisCache redisCache;

    @Autowired
    private ReviewWordCloudSupport wordCloudSupport;

    @Override
    public CategorySalesVO getCategorySales(String statDate)
    {
        String date = resolveStatDate(statDate);
        String cacheKey = StatsCacheKeys.categorySales(date);
        CategorySalesVO cached = redisCache.getCacheObject(cacheKey);
        if (cached != null)
        {
            return cached;
        }
        List<CategoryQtyRow> rows = safeList(statsMapper.selectCategoryQtyByFinishDate(date));
        CategorySalesVO vo = buildCategorySales(date, rows);
        redisCache.setCacheObject(cacheKey, vo, OpenStatsConstants.CACHE_TTL_CATEGORY_MIN, TimeUnit.MINUTES);
        return vo;
    }

    @Override
    public HotCategoryRankVO getHotCategoryRank(String statDate)
    {
        String date = resolveStatDate(statDate);
        String cacheKey = StatsCacheKeys.hotCategory(date);
        HotCategoryRankVO cached = redisCache.getCacheObject(cacheKey);
        if (cached != null)
        {
            return cached;
        }
        List<CategoryQtyRow> rows = safeList(statsMapper.selectCategoryQtyByFinishDate(date));
        HotCategoryRankVO vo = buildHotCategoryRank(date, rows);
        redisCache.setCacheObject(cacheKey, vo, OpenStatsConstants.CACHE_TTL_CATEGORY_MIN, TimeUnit.MINUTES);
        return vo;
    }

    @Override
    public OrderTrendVO getOrderTrend(String statYear)
    {
        int year = resolveStatYear(statYear);
        String cacheKey = StatsCacheKeys.orderTrend(year);
        OrderTrendVO cached = redisCache.getCacheObject(cacheKey);
        if (cached != null)
        {
            return cached;
        }
        List<MonthCountRow> rows = safeList(statsMapper.selectOrderCountByYear(year));
        OrderTrendVO vo = buildOrderTrend(year, rows);
        redisCache.setCacheObject(cacheKey, vo, OpenStatsConstants.CACHE_TTL_YEARLY_HOUR, TimeUnit.HOURS);
        return vo;
    }

    @Override
    public ShopEntryVO getShopEntry(String statYear)
    {
        int year = resolveStatYear(statYear);
        String cacheKey = StatsCacheKeys.shopEntry(year);
        ShopEntryVO cached = redisCache.getCacheObject(cacheKey);
        if (cached != null)
        {
            return cached;
        }
        List<MonthCountRow> rows = safeList(statsMapper.selectShopCountByYear(year));
        ShopEntryVO vo = buildShopEntry(year, rows);
        redisCache.setCacheObject(cacheKey, vo, OpenStatsConstants.CACHE_TTL_YEARLY_HOUR, TimeUnit.HOURS);
        return vo;
    }

    @Override
    public RegionRankVO getRegionRank(String statYear)
    {
        int year = resolveStatYear(statYear);
        String cacheKey = StatsCacheKeys.regionRank(year);
        RegionRankVO cached = redisCache.getCacheObject(cacheKey);
        if (cached != null)
        {
            return cached;
        }
        List<RegionAmountRow> rows = safeList(statsMapper.selectRegionAmountByYear(year));
        RegionRankVO vo = buildRegionRank(year, rows);
        redisCache.setCacheObject(cacheKey, vo, OpenStatsConstants.CACHE_TTL_YEARLY_HOUR, TimeUnit.HOURS);
        return vo;
    }

    @Override
    public ReviewWordCloudVO getReviewWordCloud()
    {
        String cacheKey = StatsCacheKeys.wordCloud();
        ReviewWordCloudVO cached = redisCache.getCacheObject(cacheKey);
        if (cached != null)
        {
            return cached;
        }
        List<String> contents = safeList(statsMapper.selectReviewContentsForWordCloud());
        ReviewWordCloudVO vo = wordCloudSupport.buildTopWords(contents);
        redisCache.setCacheObject(cacheKey, vo, OpenStatsConstants.CACHE_TTL_WORDCLOUD_HOUR, TimeUnit.HOURS);
        return vo;
    }

    @Override
    public StatsOverviewVO getOverview(String statDate, String statYear)
    {
        StatsOverviewVO overview = new StatsOverviewVO();
        overview.setCategorySales(getCategorySales(statDate));
        overview.setHotCategoryRank(getHotCategoryRank(statDate));
        overview.setOrderTrend(getOrderTrend(statYear));
        overview.setShopEntry(getShopEntry(statYear));
        overview.setRegionRank(getRegionRank(statYear));
        overview.setReviewWordCloud(getReviewWordCloud());
        return overview;
    }

    private CategorySalesVO buildCategorySales(String statDate, List<CategoryQtyRow> rows)
    {
        CategorySalesVO vo = new CategorySalesVO();
        vo.setStatDate(statDate);
        long totalQty = 0L;
        List<CategorySalesItemVO> items = new ArrayList<>();
        for (CategoryQtyRow row : rows)
        {
            long qty = row.getQty() != null ? row.getQty() : 0L;
            totalQty += qty;
            CategorySalesItemVO item = new CategorySalesItemVO();
            item.setCategoryId(row.getCategoryId() != null ? row.getCategoryId() : OpenStatsConstants.UNCATEGORIZED_ID);
            item.setCategoryName(StringUtils.isNotEmpty(row.getCategoryName()) ? row.getCategoryName()
                    : OpenStatsConstants.UNCATEGORIZED_NAME);
            item.setQty(qty);
            items.add(item);
        }
        vo.setTotalQty(totalQty);
        applyCategoryRatios(items, totalQty);
        vo.setItems(items);
        return vo;
    }

    private HotCategoryRankVO buildHotCategoryRank(String statDate, List<CategoryQtyRow> rows)
    {
        rows.sort(Comparator.comparing(CategoryQtyRow::getQty, Comparator.nullsFirst(Long::compareTo)).reversed()
                .thenComparing(row -> row.getCategoryId() != null ? row.getCategoryId() : OpenStatsConstants.UNCATEGORIZED_ID));
        HotCategoryRankVO vo = new HotCategoryRankVO();
        vo.setStatDate(statDate);
        int limit = Math.min(OpenStatsConstants.HOT_CATEGORY_LIMIT, rows.size());
        for (int i = 0; i < limit; i++)
        {
            CategoryQtyRow row = rows.get(i);
            HotCategoryRankItemVO item = new HotCategoryRankItemVO();
            item.setRank(i + 1);
            item.setCategoryId(row.getCategoryId() != null ? row.getCategoryId() : OpenStatsConstants.UNCATEGORIZED_ID);
            item.setCategoryName(StringUtils.isNotEmpty(row.getCategoryName()) ? row.getCategoryName()
                    : OpenStatsConstants.UNCATEGORIZED_NAME);
            item.setQty(row.getQty() != null ? row.getQty() : 0L);
            vo.getItems().add(item);
        }
        return vo;
    }

    private OrderTrendVO buildOrderTrend(int statYear, List<MonthCountRow> rows)
    {
        Map<Integer, Long> monthMap = toMonthMap(rows);
        OrderTrendVO vo = new OrderTrendVO();
        vo.setStatYear(statYear);
        for (int month = 1; month <= OpenStatsConstants.MONTHS_IN_YEAR; month++)
        {
            OrderTrendItemVO item = new OrderTrendItemVO();
            item.setMonth(month);
            item.setOrderCount(monthMap.getOrDefault(month, 0L));
            vo.getItems().add(item);
        }
        return vo;
    }

    private ShopEntryVO buildShopEntry(int statYear, List<MonthCountRow> rows)
    {
        Map<Integer, Long> monthMap = toMonthMap(rows);
        long yearTotal = monthMap.values().stream().mapToLong(Long::longValue).sum();
        ShopEntryVO vo = new ShopEntryVO();
        vo.setStatYear(statYear);
        vo.setYearTotal(yearTotal);
        for (int month = 1; month <= OpenStatsConstants.MONTHS_IN_YEAR; month++)
        {
            long shopCount = monthMap.getOrDefault(month, 0L);
            ShopEntryItemVO item = new ShopEntryItemVO();
            item.setMonth(month);
            item.setShopCount(shopCount);
            if (yearTotal > 0)
            {
                BigDecimal ratio = BigDecimal.valueOf(shopCount * 100.0 / yearTotal)
                        .setScale(1, RoundingMode.HALF_UP);
                item.setRatio(ratio.doubleValue());
            }
            vo.getItems().add(item);
        }
        adjustShopEntryRatios(vo.getItems(), yearTotal);
        return vo;
    }

    private RegionRankVO buildRegionRank(int statYear, List<RegionAmountRow> rows)
    {
        Map<String, BigDecimal> cityAmount = new HashMap<>();
        for (RegionAmountRow row : rows)
        {
            String city = AddressCityParser.parseCity(row.getConsigneeAddress());
            BigDecimal amount = row.getAmount() != null ? row.getAmount() : BigDecimal.ZERO;
            cityAmount.merge(city, amount, BigDecimal::add);
        }
        List<Map.Entry<String, BigDecimal>> sorted = new ArrayList<>(cityAmount.entrySet());
        sorted.sort((left, right) -> {
            int amountCmp = right.getValue().compareTo(left.getValue());
            if (amountCmp != 0)
            {
                return amountCmp;
            }
            return left.getKey().compareTo(right.getKey());
        });
        RegionRankVO vo = new RegionRankVO();
        vo.setStatYear(statYear);
        int limit = Math.min(OpenStatsConstants.REGION_RANK_LIMIT, sorted.size());
        for (int i = 0; i < limit; i++)
        {
            Map.Entry<String, BigDecimal> entry = sorted.get(i);
            RegionRankItemVO item = new RegionRankItemVO();
            item.setRank(i + 1);
            item.setCity(entry.getKey());
            item.setAmountWan(entry.getValue()
                    .divide(new BigDecimal("10000"), 2, RoundingMode.HALF_UP)
                    .doubleValue());
            vo.getItems().add(item);
        }
        return vo;
    }

    private void applyCategoryRatios(List<CategorySalesItemVO> items, long totalQty)
    {
        if (totalQty <= 0 || items.isEmpty())
        {
            return;
        }
        BigDecimal sum = BigDecimal.ZERO;
        int maxIdx = 0;
        long maxQty = Long.MIN_VALUE;
        for (int i = 0; i < items.size(); i++)
        {
            CategorySalesItemVO item = items.get(i);
            BigDecimal ratio = BigDecimal.valueOf(item.getQty() * 100.0 / totalQty)
                    .setScale(1, RoundingMode.HALF_UP);
            item.setRatio(ratio.doubleValue());
            sum = sum.add(ratio);
            if (item.getQty() > maxQty)
            {
                maxQty = item.getQty();
                maxIdx = i;
            }
        }
        if (items.size() > 1)
        {
            BigDecimal target = new BigDecimal("100.0");
            if (sum.compareTo(target) != 0)
            {
                BigDecimal adjusted = BigDecimal.valueOf(items.get(maxIdx).getRatio())
                        .add(target.subtract(sum))
                        .setScale(1, RoundingMode.HALF_UP);
                items.get(maxIdx).setRatio(adjusted.doubleValue());
            }
        }
    }

    private void adjustShopEntryRatios(List<ShopEntryItemVO> items, long yearTotal)
    {
        if (yearTotal <= 0 || items.isEmpty())
        {
            return;
        }
        BigDecimal sum = BigDecimal.ZERO;
        int maxIdx = 0;
        long maxCount = Long.MIN_VALUE;
        for (int i = 0; i < items.size(); i++)
        {
            ShopEntryItemVO item = items.get(i);
            if (item.getRatio() == null)
            {
                continue;
            }
            sum = sum.add(BigDecimal.valueOf(item.getRatio()));
            if (item.getShopCount() > maxCount)
            {
                maxCount = item.getShopCount();
                maxIdx = i;
            }
        }
        if (items.size() > 1 && sum.compareTo(new BigDecimal("100.0")) != 0
                && items.get(maxIdx).getRatio() != null)
        {
            BigDecimal adjusted = BigDecimal.valueOf(items.get(maxIdx).getRatio())
                    .add(new BigDecimal("100.0").subtract(sum))
                    .setScale(1, RoundingMode.HALF_UP);
            items.get(maxIdx).setRatio(adjusted.doubleValue());
        }
    }

    private Map<Integer, Long> toMonthMap(List<MonthCountRow> rows)
    {
        Map<Integer, Long> monthMap = new HashMap<>();
        for (MonthCountRow row : rows)
        {
            if (row.getMonth() != null)
            {
                monthMap.put(row.getMonth(), row.getCnt() != null ? row.getCnt() : 0L);
            }
        }
        return monthMap;
    }

    private String resolveStatDate(String statDate)
    {
        if (StringUtils.isEmpty(statDate))
        {
            return LocalDate.now(ZONE_SHANGHAI).format(DATE_FMT);
        }
        try
        {
            return LocalDate.parse(statDate.trim(), DATE_FMT).format(DATE_FMT);
        }
        catch (DateTimeParseException ex)
        {
            return LocalDate.now(ZONE_SHANGHAI).format(DATE_FMT);
        }
    }

    private int resolveStatYear(String statYear)
    {
        if (StringUtils.isEmpty(statYear))
        {
            return LocalDate.now(ZONE_SHANGHAI).getYear();
        }
        try
        {
            return Integer.parseInt(statYear.trim());
        }
        catch (NumberFormatException ex)
        {
            return LocalDate.now(ZONE_SHANGHAI).getYear();
        }
    }

    private <T> List<T> safeList(List<T> list)
    {
        return list != null ? list : Collections.emptyList();
    }
}
