package com.haha.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.haha.entity.dto.*;
import com.haha.common.constant.DeviceConstants;
import com.haha.common.constant.OrderConstants;
import com.haha.common.enums.CommonStatus;
import com.haha.common.enums.UserTag;
import com.haha.entity.*;
import com.haha.mapper.*;
import com.haha.service.StatisticsService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@Service
public class StatisticsServiceImpl implements StatisticsService {

    @Autowired
    private OrderMapper orderMapper;

    @Autowired
    private OrderItemMapper orderItemMapper;

    @Autowired
    private DeviceMapper deviceMapper;

    @Autowired
    private ShopMapper shopMapper;

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private ProductMapper productMapper;

    @Override
    public StatisticsOverviewVO getOverview(StatisticsQueryDTO queryDTO) {
        StatisticsOverviewVO overview = new StatisticsOverviewVO();
        
        LocalDate startDate = parseDate(queryDTO.getStartDate(), LocalDate.now().minusDays(30));
        LocalDate endDate = parseDate(queryDTO.getEndDate(), LocalDate.now());
        
        LocalDateTime startDateTime = startDate.atStartOfDay();
        LocalDateTime endDateTime = endDate.atTime(LocalTime.MAX);
        
        LambdaQueryWrapper<Order> orderWrapper = new LambdaQueryWrapper<>();
        orderWrapper.eq(Order::getPayStatus, OrderConstants.PAY_STATUS_PAID)
                   .ge(Order::getPayTime, startDateTime)
                   .le(Order::getPayTime, endDateTime);
        
        if (queryDTO.getShopId() != null) {
            orderWrapper.eq(Order::getShopId, queryDTO.getShopId());
        }
        
        // 只选需要的列，避免 SELECT * 全量加载
        orderWrapper.select(Order::getTotalAmount, Order::getUserId);
        List<Order> orders = orderMapper.selectList(orderWrapper);

        BigDecimal totalSales = orders.stream()
                .map(o -> o.getTotalAmount() != null ? o.getTotalAmount() : BigDecimal.ZERO)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        overview.setTotalSales(totalSales.setScale(2, RoundingMode.HALF_UP));
        overview.setTotalOrders(orders.size());

        Set<Long> userIds = orders.stream()
                .map(Order::getUserId).filter(Objects::nonNull).collect(Collectors.toSet());
        overview.setTotalUsers(userIds.size());

        long totalDevices = deviceMapper.selectCount(null);
        long onlineDevices = deviceMapper.selectCount(
                new LambdaQueryWrapper<Device>().eq(Device::getStatus, DeviceConstants.STATUS_ONLINE));
        overview.setTotalDevices((int) totalDevices);
        overview.setOnlineDevices((int) onlineDevices);

        long totalShops = shopMapper.selectCount(null);
        overview.setTotalShops((int) totalShops);

        BigDecimal avgOrderAmount = orders.size() > 0
                ? totalSales.divide(BigDecimal.valueOf(orders.size()), 2, RoundingMode.HALF_UP)
                : BigDecimal.ZERO;
        overview.setAvgOrderAmount(avgOrderAmount);
        
        List<CategoryStatVO> categoryList = getCategoryOverview(queryDTO);
        overview.setCategoryList(categoryList);
        
        BigDecimal totalProfit = categoryList.stream()
                .map(c -> c.getProfitAmount() != null ? c.getProfitAmount() : BigDecimal.ZERO)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        overview.setTotalProfit(totalProfit.setScale(2, RoundingMode.HALF_UP));
        
        BigDecimal avgProfitRate = totalSales.compareTo(BigDecimal.ZERO) > 0 
                ? totalProfit.divide(totalSales, 4, RoundingMode.HALF_UP).multiply(new BigDecimal("100"))
                : BigDecimal.ZERO;
        overview.setAvgProfitRate(avgProfitRate.setScale(2, RoundingMode.HALF_UP));
        
        return overview;
    }

    @Override
    public List<CategoryStatVO> getCategoryOverview(StatisticsQueryDTO queryDTO) {
        LocalDate startDate = parseDate(queryDTO.getStartDate(), LocalDate.now().minusDays(30));
        LocalDate endDate = parseDate(queryDTO.getEndDate(), LocalDate.now());
        
        LocalDateTime startDateTime = startDate.atStartOfDay();
        LocalDateTime endDateTime = endDate.atTime(LocalTime.MAX);
        
        LambdaQueryWrapper<OrderItem> wrapper = new LambdaQueryWrapper<>();
        wrapper.ge(OrderItem::getPayTime, startDateTime)
               .le(OrderItem::getPayTime, endDateTime);
        
        if (queryDTO.getShopId() != null) {
            wrapper.eq(OrderItem::getShopId, queryDTO.getShopId());
        }
        
        // 只选需要的列
        wrapper.select(OrderItem::getProductType, OrderItem::getQuantity, OrderItem::getTotalAmount,
                OrderItem::getCostPrice, OrderItem::getOrderId);
        List<OrderItem> items = orderItemMapper.selectList(wrapper);

        Map<String, List<OrderItem>> categoryMap = items.stream()
                .filter(item -> item.getProductType() != null)
                .collect(Collectors.groupingBy(OrderItem::getProductType));

        BigDecimal totalSales = items.stream()
                .map(i -> i.getTotalAmount() != null ? i.getTotalAmount() : BigDecimal.ZERO)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        
        List<CategoryStatVO> result = new ArrayList<>();
        for (Map.Entry<String, List<OrderItem>> entry : categoryMap.entrySet()) {
            List<OrderItem> categoryItems = entry.getValue();
            
            CategoryStatVO vo = new CategoryStatVO();
            vo.setCategory(entry.getKey());
            vo.setQuantity(categoryItems.stream().mapToInt(i -> i.getQuantity() != null ? i.getQuantity() : 0).sum());
            
            BigDecimal salesAmount = categoryItems.stream()
                    .map(i -> i.getTotalAmount() != null ? i.getTotalAmount() : BigDecimal.ZERO)
                    .reduce(BigDecimal.ZERO, BigDecimal::add);
            vo.setSalesAmount(salesAmount.setScale(2, RoundingMode.HALF_UP));
            
            BigDecimal costAmount = categoryItems.stream()
                    .map(i -> i.getCostPrice() != null && i.getQuantity() != null 
                            ? i.getCostPrice().multiply(BigDecimal.valueOf(i.getQuantity())) 
                            : BigDecimal.ZERO)
                    .reduce(BigDecimal.ZERO, BigDecimal::add);
            vo.setCostAmount(costAmount.setScale(2, RoundingMode.HALF_UP));
            
            BigDecimal profitAmount = salesAmount.subtract(costAmount);
            vo.setProfitAmount(profitAmount.setScale(2, RoundingMode.HALF_UP));
            
            BigDecimal profitRate = salesAmount.compareTo(BigDecimal.ZERO) > 0 
                    ? profitAmount.divide(salesAmount, 4, RoundingMode.HALF_UP).multiply(new BigDecimal("100"))
                    : BigDecimal.ZERO;
            vo.setProfitRate(profitRate.setScale(2, RoundingMode.HALF_UP));
            
            vo.setOrderCount((int) categoryItems.stream().map(OrderItem::getOrderId).distinct().count());
            
            BigDecimal percentage = totalSales.compareTo(BigDecimal.ZERO) > 0 
                    ? salesAmount.divide(totalSales, 4, RoundingMode.HALF_UP).multiply(new BigDecimal("100"))
                    : BigDecimal.ZERO;
            vo.setPercentage(percentage.setScale(2, RoundingMode.HALF_UP));
            
            result.add(vo);
        }
        
        result.sort((a, b) -> b.getSalesAmount().compareTo(a.getSalesAmount()));
        
        return result;
    }

    @Override
    public TrendDataVO getCategoryTrend(StatisticsQueryDTO queryDTO) {
        TrendDataVO trend = new TrendDataVO();
        
        LocalDate startDate = parseDate(queryDTO.getStartDate(), LocalDate.now().minusDays(30));
        LocalDate endDate = parseDate(queryDTO.getEndDate(), LocalDate.now());
        
        List<String> dates = new ArrayList<>();
        List<SeriesDataVO> seriesList = new ArrayList<>();
        
        LambdaQueryWrapper<OrderItem> wrapper = new LambdaQueryWrapper<>();
        wrapper.ge(OrderItem::getPayTime, startDate.atStartOfDay())
               .le(OrderItem::getPayTime, endDate.atTime(LocalTime.MAX));
        
        if (queryDTO.getShopId() != null) {
            wrapper.eq(OrderItem::getShopId, queryDTO.getShopId());
        }
        if (queryDTO.getCategory() != null) {
            wrapper.eq(OrderItem::getProductType, queryDTO.getCategory());
        }
        
        wrapper.select(OrderItem::getProductType, OrderItem::getTotalAmount, OrderItem::getPayTime);
        List<OrderItem> items = orderItemMapper.selectList(wrapper);

        Map<String, Map<String, BigDecimal>> categoryDateAmount = new HashMap<>();
        for (OrderItem item : items) {
            String date = item.getPayTime().toLocalDate().toString();
            String category = item.getProductType() != null ? item.getProductType() : "其他";
            BigDecimal amount = item.getTotalAmount() != null ? item.getTotalAmount() : BigDecimal.ZERO;
            
            categoryDateAmount.computeIfAbsent(category, k -> new HashMap<>())
                    .merge(date, amount, BigDecimal::add);
        }
        
        long days = ChronoUnit.DAYS.between(startDate, endDate) + 1;
        for (int i = 0; i < days; i++) {
            LocalDate date = startDate.plusDays(i);
            dates.add(date.format(DateTimeFormatter.ofPattern("MM-dd")));
        }
        trend.setDates(dates);
        
        Set<String> categories = categoryDateAmount.keySet();
        for (String category : categories) {
            SeriesDataVO series = new SeriesDataVO();
            series.setName(category);
            
            List<BigDecimal> data = new ArrayList<>();
            for (int i = 0; i < days; i++) {
                LocalDate date = startDate.plusDays(i);
                BigDecimal amount = categoryDateAmount.getOrDefault(category, new HashMap<>())
                        .getOrDefault(date.toString(), BigDecimal.ZERO);
                data.add(amount.setScale(2, RoundingMode.HALF_UP));
            }
            series.setData(data);
            seriesList.add(series);
        }
        
        trend.setSeries(seriesList);
        return trend;
    }

    @Override
    public IPage<ProductStatVO> getProductList(StatisticsQueryDTO queryDTO) {
        queryDTO.validate();
        
        LocalDate startDate = parseDate(queryDTO.getStartDate(), LocalDate.now().minusDays(30));
        LocalDate endDate = parseDate(queryDTO.getEndDate(), LocalDate.now());
        
        LocalDateTime startDateTime = startDate.atStartOfDay();
        LocalDateTime endDateTime = endDate.atTime(LocalTime.MAX);
        
        LambdaQueryWrapper<OrderItem> wrapper = new LambdaQueryWrapper<>();
        wrapper.ge(OrderItem::getPayTime, startDateTime)
               .le(OrderItem::getPayTime, endDateTime);
        
        if (queryDTO.getShopId() != null) {
            wrapper.eq(OrderItem::getShopId, queryDTO.getShopId());
        }
        if (queryDTO.getDeviceId() != null) {
            wrapper.eq(OrderItem::getDeviceId, queryDTO.getDeviceId());
        }
        if (queryDTO.getCategory() != null) {
            wrapper.eq(OrderItem::getProductType, queryDTO.getCategory());
        }
        if (queryDTO.getKeyword() != null) {
            wrapper.and(w -> w.like(OrderItem::getProductName, queryDTO.getKeyword())
                    .or().like(OrderItem::getProductCode, queryDTO.getKeyword()));
        }
        
        List<OrderItem> items = orderItemMapper.selectList(wrapper);
        
        Map<Long, List<OrderItem>> productMap = items.stream()
                .filter(item -> item.getProductId() != null)
                .collect(Collectors.groupingBy(OrderItem::getProductId));
        
        List<ProductStatVO> resultList = new ArrayList<>();
        for (Map.Entry<Long, List<OrderItem>> entry : productMap.entrySet()) {
            List<OrderItem> productItems = entry.getValue();
            OrderItem first = productItems.get(0);
            
            ProductStatVO vo = new ProductStatVO();
            vo.setProductId(entry.getKey());
            vo.setProductCode(first.getProductCode());
            vo.setProductName(first.getProductName());
            vo.setCategory(first.getProductType());
            vo.setQuantity(productItems.stream().mapToInt(i -> i.getQuantity() != null ? i.getQuantity() : 0).sum());
            
            BigDecimal salesAmount = productItems.stream()
                    .map(i -> i.getTotalAmount() != null ? i.getTotalAmount() : BigDecimal.ZERO)
                    .reduce(BigDecimal.ZERO, BigDecimal::add);
            vo.setSalesAmount(salesAmount.setScale(2, RoundingMode.HALF_UP));
            
            BigDecimal costAmount = productItems.stream()
                    .map(i -> i.getCostPrice() != null && i.getQuantity() != null 
                            ? i.getCostPrice().multiply(BigDecimal.valueOf(i.getQuantity())) 
                            : BigDecimal.ZERO)
                    .reduce(BigDecimal.ZERO, BigDecimal::add);
            vo.setCostAmount(costAmount.setScale(2, RoundingMode.HALF_UP));
            
            BigDecimal profitAmount = salesAmount.subtract(costAmount);
            vo.setProfitAmount(profitAmount.setScale(2, RoundingMode.HALF_UP));
            
            BigDecimal profitRate = salesAmount.compareTo(BigDecimal.ZERO) > 0 
                    ? profitAmount.divide(salesAmount, 4, RoundingMode.HALF_UP).multiply(new BigDecimal("100"))
                    : BigDecimal.ZERO;
            vo.setProfitRate(profitRate.setScale(2, RoundingMode.HALF_UP));
            
            vo.setOrderCount((int) productItems.stream().map(OrderItem::getOrderId).distinct().count());
            vo.setUserCount((int) productItems.stream().map(OrderItem::getUserId).filter(Objects::nonNull).distinct().count());
            
            resultList.add(vo);
        }
        
        String sortBy = queryDTO.getSortBy() != null ? queryDTO.getSortBy() : "salesAmount";
        boolean asc = "asc".equalsIgnoreCase(queryDTO.getSortOrder());
        
        Comparator<ProductStatVO> comparator = null;
        switch (sortBy) {
            case "quantity":
                comparator = Comparator.comparing(ProductStatVO::getQuantity);
                break;
            case "profit":
                comparator = Comparator.comparing(ProductStatVO::getProfitAmount);
                break;
            case "salesAmount":
            default:
                comparator = Comparator.comparing(ProductStatVO::getSalesAmount);
                break;
        }
        
        if (!asc) {
            comparator = comparator.reversed();
        }
        resultList.sort(comparator);
        
        Page<ProductStatVO> page = new Page<>(queryDTO.getPage(), queryDTO.getPageSize());
        int start = (queryDTO.getPage() - 1) * queryDTO.getPageSize();
        int end = Math.min(start + queryDTO.getPageSize(), resultList.size());
        
        page.setRecords(resultList.subList(start, end));
        page.setTotal(resultList.size());
        
        return page;
    }

    @Override
    public List<ProductStatVO> getProductTop(StatisticsQueryDTO queryDTO) {
        LocalDate startDate = parseDate(queryDTO.getStartDate(), LocalDate.now().minusDays(30));
        LocalDate endDate = parseDate(queryDTO.getEndDate(), LocalDate.now());
        int limit = queryDTO.getLimit() != null ? queryDTO.getLimit() : 10;
        String type = queryDTO.getType() != null ? queryDTO.getType() : "sales";
        
        StatisticsQueryDTO listQuery = new StatisticsQueryDTO();
        listQuery.setStartDate(startDate.toString());
        listQuery.setEndDate(endDate.toString());
        listQuery.setShopId(queryDTO.getShopId());
        listQuery.setSortBy(type);
        listQuery.setSortOrder("desc");
        listQuery.setPage(1);
        listQuery.setPageSize(limit);
        
        IPage<ProductStatVO> page = getProductList(listQuery);
        return page.getRecords();
    }

    @Override
    public TrendDataVO getProductTrend(Long productId, StatisticsQueryDTO queryDTO) {
        TrendDataVO trend = new TrendDataVO();
        
        LocalDate startDate = parseDate(queryDTO.getStartDate(), LocalDate.now().minusDays(30));
        LocalDate endDate = parseDate(queryDTO.getEndDate(), LocalDate.now());
        
        LambdaQueryWrapper<OrderItem> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(OrderItem::getProductId, productId)
               .ge(OrderItem::getPayTime, startDate.atStartOfDay())
               .le(OrderItem::getPayTime, endDate.atTime(LocalTime.MAX));
        
        List<OrderItem> items = orderItemMapper.selectList(wrapper);
        
        Map<String, BigDecimal> dateAmount = items.stream()
                .collect(Collectors.groupingBy(
                        i -> i.getPayTime().toLocalDate().toString(),
                        Collectors.reducing(BigDecimal.ZERO, 
                                i -> i.getTotalAmount() != null ? i.getTotalAmount() : BigDecimal.ZERO,
                                BigDecimal::add)
                ));
        
        List<String> dates = new ArrayList<>();
        List<BigDecimal> data = new ArrayList<>();
        
        long days = ChronoUnit.DAYS.between(startDate, endDate) + 1;
        for (int i = 0; i < days; i++) {
            LocalDate date = startDate.plusDays(i);
            dates.add(date.format(DateTimeFormatter.ofPattern("MM-dd")));
            data.add(dateAmount.getOrDefault(date.toString(), BigDecimal.ZERO).setScale(2, RoundingMode.HALF_UP));
        }
        
        trend.setDates(dates);
        
        SeriesDataVO series = new SeriesDataVO();
        series.setName("销售额");
        series.setData(data);
        trend.setSeries(Collections.singletonList(series));
        
        return trend;
    }

    @Override
    public Map<String, Object> getDeviceOverview(StatisticsQueryDTO queryDTO) {
        Map<String, Object> result = new HashMap<>();
        
        LocalDate startDate = parseDate(queryDTO.getStartDate(), LocalDate.now().minusDays(30));
        LocalDate endDate = parseDate(queryDTO.getEndDate(), LocalDate.now());
        
        long totalDevices = deviceMapper.selectCount(null);
        long onlineDevices = deviceMapper.selectCount(
                new LambdaQueryWrapper<Device>().eq(Device::getStatus, DeviceConstants.STATUS_ONLINE)
        );
        
        result.put("totalDevices", totalDevices);
        result.put("onlineDevices", onlineDevices);
        result.put("offlineDevices", totalDevices - onlineDevices);
        result.put("onlineRate", totalDevices > 0 
                ? String.format("%.1f%%", onlineDevices * 100.0 / totalDevices) 
                : "0%");
        
        LambdaQueryWrapper<Order> orderWrapper = new LambdaQueryWrapper<>();
        orderWrapper.eq(Order::getPayStatus, OrderConstants.PAY_STATUS_PAID)
                   .ge(Order::getPayTime, startDate.atStartOfDay())
                   .le(Order::getPayTime, endDate.atTime(LocalTime.MAX));
        
        if (queryDTO.getShopId() != null) {
            orderWrapper.eq(Order::getShopId, queryDTO.getShopId());
        }
        
        List<Order> orders = orderMapper.selectList(orderWrapper);
        
        BigDecimal totalSales = orders.stream()
                .map(o -> o.getTotalAmount() != null ? o.getTotalAmount() : BigDecimal.ZERO)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        result.put("totalSales", totalSales.setScale(2, RoundingMode.HALF_UP));
        result.put("totalOrders", orders.size());
        
        return result;
    }

    @Override
    public IPage<DeviceStatVO> getDeviceList(StatisticsQueryDTO queryDTO) {
        queryDTO.validate();
        
        LocalDate startDate = parseDate(queryDTO.getStartDate(), LocalDate.now().minusDays(30));
        LocalDate endDate = parseDate(queryDTO.getEndDate(), LocalDate.now());
        
        LambdaQueryWrapper<Device> deviceWrapper = new LambdaQueryWrapper<>();
        if (queryDTO.getShopId() != null) {
            deviceWrapper.eq(Device::getShopId, queryDTO.getShopId());
        }
        if (queryDTO.getStatus() != null) {
            deviceWrapper.eq(Device::getStatus, queryDTO.getStatus());
        }
        if (queryDTO.getKeyword() != null) {
            deviceWrapper.and(w -> w.like(Device::getDeviceId, queryDTO.getKeyword())
                    .or().like(Device::getName, queryDTO.getKeyword()));
        }
        
        List<Device> devices = deviceMapper.selectList(deviceWrapper);
        
        LambdaQueryWrapper<Order> orderWrapper = new LambdaQueryWrapper<>();
        orderWrapper.eq(Order::getPayStatus, OrderConstants.PAY_STATUS_PAID)
                   .ge(Order::getPayTime, startDate.atStartOfDay())
                   .le(Order::getPayTime, endDate.atTime(LocalTime.MAX));
        
        if (queryDTO.getShopId() != null) {
            orderWrapper.eq(Order::getShopId, queryDTO.getShopId());
        }
        
        List<Order> orders = orderMapper.selectList(orderWrapper);
        
        Map<String, List<Order>> deviceOrderMap = orders.stream()
                .filter(o -> o.getDeviceId() != null)
                .collect(Collectors.groupingBy(Order::getDeviceId));
        
        // 批量查询门店名称（替代 N+1 逐条 selectById）
        Set<Long> shopIds = devices.stream().map(Device::getShopId).filter(Objects::nonNull).collect(Collectors.toSet());
        Map<Long, String> shopNameMap = shopIds.isEmpty() ? Map.of()
                : shopMapper.selectBatchIds(shopIds).stream()
                        .collect(Collectors.toMap(Shop::getId, Shop::getName, (a, b) -> a));

        List<DeviceStatVO> resultList = new ArrayList<>();
        for (Device device : devices) {
            DeviceStatVO vo = new DeviceStatVO();
            vo.setDeviceId(device.getDeviceId());
            vo.setDeviceName(device.getName());
            vo.setShopId(device.getShopId());
            vo.setStatus(device.getStatus());
            vo.setStatusLabel(DeviceConstants.isOnline(device.getStatus()) ? "在线" : "离线");
            vo.setStatusColor(DeviceConstants.isOnline(device.getStatus()) ? "success" : "info");
            vo.setShopName(shopNameMap.get(device.getShopId()));

            List<Order> deviceOrders = deviceOrderMap.getOrDefault(device.getDeviceId(), Collections.emptyList());
            vo.setOrderCount(deviceOrders.size());
            vo.setUserCount((int) deviceOrders.stream().map(Order::getUserId).filter(Objects::nonNull).distinct().count());

            BigDecimal salesAmount = deviceOrders.stream()
                    .map(o -> o.getTotalAmount() != null ? o.getTotalAmount() : BigDecimal.ZERO)
                    .reduce(BigDecimal.ZERO, BigDecimal::add);
            vo.setSalesAmount(salesAmount.setScale(2, RoundingMode.HALF_UP));

            BigDecimal avgOrderAmount = deviceOrders.size() > 0
                    ? salesAmount.divide(BigDecimal.valueOf(deviceOrders.size()), 2, RoundingMode.HALF_UP)
                    : BigDecimal.ZERO;
            vo.setAvgOrderAmount(avgOrderAmount);

            long days = ChronoUnit.DAYS.between(startDate, endDate) + 1;
            BigDecimal dailySales = salesAmount.divide(BigDecimal.valueOf(days), 2, RoundingMode.HALF_UP);
            vo.setDailySalesAmount(dailySales);

            resultList.add(vo);
        }
        
        String sortBy = queryDTO.getSortBy() != null ? queryDTO.getSortBy() : "salesAmount";
        boolean asc = "asc".equalsIgnoreCase(queryDTO.getSortOrder());
        
        Comparator<DeviceStatVO> comparator = null;
        switch (sortBy) {
            case "orderCount":
                comparator = Comparator.comparing(DeviceStatVO::getOrderCount);
                break;
            case "salesAmount":
            default:
                comparator = Comparator.comparing(DeviceStatVO::getSalesAmount);
                break;
        }
        
        if (!asc) {
            comparator = comparator.reversed();
        }
        resultList.sort(comparator);
        
        Page<DeviceStatVO> page = new Page<>(queryDTO.getPage(), queryDTO.getPageSize());
        int start = (queryDTO.getPage() - 1) * queryDTO.getPageSize();
        int end = Math.min(start + queryDTO.getPageSize(), resultList.size());
        
        page.setRecords(resultList.subList(start, end));
        page.setTotal(resultList.size());
        
        return page;
    }

    @Override
    public TrendDataVO getDeviceTrend(String deviceId, StatisticsQueryDTO queryDTO) {
        TrendDataVO trend = new TrendDataVO();
        
        LocalDate startDate = parseDate(queryDTO.getStartDate(), LocalDate.now().minusDays(30));
        LocalDate endDate = parseDate(queryDTO.getEndDate(), LocalDate.now());
        
        LambdaQueryWrapper<Order> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(Order::getDeviceId, deviceId)
               .eq(Order::getPayStatus, OrderConstants.PAY_STATUS_PAID)
               .ge(Order::getPayTime, startDate.atStartOfDay())
               .le(Order::getPayTime, endDate.atTime(LocalTime.MAX));
        
        wrapper.select(Order::getTotalAmount, Order::getUserId, Order::getPayTime, Order::getShopId, Order::getDeviceId);
        List<Order> orders = orderMapper.selectList(wrapper);
        
        Map<String, BigDecimal> dateAmount = orders.stream()
                .collect(Collectors.groupingBy(
                        o -> o.getPayTime().toLocalDate().toString(),
                        Collectors.reducing(BigDecimal.ZERO, 
                                o -> o.getTotalAmount() != null ? o.getTotalAmount() : BigDecimal.ZERO,
                                BigDecimal::add)
                ));
        
        List<String> dates = new ArrayList<>();
        List<BigDecimal> data = new ArrayList<>();
        
        long days = ChronoUnit.DAYS.between(startDate, endDate) + 1;
        for (int i = 0; i < days; i++) {
            LocalDate date = startDate.plusDays(i);
            dates.add(date.format(DateTimeFormatter.ofPattern("MM-dd")));
            data.add(dateAmount.getOrDefault(date.toString(), BigDecimal.ZERO).setScale(2, RoundingMode.HALF_UP));
        }
        
        trend.setDates(dates);
        
        SeriesDataVO series = new SeriesDataVO();
        series.setName("销售额");
        series.setData(data);
        trend.setSeries(Collections.singletonList(series));
        
        return trend;
    }

    @Override
    public Map<String, Object> getShopOverview(StatisticsQueryDTO queryDTO) {
        Map<String, Object> result = new HashMap<>();
        
        LocalDate startDate = parseDate(queryDTO.getStartDate(), LocalDate.now().minusDays(30));
        LocalDate endDate = parseDate(queryDTO.getEndDate(), LocalDate.now());
        
        long totalShops = shopMapper.selectCount(null);
        long activeShops = shopMapper.selectCount(
                new LambdaQueryWrapper<Shop>().eq(Shop::getStatus, CommonStatus.ENABLED.getCode())
        );
        
        result.put("totalShops", totalShops);
        result.put("activeShops", activeShops);
        
        LambdaQueryWrapper<Order> orderWrapper = new LambdaQueryWrapper<>();
        orderWrapper.eq(Order::getPayStatus, OrderConstants.PAY_STATUS_PAID)
                   .ge(Order::getPayTime, startDate.atStartOfDay())
                   .le(Order::getPayTime, endDate.atTime(LocalTime.MAX));
        
        List<Order> orders = orderMapper.selectList(orderWrapper);
        
        BigDecimal totalSales = orders.stream()
                .map(o -> o.getTotalAmount() != null ? o.getTotalAmount() : BigDecimal.ZERO)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        result.put("totalSales", totalSales.setScale(2, RoundingMode.HALF_UP));
        result.put("totalOrders", orders.size());
        
        return result;
    }

    @Override
    public IPage<ShopStatVO> getShopList(StatisticsQueryDTO queryDTO) {
        queryDTO.validate();
        
        LocalDate startDate = parseDate(queryDTO.getStartDate(), LocalDate.now().minusDays(30));
        LocalDate endDate = parseDate(queryDTO.getEndDate(), LocalDate.now());
        
        LambdaQueryWrapper<Shop> shopWrapper = new LambdaQueryWrapper<>();
        if (queryDTO.getProvince() != null) {
            shopWrapper.eq(Shop::getProvince, queryDTO.getProvince());
        }
        if (queryDTO.getCity() != null) {
            shopWrapper.eq(Shop::getCity, queryDTO.getCity());
        }
        if (queryDTO.getDistrict() != null) {
            shopWrapper.eq(Shop::getDistrict, queryDTO.getDistrict());
        }
        if (queryDTO.getStatus() != null) {
            shopWrapper.eq(Shop::getStatus, queryDTO.getStatus());
        }
        if (queryDTO.getKeyword() != null) {
            shopWrapper.like(Shop::getName, queryDTO.getKeyword());
        }
        
        List<Shop> shops = shopMapper.selectList(shopWrapper);
        
        LambdaQueryWrapper<Order> orderWrapper = new LambdaQueryWrapper<>();
        orderWrapper.eq(Order::getPayStatus, OrderConstants.PAY_STATUS_PAID)
                   .ge(Order::getPayTime, startDate.atStartOfDay())
                   .le(Order::getPayTime, endDate.atTime(LocalTime.MAX));
        
        List<Order> orders = orderMapper.selectList(orderWrapper);
        
        Map<Long, List<Order>> shopOrderMap = orders.stream()
                .filter(o -> o.getShopId() != null)
                .collect(Collectors.groupingBy(Order::getShopId));
        
        List<ShopStatVO> resultList = new ArrayList<>();
        for (Shop shop : shops) {
            ShopStatVO vo = new ShopStatVO();
            vo.setShopId(shop.getId());
            vo.setShopName(shop.getName());
            vo.setProvince(shop.getProvince());
            vo.setCity(shop.getCity());
            vo.setDistrict(shop.getDistrict());
            vo.setAddress(shop.getAddress());
            vo.setStatus(shop.getStatus());
            vo.setStatusLabel(CommonStatus.isEnabled(shop.getStatus()) ? "启用" : "禁用");
            
            long deviceCount = deviceMapper.selectCount(
                    new LambdaQueryWrapper<Device>().eq(Device::getShopId, shop.getId())
            );
            vo.setDeviceCount((int) deviceCount);
            
            List<Order> shopOrders = shopOrderMap.getOrDefault(shop.getId(), Collections.emptyList());
            
            vo.setOrderCount(shopOrders.size());
            vo.setUserCount((int) shopOrders.stream().map(Order::getUserId).filter(Objects::nonNull).distinct().count());
            
            BigDecimal salesAmount = shopOrders.stream()
                    .map(o -> o.getTotalAmount() != null ? o.getTotalAmount() : BigDecimal.ZERO)
                    .reduce(BigDecimal.ZERO, BigDecimal::add);
            vo.setSalesAmount(salesAmount.setScale(2, RoundingMode.HALF_UP));
            
            BigDecimal avgOrderAmount = shopOrders.size() > 0 
                    ? salesAmount.divide(BigDecimal.valueOf(shopOrders.size()), 2, RoundingMode.HALF_UP)
                    : BigDecimal.ZERO;
            vo.setAvgOrderAmount(avgOrderAmount);
            
            long days = ChronoUnit.DAYS.between(startDate, endDate) + 1;
            BigDecimal dailySales = salesAmount.divide(BigDecimal.valueOf(days), 2, RoundingMode.HALF_UP);
            vo.setDailySalesAmount(dailySales);
            
            BigDecimal deviceOutput = deviceCount > 0 
                    ? salesAmount.divide(BigDecimal.valueOf(deviceCount), 2, RoundingMode.HALF_UP)
                    : BigDecimal.ZERO;
            vo.setDeviceOutput(deviceOutput);
            
            resultList.add(vo);
        }
        
        String sortBy = queryDTO.getSortBy() != null ? queryDTO.getSortBy() : "salesAmount";
        boolean asc = "asc".equalsIgnoreCase(queryDTO.getSortOrder());
        
        Comparator<ShopStatVO> comparator = null;
        switch (sortBy) {
            case "orderCount":
                comparator = Comparator.comparing(ShopStatVO::getOrderCount);
                break;
            case "profit":
                comparator = Comparator.comparing(ShopStatVO::getProfitAmount);
                break;
            case "salesAmount":
            default:
                comparator = Comparator.comparing(ShopStatVO::getSalesAmount);
                break;
        }
        
        if (!asc) {
            comparator = comparator.reversed();
        }
        resultList.sort(comparator);
        
        Page<ShopStatVO> page = new Page<>(queryDTO.getPage(), queryDTO.getPageSize());
        int start = (queryDTO.getPage() - 1) * queryDTO.getPageSize();
        int end = Math.min(start + queryDTO.getPageSize(), resultList.size());
        
        page.setRecords(resultList.subList(start, end));
        page.setTotal(resultList.size());
        
        return page;
    }

    @Override
    public List<ShopStatVO> getShopRanking(StatisticsQueryDTO queryDTO) {
        LocalDate startDate = parseDate(queryDTO.getStartDate(), LocalDate.now().minusDays(30));
        LocalDate endDate = parseDate(queryDTO.getEndDate(), LocalDate.now());
        int limit = queryDTO.getLimit() != null ? queryDTO.getLimit() : 10;
        String type = queryDTO.getType() != null ? queryDTO.getType() : "sales";
        
        StatisticsQueryDTO listQuery = new StatisticsQueryDTO();
        listQuery.setStartDate(startDate.toString());
        listQuery.setEndDate(endDate.toString());
        listQuery.setProvince(queryDTO.getProvince());
        listQuery.setCity(queryDTO.getCity());
        listQuery.setSortBy(type);
        listQuery.setSortOrder("desc");
        listQuery.setPage(1);
        listQuery.setPageSize(limit);
        
        IPage<ShopStatVO> page = getShopList(listQuery);
        return page.getRecords();
    }

    @Override
    public TrendDataVO getShopTrend(Long shopId, StatisticsQueryDTO queryDTO) {
        TrendDataVO trend = new TrendDataVO();
        
        LocalDate startDate = parseDate(queryDTO.getStartDate(), LocalDate.now().minusDays(30));
        LocalDate endDate = parseDate(queryDTO.getEndDate(), LocalDate.now());
        
        LambdaQueryWrapper<Order> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(Order::getShopId, shopId)
               .eq(Order::getPayStatus, OrderConstants.PAY_STATUS_PAID)
               .ge(Order::getPayTime, startDate.atStartOfDay())
               .le(Order::getPayTime, endDate.atTime(LocalTime.MAX));
        
        wrapper.select(Order::getTotalAmount, Order::getUserId, Order::getPayTime, Order::getShopId, Order::getDeviceId);
        List<Order> orders = orderMapper.selectList(wrapper);
        
        Map<String, BigDecimal> dateAmount = orders.stream()
                .collect(Collectors.groupingBy(
                        o -> o.getPayTime().toLocalDate().toString(),
                        Collectors.reducing(BigDecimal.ZERO, 
                                o -> o.getTotalAmount() != null ? o.getTotalAmount() : BigDecimal.ZERO,
                                BigDecimal::add)
                ));
        
        List<String> dates = new ArrayList<>();
        List<BigDecimal> data = new ArrayList<>();
        
        long days = ChronoUnit.DAYS.between(startDate, endDate) + 1;
        for (int i = 0; i < days; i++) {
            LocalDate date = startDate.plusDays(i);
            dates.add(date.format(DateTimeFormatter.ofPattern("MM-dd")));
            data.add(dateAmount.getOrDefault(date.toString(), BigDecimal.ZERO).setScale(2, RoundingMode.HALF_UP));
        }
        
        trend.setDates(dates);
        
        SeriesDataVO series = new SeriesDataVO();
        series.setName("销售额");
        series.setData(data);
        trend.setSeries(Collections.singletonList(series));
        
        return trend;
    }

    @Override
    public Map<String, Object> getProfitOverview(StatisticsQueryDTO queryDTO) {
        Map<String, Object> result = new HashMap<>();
        
        LocalDate startDate = parseDate(queryDTO.getStartDate(), LocalDate.now().minusDays(30));
        LocalDate endDate = parseDate(queryDTO.getEndDate(), LocalDate.now());
        
        LocalDateTime startDateTime = startDate.atStartOfDay();
        LocalDateTime endDateTime = endDate.atTime(LocalTime.MAX);
        
        LambdaQueryWrapper<OrderItem> itemWrapper = new LambdaQueryWrapper<>();
        itemWrapper.ge(OrderItem::getPayTime, startDateTime)
                   .le(OrderItem::getPayTime, endDateTime);
        
        List<OrderItem> items = orderItemMapper.selectList(itemWrapper);
        
        BigDecimal salesAmount = items.stream()
                .map(i -> i.getTotalAmount() != null ? i.getTotalAmount() : BigDecimal.ZERO)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        result.put("salesAmount", salesAmount.setScale(2, RoundingMode.HALF_UP));
        
        BigDecimal costAmount = items.stream()
                .map(i -> i.getCostPrice() != null && i.getQuantity() != null 
                        ? i.getCostPrice().multiply(BigDecimal.valueOf(i.getQuantity())) 
                        : BigDecimal.ZERO)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        result.put("costAmount", costAmount.setScale(2, RoundingMode.HALF_UP));
        
        BigDecimal grossProfit = salesAmount.subtract(costAmount);
        result.put("grossProfit", grossProfit.setScale(2, RoundingMode.HALF_UP));
        
        BigDecimal grossProfitRate = salesAmount.compareTo(BigDecimal.ZERO) > 0 
                ? grossProfit.divide(salesAmount, 4, RoundingMode.HALF_UP).multiply(new BigDecimal("100"))
                : BigDecimal.ZERO;
        result.put("grossProfitRate", grossProfitRate.setScale(2, RoundingMode.HALF_UP));
        
        return result;
    }

    @Override
    public IPage<ProfitStatVO> getProfitList(StatisticsQueryDTO queryDTO) {
        queryDTO.validate();
        
        LocalDate startDate = parseDate(queryDTO.getStartDate(), LocalDate.now().minusDays(30));
        LocalDate endDate = parseDate(queryDTO.getEndDate(), LocalDate.now());
        
        LambdaQueryWrapper<Shop> shopWrapper = new LambdaQueryWrapper<>();
        if (queryDTO.getProvince() != null) {
            shopWrapper.eq(Shop::getProvince, queryDTO.getProvince());
        }
        if (queryDTO.getCity() != null) {
            shopWrapper.eq(Shop::getCity, queryDTO.getCity());
        }
        if (queryDTO.getShopId() != null) {
            shopWrapper.eq(Shop::getId, queryDTO.getShopId());
        }
        
        List<Shop> shops = shopMapper.selectList(shopWrapper);
        
        LambdaQueryWrapper<OrderItem> itemWrapper = new LambdaQueryWrapper<>();
        itemWrapper.ge(OrderItem::getPayTime, startDate.atStartOfDay())
                   .le(OrderItem::getPayTime, endDate.atTime(LocalTime.MAX));
        
        List<OrderItem> items = orderItemMapper.selectList(itemWrapper);
        
        Map<Long, List<OrderItem>> shopItemMap = items.stream()
                .filter(i -> i.getShopId() != null)
                .collect(Collectors.groupingBy(OrderItem::getShopId));
        
        List<ProfitStatVO> resultList = new ArrayList<>();
        for (Shop shop : shops) {
            ProfitStatVO vo = new ProfitStatVO();
            vo.setShopId(shop.getId());
            vo.setShopName(shop.getName());
            vo.setProvince(shop.getProvince());
            vo.setCity(shop.getCity());
            vo.setDistrict(shop.getDistrict());
            
            long deviceCount = deviceMapper.selectCount(
                    new LambdaQueryWrapper<Device>().eq(Device::getShopId, shop.getId())
            );
            vo.setDeviceCount((int) deviceCount);
            
            List<OrderItem> shopItems = shopItemMap.getOrDefault(shop.getId(), Collections.emptyList());
            
            BigDecimal salesAmount = shopItems.stream()
                    .map(i -> i.getTotalAmount() != null ? i.getTotalAmount() : BigDecimal.ZERO)
                    .reduce(BigDecimal.ZERO, BigDecimal::add);
            vo.setSalesAmount(salesAmount.setScale(2, RoundingMode.HALF_UP));
            
            BigDecimal costAmount = shopItems.stream()
                    .map(i -> i.getCostPrice() != null && i.getQuantity() != null 
                            ? i.getCostPrice().multiply(BigDecimal.valueOf(i.getQuantity())) 
                            : BigDecimal.ZERO)
                    .reduce(BigDecimal.ZERO, BigDecimal::add);
            vo.setCostAmount(costAmount.setScale(2, RoundingMode.HALF_UP));
            
            BigDecimal grossProfit = salesAmount.subtract(costAmount);
            vo.setGrossProfit(grossProfit.setScale(2, RoundingMode.HALF_UP));
            
            BigDecimal grossProfitRate = salesAmount.compareTo(BigDecimal.ZERO) > 0 
                    ? grossProfit.divide(salesAmount, 4, RoundingMode.HALF_UP).multiply(new BigDecimal("100"))
                    : BigDecimal.ZERO;
            vo.setGrossProfitRate(grossProfitRate.setScale(2, RoundingMode.HALF_UP));
            
            vo.setRefundAmount(BigDecimal.ZERO);
            vo.setRefundRate(BigDecimal.ZERO);
            vo.setNetProfit(grossProfit);
            
            BigDecimal netProfitRate = salesAmount.compareTo(BigDecimal.ZERO) > 0 
                    ? grossProfit.divide(salesAmount, 4, RoundingMode.HALF_UP).multiply(new BigDecimal("100"))
                    : BigDecimal.ZERO;
            vo.setNetProfitRate(netProfitRate.setScale(2, RoundingMode.HALF_UP));
            
            BigDecimal deviceProfit = deviceCount > 0 
                    ? grossProfit.divide(BigDecimal.valueOf(deviceCount), 2, RoundingMode.HALF_UP)
                    : BigDecimal.ZERO;
            vo.setDeviceProfit(deviceProfit);
            
            long days = ChronoUnit.DAYS.between(startDate, endDate) + 1;
            BigDecimal dailyProfit = grossProfit.divide(BigDecimal.valueOf(days), 2, RoundingMode.HALF_UP);
            vo.setDailyProfit(dailyProfit);
            
            resultList.add(vo);
        }
        
        String sortBy = queryDTO.getSortBy() != null ? queryDTO.getSortBy() : "netProfit";
        boolean asc = "asc".equalsIgnoreCase(queryDTO.getSortOrder());
        
        Comparator<ProfitStatVO> comparator = null;
        switch (sortBy) {
            case "salesAmount":
                comparator = Comparator.comparing(ProfitStatVO::getSalesAmount);
                break;
            case "grossProfit":
                comparator = Comparator.comparing(ProfitStatVO::getGrossProfit);
                break;
            case "netProfit":
            default:
                comparator = Comparator.comparing(ProfitStatVO::getNetProfit);
                break;
        }
        
        if (!asc) {
            comparator = comparator.reversed();
        }
        resultList.sort(comparator);
        
        Page<ProfitStatVO> page = new Page<>(queryDTO.getPage(), queryDTO.getPageSize());
        int start = (queryDTO.getPage() - 1) * queryDTO.getPageSize();
        int end = Math.min(start + queryDTO.getPageSize(), resultList.size());
        
        page.setRecords(resultList.subList(start, end));
        page.setTotal(resultList.size());
        
        return page;
    }

    @Override
    public TrendDataVO getProfitTrend(StatisticsQueryDTO queryDTO) {
        TrendDataVO trend = new TrendDataVO();
        
        LocalDate startDate = parseDate(queryDTO.getStartDate(), LocalDate.now().minusDays(30));
        LocalDate endDate = parseDate(queryDTO.getEndDate(), LocalDate.now());
        
        LambdaQueryWrapper<OrderItem> wrapper = new LambdaQueryWrapper<>();
        wrapper.ge(OrderItem::getPayTime, startDate.atStartOfDay())
               .le(OrderItem::getPayTime, endDate.atTime(LocalTime.MAX));
        
        if (queryDTO.getShopId() != null) {
            wrapper.eq(OrderItem::getShopId, queryDTO.getShopId());
        }
        
        List<OrderItem> items = orderItemMapper.selectList(wrapper);
        
        Map<String, BigDecimal> dateProfit = new HashMap<>();
        for (OrderItem item : items) {
            String date = item.getPayTime().toLocalDate().toString();
            BigDecimal sales = item.getTotalAmount() != null ? item.getTotalAmount() : BigDecimal.ZERO;
            BigDecimal cost = item.getCostPrice() != null && item.getQuantity() != null 
                    ? item.getCostPrice().multiply(BigDecimal.valueOf(item.getQuantity())) 
                    : BigDecimal.ZERO;
            BigDecimal profit = sales.subtract(cost);
            dateProfit.merge(date, profit, BigDecimal::add);
        }
        
        List<String> dates = new ArrayList<>();
        List<BigDecimal> data = new ArrayList<>();
        
        long days = ChronoUnit.DAYS.between(startDate, endDate) + 1;
        for (int i = 0; i < days; i++) {
            LocalDate date = startDate.plusDays(i);
            dates.add(date.format(DateTimeFormatter.ofPattern("MM-dd")));
            data.add(dateProfit.getOrDefault(date.toString(), BigDecimal.ZERO).setScale(2, RoundingMode.HALF_UP));
        }
        
        trend.setDates(dates);
        
        SeriesDataVO series = new SeriesDataVO();
        series.setName("利润");
        series.setData(data);
        trend.setSeries(Collections.singletonList(series));
        
        return trend;
    }

    @Override
    public List<ProfitStatVO> getProfitWarning(StatisticsQueryDTO queryDTO) {
        StatisticsQueryDTO listQuery = new StatisticsQueryDTO();
        listQuery.setStartDate(queryDTO.getStartDate());
        listQuery.setEndDate(queryDTO.getEndDate());
        listQuery.setProvince(queryDTO.getProvince());
        listQuery.setCity(queryDTO.getCity());
        listQuery.setSortBy("netProfit");
        listQuery.setSortOrder("asc");
        listQuery.setPage(1);
        listQuery.setPageSize(20);
        
        IPage<ProfitStatVO> page = getProfitList(listQuery);
        
        String type = queryDTO.getType() != null ? queryDTO.getType() : "low";
        BigDecimal threshold = queryDTO.getThreshold() != null 
                ? queryDTO.getThreshold() 
                : new BigDecimal("10");
        
        return page.getRecords().stream()
                .filter(vo -> {
                    if ("negative".equals(type)) {
                        return vo.getNetProfit().compareTo(BigDecimal.ZERO) < 0;
                    } else {
                        return vo.getNetProfitRate().compareTo(threshold) < 0;
                    }
                })
                .collect(Collectors.toList());
    }

    @Override
    public Map<String, Object> getRepurchaseOverview(StatisticsQueryDTO queryDTO) {
        Map<String, Object> result = new HashMap<>();
        
        LocalDate startDate = parseDate(queryDTO.getStartDate(), LocalDate.now().minusDays(30));
        LocalDate endDate = parseDate(queryDTO.getEndDate(), LocalDate.now());
        
        LambdaQueryWrapper<Order> orderWrapper = new LambdaQueryWrapper<>();
        orderWrapper.eq(Order::getPayStatus, OrderConstants.PAY_STATUS_PAID)
                   .ge(Order::getPayTime, startDate.atStartOfDay())
                   .le(Order::getPayTime, endDate.atTime(LocalTime.MAX));
        
        if (queryDTO.getShopId() != null) {
            orderWrapper.eq(Order::getShopId, queryDTO.getShopId());
        }
        
        List<Order> orders = orderMapper.selectList(orderWrapper);
        
        Map<Long, List<Order>> userOrderMap = orders.stream()
                .filter(o -> o.getUserId() != null)
                .collect(Collectors.groupingBy(Order::getUserId));
        
        long totalUsers = userOrderMap.size();
        long newUsers = userOrderMap.values().stream()
                .filter(orderList -> orderList.size() == 1)
                .count();
        long repurchaseUsers = userOrderMap.values().stream()
                .filter(orderList -> orderList.size() >= 2)
                .count();
        
        result.put("totalUsers", totalUsers);
        result.put("newUsers", newUsers);
        result.put("repurchaseUsers", repurchaseUsers);
        
        BigDecimal repurchaseRate = totalUsers > 0 
                ? BigDecimal.valueOf(repurchaseUsers * 100.0 / totalUsers)
                : BigDecimal.ZERO;
        result.put("repurchaseRate", repurchaseRate.setScale(2, RoundingMode.HALF_UP));
        
        BigDecimal totalSales = orders.stream()
                .map(o -> o.getTotalAmount() != null ? o.getTotalAmount() : BigDecimal.ZERO)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        
        BigDecimal avgOrderAmount = orders.size() > 0 
                ? totalSales.divide(BigDecimal.valueOf(orders.size()), 2, RoundingMode.HALF_UP)
                : BigDecimal.ZERO;
        result.put("avgOrderAmount", avgOrderAmount);
        
        BigDecimal avgPurchaseCount = totalUsers > 0 
                ? BigDecimal.valueOf(orders.size()).divide(BigDecimal.valueOf(totalUsers), 2, RoundingMode.HALF_UP)
                : BigDecimal.ZERO;
        result.put("avgPurchaseCount", avgPurchaseCount);
        
        BigDecimal ltv = avgPurchaseCount.multiply(avgOrderAmount);
        result.put("ltv", ltv.setScale(2, RoundingMode.HALF_UP));
        
        return result;
    }

    @Override
    public Map<String, Object> getRepurchaseDistribution(StatisticsQueryDTO queryDTO) {
        Map<String, Object> result = new HashMap<>();
        
        LocalDate startDate = parseDate(queryDTO.getStartDate(), LocalDate.now().minusDays(30));
        LocalDate endDate = parseDate(queryDTO.getEndDate(), LocalDate.now());
        String type = queryDTO.getType() != null ? queryDTO.getType() : "layer";
        
        LambdaQueryWrapper<Order> orderWrapper = new LambdaQueryWrapper<>();
        orderWrapper.eq(Order::getPayStatus, OrderConstants.PAY_STATUS_PAID)
                   .ge(Order::getPayTime, startDate.atStartOfDay())
                   .le(Order::getPayTime, endDate.atTime(LocalTime.MAX));
        
        if (queryDTO.getShopId() != null) {
            orderWrapper.eq(Order::getShopId, queryDTO.getShopId());
        }
        
        List<Order> orders = orderMapper.selectList(orderWrapper);
        
        Map<Long, List<Order>> userOrderMap = orders.stream()
                .filter(o -> o.getUserId() != null)
                .collect(Collectors.groupingBy(Order::getUserId));
        
        if ("interval".equals(type)) {
            Map<String, Integer> intervalDistribution = new LinkedHashMap<>();
            intervalDistribution.put("1天内", 0);
            intervalDistribution.put("1-3天", 0);
            intervalDistribution.put("3-7天", 0);
            intervalDistribution.put("7-14天", 0);
            intervalDistribution.put("14-30天", 0);
            intervalDistribution.put("30天以上", 0);
            
            for (List<Order> userOrders : userOrderMap.values()) {
                if (userOrders.size() >= 2) {
                    userOrders.sort(Comparator.comparing(Order::getPayTime));
                    for (int i = 1; i < userOrders.size(); i++) {
                        long days = ChronoUnit.DAYS.between(
                                userOrders.get(i-1).getPayTime().toLocalDate(),
                                userOrders.get(i).getPayTime().toLocalDate()
                        );
                        
                        if (days < 1) intervalDistribution.merge("1天内", 1, Integer::sum);
                        else if (days < 3) intervalDistribution.merge("1-3天", 1, Integer::sum);
                        else if (days < 7) intervalDistribution.merge("3-7天", 1, Integer::sum);
                        else if (days < 14) intervalDistribution.merge("7-14天", 1, Integer::sum);
                        else if (days < 30) intervalDistribution.merge("14-30天", 1, Integer::sum);
                        else intervalDistribution.merge("30天以上", 1, Integer::sum);
                    }
                }
            }
            
            result.put("distribution", intervalDistribution);
        } else {
            Map<String, Integer> layerDistribution = new LinkedHashMap<>();
            
            int newUserCount = 0;
            int activeUserCount = 0;
            int loyalUserCount = 0;
            int churnUserCount = 0;
            
            LocalDate now = LocalDate.now();
            
            for (Map.Entry<Long, List<Order>> entry : userOrderMap.entrySet()) {
                List<Order> userOrders = entry.getValue();
                int totalOrders = userOrders.size();
                
                LocalDate lastOrderDate = userOrders.stream()
                        .map(o -> o.getPayTime().toLocalDate())
                        .max(LocalDate::compareTo)
                        .orElse(now);
                
                long daysSinceLastOrder = ChronoUnit.DAYS.between(lastOrderDate, now);
                
                if (totalOrders == 1) {
                    newUserCount++;
                } else if (totalOrders >= 5) {
                    loyalUserCount++;
                } else if (daysSinceLastOrder > 60) {
                    churnUserCount++;
                } else {
                    activeUserCount++;
                }
            }
            
            layerDistribution.put("新用户", newUserCount);
            layerDistribution.put("活跃用户", activeUserCount);
            layerDistribution.put("忠诚用户", loyalUserCount);
            layerDistribution.put("流失用户", churnUserCount);
            
            result.put("distribution", layerDistribution);
        }
        
        return result;
    }

    @Override
    public TrendDataVO getRepurchaseTrend(StatisticsQueryDTO queryDTO) {
        TrendDataVO trend = new TrendDataVO();
        
        LocalDate startDate = parseDate(queryDTO.getStartDate(), LocalDate.now().minusDays(30));
        LocalDate endDate = parseDate(queryDTO.getEndDate(), LocalDate.now());
        
        List<String> dates = new ArrayList<>();
        List<BigDecimal> data = new ArrayList<>();
        
        long days = ChronoUnit.DAYS.between(startDate, endDate) + 1;
        for (int i = 0; i < days; i++) {
            LocalDate date = startDate.plusDays(i);
            dates.add(date.format(DateTimeFormatter.ofPattern("MM-dd")));
            
            LocalDateTime dayStart = date.atStartOfDay();
            LocalDateTime dayEnd = date.atTime(LocalTime.MAX);
            
            LambdaQueryWrapper<Order> dayWrapper = new LambdaQueryWrapper<>();
            dayWrapper.eq(Order::getPayStatus, OrderConstants.PAY_STATUS_PAID)
                     .ge(Order::getPayTime, dayStart)
                     .le(Order::getPayTime, dayEnd);
            
            if (queryDTO.getShopId() != null) {
                dayWrapper.eq(Order::getShopId, queryDTO.getShopId());
            }
            
            List<Order> dayOrders = orderMapper.selectList(dayWrapper);
            
            Map<Long, Long> userOrderCount = dayOrders.stream()
                    .filter(o -> o.getUserId() != null)
                    .collect(Collectors.groupingBy(Order::getUserId, Collectors.counting()));
            
            long dayRepurchaseUsers = userOrderCount.values().stream()
                    .filter(count -> count >= 2)
                    .count();
            
            BigDecimal repurchaseRate = userOrderCount.size() > 0 
                    ? BigDecimal.valueOf(dayRepurchaseUsers * 100.0 / userOrderCount.size())
                    : BigDecimal.ZERO;
            
            data.add(repurchaseRate.setScale(2, RoundingMode.HALF_UP));
        }
        
        trend.setDates(dates);
        
        SeriesDataVO series = new SeriesDataVO();
        series.setName("复购率(%)");
        series.setData(data);
        trend.setSeries(Collections.singletonList(series));
        
        return trend;
    }

    @Override
    public IPage<RepurchaseStatVO> getRepurchaseUsers(StatisticsQueryDTO queryDTO) {
        queryDTO.validate();
        
        LocalDate startDate = parseDate(queryDTO.getStartDate(), LocalDate.now().minusDays(30));
        LocalDate endDate = parseDate(queryDTO.getEndDate(), LocalDate.now());
        
        LambdaQueryWrapper<Order> orderWrapper = new LambdaQueryWrapper<>();
        orderWrapper.eq(Order::getPayStatus, OrderConstants.PAY_STATUS_PAID)
                   .ge(Order::getPayTime, startDate.atStartOfDay())
                   .le(Order::getPayTime, endDate.atTime(LocalTime.MAX));
        
        if (queryDTO.getShopId() != null) {
            orderWrapper.eq(Order::getShopId, queryDTO.getShopId());
        }
        
        List<Order> orders = orderMapper.selectList(orderWrapper);
        
        Map<Long, List<Order>> userOrderMap = orders.stream()
                .filter(o -> o.getUserId() != null)
                .collect(Collectors.groupingBy(Order::getUserId));
        
        List<RepurchaseStatVO> resultList = new ArrayList<>();
        
        for (Map.Entry<Long, List<Order>> entry : userOrderMap.entrySet()) {
            Long userId = entry.getKey();
            List<Order> userOrders = entry.getValue();
            
            if (queryDTO.getMinOrderCount() != null && userOrders.size() < queryDTO.getMinOrderCount()) {
                continue;
            }
            
            RepurchaseStatVO vo = new RepurchaseStatVO();
            vo.setUserId(userId);
            vo.setOrderCount(userOrders.size());
            
            User user = userMapper.selectById(userId);
            if (user != null) {
                vo.setNickname(user.getNickname());
                vo.setPhone(user.getPhone());
            }
            
            userOrders.sort(Comparator.comparing(Order::getPayTime));
            
            vo.setFirstOrderDate(userOrders.get(0).getPayTime().toLocalDate());
            vo.setLastOrderDate(userOrders.get(userOrders.size() - 1).getPayTime().toLocalDate());
            
            BigDecimal totalAmount = userOrders.stream()
                    .map(o -> o.getTotalAmount() != null ? o.getTotalAmount() : BigDecimal.ZERO)
                    .reduce(BigDecimal.ZERO, BigDecimal::add);
            vo.setTotalAmount(totalAmount.setScale(2, RoundingMode.HALF_UP));
            
            BigDecimal avgOrderAmount = userOrders.size() > 0 
                    ? totalAmount.divide(BigDecimal.valueOf(userOrders.size()), 2, RoundingMode.HALF_UP)
                    : BigDecimal.ZERO;
            vo.setAvgOrderAmount(avgOrderAmount);
            
            if (userOrders.size() >= 2) {
                long avgDays = 0;
                for (int i = 1; i < userOrders.size(); i++) {
                    avgDays += ChronoUnit.DAYS.between(
                            userOrders.get(i-1).getPayTime().toLocalDate(),
                            userOrders.get(i).getPayTime().toLocalDate()
                    );
                }
                vo.setRepurchaseDays((int) (avgDays / (userOrders.size() - 1)));
            } else {
                vo.setRepurchaseDays(0);
            }
            
            String userLayer = determineUserLayer(userOrders);
            vo.setUserLayer(userLayer);
            vo.setUserLayerLabel(getUserLayerLabel(userLayer));
            
            resultList.add(vo);
        }
        
        if (queryDTO.getUserLayer() != null) {
            resultList = resultList.stream()
                    .filter(vo -> queryDTO.getUserLayer().equals(vo.getUserLayer()))
                    .collect(Collectors.toList());
        }
        
        String sortBy = queryDTO.getSortBy() != null ? queryDTO.getSortBy() : "totalAmount";
        boolean asc = "asc".equalsIgnoreCase(queryDTO.getSortOrder());
        
        Comparator<RepurchaseStatVO> comparator = null;
        switch (sortBy) {
            case "orderCount":
                comparator = Comparator.comparing(RepurchaseStatVO::getOrderCount);
                break;
            case "avgOrderAmount":
                comparator = Comparator.comparing(RepurchaseStatVO::getAvgOrderAmount);
                break;
            case "totalAmount":
            default:
                comparator = Comparator.comparing(RepurchaseStatVO::getTotalAmount);
                break;
        }
        
        if (!asc) {
            comparator = comparator.reversed();
        }
        resultList.sort(comparator);
        
        Page<RepurchaseStatVO> page = new Page<>(queryDTO.getPage(), queryDTO.getPageSize());
        int start = (queryDTO.getPage() - 1) * queryDTO.getPageSize();
        int end = Math.min(start + queryDTO.getPageSize(), resultList.size());
        
        page.setRecords(resultList.subList(start, end));
        page.setTotal(resultList.size());
        
        return page;
    }

    @Override
    public byte[] exportStatistics(StatisticsQueryDTO queryDTO) {
        return new byte[0];
    }
    
    private LocalDate parseDate(String dateStr, LocalDate defaultDate) {
        if (dateStr == null || dateStr.isEmpty()) {
            return defaultDate;
        }
        try {
            return LocalDate.parse(dateStr);
        } catch (Exception e) {
            return defaultDate;
        }
    }
    
    private String determineUserLayer(List<Order> userOrders) {
        int totalOrders = userOrders.size();
        LocalDate now = LocalDate.now();
        
        LocalDate lastOrderDate = userOrders.stream()
                .map(o -> o.getPayTime().toLocalDate())
                .max(LocalDate::compareTo)
                .orElse(now);
        
        long daysSinceLastOrder = ChronoUnit.DAYS.between(lastOrderDate, now);
        
        if (totalOrders == 1) {
            return UserTag.NEW.getCode();
        } else if (totalOrders >= 5) {
            return "loyal";
        } else if (daysSinceLastOrder > 60) {
            return "churn";
        } else {
            return "active";
        }
    }
    
    private String getUserLayerLabel(String layer) {
        switch (layer) {
            case "new": return "新用户";
            case "active": return "活跃用户";
            case "loyal": return "忠诚用户";
            case "churn": return "流失用户";
            default: return "未知";
        }
    }
}
