package com.starry.admin.modules.statistics.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.starry.admin.common.exception.ServiceException;
import com.starry.admin.modules.clerk.module.entity.PlayClerkLevelInfoEntity;
import com.starry.admin.modules.clerk.module.entity.PlayClerkUserInfoEntity;
import com.starry.admin.modules.clerk.service.IPlayClerkLevelInfoService;
import com.starry.admin.modules.clerk.service.IPlayClerkUserInfoService;
import com.starry.admin.modules.order.module.constant.OrderConstant;
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
import com.starry.admin.modules.order.service.IPlayOrderInfoService;
import com.starry.admin.modules.personnel.module.entity.PlayPersonnelGroupInfoEntity;
import com.starry.admin.modules.personnel.service.IPlayPersonnelGroupInfoService;
import com.starry.admin.modules.statistics.module.enums.ClerkPerformanceSortField;
import com.starry.admin.modules.statistics.module.enums.SortDirection;
import com.starry.admin.modules.statistics.module.vo.ClerkPerformanceDetailCompositionVo;
import com.starry.admin.modules.statistics.module.vo.ClerkPerformanceDetailQueryVo;
import com.starry.admin.modules.statistics.module.vo.ClerkPerformanceDetailResponseVo;
import com.starry.admin.modules.statistics.module.vo.ClerkPerformanceOverviewQueryVo;
import com.starry.admin.modules.statistics.module.vo.ClerkPerformanceOverviewResponseVo;
import com.starry.admin.modules.statistics.module.vo.ClerkPerformanceOverviewSummaryVo;
import com.starry.admin.modules.statistics.module.vo.ClerkPerformanceProfileVo;
import com.starry.admin.modules.statistics.module.vo.ClerkPerformanceSnapshotVo;
import com.starry.admin.modules.statistics.module.vo.ClerkPerformanceTrendPointVo;
import com.starry.admin.modules.statistics.module.vo.PlayClerkPerformanceInfoReturnVo;
import com.starry.admin.modules.statistics.service.IPlayClerkPerformanceService;
import com.starry.admin.modules.withdraw.mapper.EarningsLineMapper;
import com.starry.admin.utils.DateRangeUtils;
import com.starry.admin.utils.SecurityUtils;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;

/**
 * 店员业绩数据服务。
 */
@Service
public class PlayClerkPerformanceServiceImpl implements IPlayClerkPerformanceService {

    @Resource
    private IPlayClerkUserInfoService clerkUserInfoService;

    @Resource
    private IPlayOrderInfoService playOrderInfoService;

    @Resource
    private IPlayClerkLevelInfoService playClerkLevelInfoService;

    @Resource
    private IPlayPersonnelGroupInfoService playPersonnelGroupInfoService;

    @Resource
    private EarningsLineMapper earningsLineMapper;

    @Override
    public PlayClerkPerformanceInfoReturnVo getClerkPerformanceInfo(PlayClerkUserInfoEntity userInfo,
            List<PlayOrderInfoEntity> orderInfoEntities, List<PlayClerkLevelInfoEntity> clerkLevelInfoEntities,
            List<PlayPersonnelGroupInfoEntity> groupInfoEntities, String startTime, String endTime) {

        Set<String> customIds = new HashSet<>();
        int orderContinueNumber = 0;
        int orderRefundNumber = 0;
        int ordersExpiredNumber = 0;
        int completedOrderCount = 0;
        BigDecimal finalAmount = BigDecimal.ZERO;
        BigDecimal orderFirstAmount = BigDecimal.ZERO;
        BigDecimal orderTotalAmount = BigDecimal.ZERO;
        BigDecimal orderRewardAmount = BigDecimal.ZERO;
        BigDecimal orderRefundAmount = BigDecimal.ZERO;

        for (PlayOrderInfoEntity orderInfoEntity : orderInfoEntities) {
            if (!isCompletedOrder(orderInfoEntity)) {
                continue;
            }
            completedOrderCount++;
            customIds.add(orderInfoEntity.getPurchaserBy());
            finalAmount = finalAmount.add(defaultZero(orderInfoEntity.getFinalAmount()));
            if (OrderConstant.YesNoFlag.YES.getCode().equals(orderInfoEntity.getFirstOrder())) {
                orderFirstAmount = orderFirstAmount.add(defaultZero(orderInfoEntity.getFinalAmount()));
            } else {
                orderContinueNumber++;
                orderTotalAmount = orderTotalAmount.add(defaultZero(orderInfoEntity.getFinalAmount()));
            }
            if (OrderConstant.PlaceType.REWARD.getCode().equals(orderInfoEntity.getPlaceType())) {
                orderRewardAmount = orderRewardAmount.add(defaultZero(orderInfoEntity.getFinalAmount()));
            }
            if (OrderConstant.OrderRefundFlag.REFUNDED.getCode().equals(orderInfoEntity.getRefundType())) {
                orderRefundNumber++;
                orderRefundAmount = orderRefundAmount.add(defaultZero(orderInfoEntity.getRefundAmount()));
            }
            if (OrderConstant.OrdersExpiredState.EXPIRED.getCode().equals(orderInfoEntity.getOrdersExpiredState())) {
                ordersExpiredNumber++;
            }
        }

        BigDecimal estimatedRevenue =
                calculateEarningsAmount(userInfo.getId(), orderInfoEntities, startTime, endTime);

        PlayClerkPerformanceInfoReturnVo returnVo = new PlayClerkPerformanceInfoReturnVo();
        returnVo.setClerkId(userInfo.getId());
        returnVo.setClerkNickname(userInfo.getNickname());
        returnVo.setClerkSex(userInfo.getSex());
        for (PlayClerkLevelInfoEntity infoEntity : clerkLevelInfoEntities) {
            if (Objects.equals(infoEntity.getId(), userInfo.getLevelId())) {
                returnVo.setLevelName(infoEntity.getName());
            }
        }
        for (PlayPersonnelGroupInfoEntity infoEntity : groupInfoEntities) {
            if (Objects.equals(infoEntity.getId(), userInfo.getGroupId())) {
                returnVo.setGroupName(infoEntity.getGroupName());
            }
        }
        returnVo.setOrderNumber(completedOrderCount);
        returnVo.setOrderContinueNumber(orderContinueNumber);
        returnVo.setOrderRefundNumber(orderRefundNumber);
        returnVo.setOrdersExpiredNumber(ordersExpiredNumber);
        returnVo.setFinalAmount(finalAmount);
        returnVo.setOrderFirstAmount(orderFirstAmount);
        returnVo.setOrderTotalAmount(orderTotalAmount);
        returnVo.setOrderRewardAmount(orderRewardAmount);
        returnVo.setOrderRefundAmount(orderRefundAmount);
        returnVo.setCustomNumber(customIds.size());
        returnVo.setEstimatedRevenue(estimatedRevenue);
        return returnVo;
    }

    @Override
    public ClerkPerformanceOverviewResponseVo queryOverview(ClerkPerformanceOverviewQueryVo vo) {
        DateRange range = resolveDateRange(vo.getEndOrderTime());
        List<PlayClerkUserInfoEntity> clerks = loadAccessibleClerks(vo);
        ClerkPerformanceOverviewResponseVo responseVo = new ClerkPerformanceOverviewResponseVo();
        if (CollectionUtil.isEmpty(clerks)) {
            responseVo.setSummary(new ClerkPerformanceOverviewSummaryVo());
            responseVo.setRankings(Collections.emptyList());
            responseVo.setTotal(0);
            return responseVo;
        }
        Map<String, String> levelNameMap = playClerkLevelInfoService.selectAll().stream().collect(
                Collectors.toMap(PlayClerkLevelInfoEntity::getId, PlayClerkLevelInfoEntity::getName, (a, b) -> a));
        Map<String, String> groupNameMap = playPersonnelGroupInfoService.selectAll().stream().collect(
                Collectors.toMap(PlayPersonnelGroupInfoEntity::getId, PlayPersonnelGroupInfoEntity::getGroupName,
                        (a, b) -> a));
        List<ClerkPerformanceSnapshotVo> snapshots = new ArrayList<>(clerks.size());
        for (PlayClerkUserInfoEntity clerk : clerks) {
            List<PlayOrderInfoEntity> orders = playOrderInfoService.clerkSelectOrderInfoList(clerk.getId(),
                    range.startTime, range.endTime);
            snapshots.add(buildSnapshot(clerk, orders, levelNameMap, groupNameMap, range.startTime, range.endTime));
        }
        int total = snapshots.size();
        ClerkPerformanceOverviewSummaryVo summary = aggregateSummary(snapshots);
        List<ClerkPerformanceSnapshotVo> sorted = new ArrayList<>(snapshots);
        applySortAndRank(sorted, vo.getSortField(), vo.getSortDirection());
        if (vo.getLimit() != null && vo.getLimit() > 0 && vo.getLimit() < sorted.size()) {
            sorted = new ArrayList<>(sorted.subList(0, vo.getLimit()));
            applySequentialRank(sorted);
        }
        responseVo.setSummary(Boolean.TRUE.equals(vo.getIncludeSummary()) ? summary : null);
        responseVo.setRankings(Boolean.TRUE.equals(vo.getIncludeRankings()) ? sorted : Collections.emptyList());
        responseVo.setTotal(total);
        return responseVo;
    }

    @Override
    public ClerkPerformanceDetailResponseVo queryDetail(ClerkPerformanceDetailQueryVo vo) {
        DateRange range = resolveDateRange(vo.getEndOrderTime());
        PlayClerkUserInfoEntity clerk = clerkUserInfoService.getById(vo.getClerkId());
        if (clerk == null) {
            throw new ServiceException("店员不存在");
        }
        List<String> accessibleIds =
                playPersonnelGroupInfoService.getValidClerkIdList(SecurityUtils.getLoginUser(), null);
        if (CollectionUtil.isEmpty(accessibleIds) || !accessibleIds.contains(clerk.getId())) {
            throw new ServiceException("无权查看该店员业绩");
        }
        Map<String, String> levelNameMap = playClerkLevelInfoService.selectAll().stream().collect(
                Collectors.toMap(PlayClerkLevelInfoEntity::getId, PlayClerkLevelInfoEntity::getName, (a, b) -> a));
        Map<String, String> groupNameMap = playPersonnelGroupInfoService.selectAll().stream().collect(
                Collectors.toMap(PlayPersonnelGroupInfoEntity::getId, PlayPersonnelGroupInfoEntity::getGroupName,
                        (a, b) -> a));
        List<PlayOrderInfoEntity> orders = playOrderInfoService.clerkSelectOrderInfoList(clerk.getId(),
                range.startTime, range.endTime);
        ClerkPerformanceSnapshotVo snapshot =
                buildSnapshot(clerk, orders, levelNameMap, groupNameMap, range.startTime, range.endTime);
        ClerkPerformanceDetailResponseVo responseVo = new ClerkPerformanceDetailResponseVo();
        responseVo.setProfile(buildProfile(clerk, levelNameMap, groupNameMap));
        responseVo.setSnapshot(snapshot);
        responseVo.setComposition(buildComposition(snapshot));
        if (Boolean.TRUE.equals(vo.getIncludeTrend())) {
            responseVo.setTrend(buildTrend(orders, range,
                    vo.getTrendDays() == null || vo.getTrendDays() <= 0 ? 7 : vo.getTrendDays()));
            responseVo.setTrendDimension("DAY");
        } else {
            responseVo.setTrend(Collections.emptyList());
        }
        return responseVo;
    }

    private ClerkPerformanceProfileVo buildProfile(PlayClerkUserInfoEntity clerk, Map<String, String> levelNameMap,
            Map<String, String> groupNameMap) {
        ClerkPerformanceProfileVo profile = new ClerkPerformanceProfileVo();
        profile.setClerkId(clerk.getId());
        profile.setNickname(clerk.getNickname());
        profile.setAvatar(clerk.getAvatar());
        profile.setSex(clerk.getSex());
        profile.setGroupName(groupNameMap.getOrDefault(clerk.getGroupId(), ""));
        profile.setLevelName(levelNameMap.getOrDefault(clerk.getLevelId(), ""));
        profile.setListingState(clerk.getListingState());
        profile.setOnlineState(clerk.getOnlineState());
        profile.setRole(null);
        return profile;
    }

    private List<ClerkPerformanceTrendPointVo> buildTrend(List<PlayOrderInfoEntity> orders, DateRange range,
            int trendDays) {
        List<PlayOrderInfoEntity> completedOrders = orders.stream()
                .filter(this::isCompletedOrder)
                .collect(Collectors.toList());
        if (CollectionUtil.isEmpty(completedOrders)) {
            return buildEmptyTrend(range, trendDays);
        }
        LocalDate end = range.endDate;
        LocalDate start = end.minusDays(trendDays - 1);
        if (start.isBefore(range.startDate)) {
            start = range.startDate;
        }
        Map<LocalDate, List<PlayOrderInfoEntity>> grouped = completedOrders.stream()
                .filter(order -> order.getPurchaserTime() != null)
                .collect(Collectors.groupingBy(order -> order.getPurchaserTime().toLocalDate()));
        List<ClerkPerformanceTrendPointVo> points = new ArrayList<>();
        LocalDate cursor = start;
        while (!cursor.isAfter(end)) {
            List<PlayOrderInfoEntity> dayOrders = grouped.getOrDefault(cursor, Collections.emptyList());
            points.add(buildTrendPoint(cursor, dayOrders));
            cursor = cursor.plusDays(1);
        }
        return points;
    }

    private List<ClerkPerformanceTrendPointVo> buildEmptyTrend(DateRange range, int trendDays) {
        List<ClerkPerformanceTrendPointVo> points = new ArrayList<>();
        LocalDate end = range.endDate;
        LocalDate start = end.minusDays(trendDays - 1);
        if (start.isBefore(range.startDate)) {
            start = range.startDate;
        }
        LocalDate cursor = start;
        while (!cursor.isAfter(end)) {
            points.add(buildTrendPoint(cursor, Collections.emptyList()));
            cursor = cursor.plusDays(1);
        }
        return points;
    }

    private ClerkPerformanceTrendPointVo buildTrendPoint(LocalDate date, List<PlayOrderInfoEntity> orders) {
        ClerkPerformanceTrendPointVo point = new ClerkPerformanceTrendPointVo();
        point.setDate(date);
        BigDecimal gmv = BigDecimal.ZERO;
        BigDecimal continuedAmount = BigDecimal.ZERO;
        BigDecimal firstAmount = BigDecimal.ZERO;
        int continuedCount = 0;
        for (PlayOrderInfoEntity order : orders) {
            BigDecimal finalAmount = defaultZero(order.getFinalAmount());
            gmv = gmv.add(finalAmount);
            if (OrderConstant.YesNoFlag.YES.getCode().equals(order.getFirstOrder())) {
                firstAmount = firstAmount.add(finalAmount);
            } else {
                continuedAmount = continuedAmount.add(finalAmount);
                continuedCount++;
            }
        }
        point.setGmv(gmv);
        point.setOrderCount(orders.size());
        point.setContinuedAmount(continuedAmount);
        point.setFirstAmount(firstAmount);
        point.setContinuedRate(calcPercentage(continuedCount, orders.size()));
        return point;
    }

    private ClerkPerformanceDetailCompositionVo buildComposition(ClerkPerformanceSnapshotVo snapshot) {
        ClerkPerformanceDetailCompositionVo composition = new ClerkPerformanceDetailCompositionVo();
        int orderTotal = snapshot.getOrderCount();
        BigDecimal gmv = snapshot.getGmv();
        List<ClerkPerformanceDetailCompositionVo.CompositionSlice> orderSlices = new ArrayList<>();
        orderSlices.add(createSlice("FIRST_ORDER", "首单", snapshot.getFirstOrderCount(), orderTotal,
                snapshot.getFirstOrderAmount(), gmv));
        orderSlices.add(createSlice("CONTINUED_ORDER", "续单", snapshot.getContinuedOrderCount(), orderTotal,
                snapshot.getContinuedOrderAmount(), gmv));
        orderSlices.add(createSlice("REFUND_ORDER", "退款", snapshot.getRefundOrderCount(), orderTotal,
                snapshot.getRefundAmount(), gmv));
        orderSlices.add(createSlice("EXPIRED_ORDER", "超时", snapshot.getExpiredOrderCount(), orderTotal,
                BigDecimal.ZERO, gmv));
        composition.setOrderComposition(orderSlices);
        List<ClerkPerformanceDetailCompositionVo.CompositionSlice> amountSlices = new ArrayList<>();
        amountSlices.add(createSlice("FIRST_AMOUNT", "首单金额", 0, orderTotal, snapshot.getFirstOrderAmount(), gmv));
        amountSlices.add(
                createSlice("CONTINUED_AMOUNT", "续单金额", 0, orderTotal, snapshot.getContinuedOrderAmount(), gmv));
        amountSlices.add(createSlice("REWARD_AMOUNT", "打赏金额", 0, orderTotal, snapshot.getRewardAmount(), gmv));
        amountSlices.add(createSlice("REFUND_AMOUNT", "退款金额", 0, orderTotal, snapshot.getRefundAmount(), gmv));
        composition.setAmountComposition(amountSlices);
        int continuedCustomers = snapshot.getContinuedUserCount();
        int totalCustomers = snapshot.getUserCount();
        int newCustomers = Math.max(totalCustomers - continuedCustomers, 0);
        List<ClerkPerformanceDetailCompositionVo.CompositionSlice> customerSlices = new ArrayList<>();
        customerSlices.add(createSlice("NEW_CUSTOMER", "新顾客", newCustomers, totalCustomers, BigDecimal.ZERO, gmv));
        customerSlices
                .add(createSlice("CONTINUED_CUSTOMER", "复购顾客", continuedCustomers, totalCustomers, BigDecimal.ZERO,
                        gmv));
        composition.setCustomerComposition(customerSlices);
        return composition;
    }

    private ClerkPerformanceDetailCompositionVo.CompositionSlice createSlice(String key, String label, int count,
            int totalCount, BigDecimal amount, BigDecimal totalAmount) {
        ClerkPerformanceDetailCompositionVo.CompositionSlice slice =
                new ClerkPerformanceDetailCompositionVo.CompositionSlice();
        slice.setKey(key);
        slice.setLabel(label);
        slice.setCount(count);
        slice.setCountRatio(calcPercentage(count, totalCount));
        slice.setAmount(amount);
        slice.setAmountRatio(calcPercentage(amount, totalAmount));
        return slice;
    }

    private void applySortAndRank(List<ClerkPerformanceSnapshotVo> snapshots, ClerkPerformanceSortField sortField,
            SortDirection direction) {
        if (CollectionUtil.isEmpty(snapshots)) {
            return;
        }
        Comparator<ClerkPerformanceSnapshotVo> comparator;
        if (sortField == null) {
            sortField = ClerkPerformanceSortField.GMV;
        }
        switch (sortField) {
            case ORDER_COUNT:
                comparator = Comparator.comparing(ClerkPerformanceSnapshotVo::getOrderCount, Comparator.nullsLast(Integer::compareTo));
                break;
            case CONTINUE_RATE:
                comparator = Comparator.comparing(ClerkPerformanceSnapshotVo::getContinuedRate, Comparator.nullsLast(BigDecimal::compareTo));
                break;
            case ESTIMATED_REVENUE:
                comparator = Comparator.comparing(ClerkPerformanceSnapshotVo::getEstimatedRevenue, Comparator.nullsLast(BigDecimal::compareTo));
                break;
            case GMV:
            default:
                comparator = Comparator.comparing(ClerkPerformanceSnapshotVo::getGmv, Comparator.nullsLast(BigDecimal::compareTo));
                break;
        }
        comparator = comparator.thenComparing(ClerkPerformanceSnapshotVo::getClerkNickname,
                Comparator.nullsFirst(String::compareTo));
        if (direction == null) {
            direction = SortDirection.DEFAULT;
        }
        if (direction == SortDirection.DESC) {
            comparator = comparator.reversed();
        }
        snapshots.sort(comparator);
        applySequentialRank(snapshots);
    }

    private void applySequentialRank(List<ClerkPerformanceSnapshotVo> snapshots) {
        for (int i = 0; i < snapshots.size(); i++) {
            snapshots.get(i).setRank(i + 1);
        }
    }

    private ClerkPerformanceOverviewSummaryVo aggregateSummary(List<ClerkPerformanceSnapshotVo> snapshots) {
        ClerkPerformanceOverviewSummaryVo summary = new ClerkPerformanceOverviewSummaryVo();
        if (CollectionUtil.isEmpty(snapshots)) {
            return summary;
        }
        BigDecimal totalGmv = BigDecimal.ZERO;
        BigDecimal continuedAmount = BigDecimal.ZERO;
        BigDecimal refundAmount = BigDecimal.ZERO;
        BigDecimal estimatedRevenue = BigDecimal.ZERO;
        int orderTotal = 0;
        int firstOrderTotal = 0;
        int continuedOrderTotal = 0;
        int refundOrderTotal = 0;
        int userTotal = 0;
        int continuedUserTotal = 0;
        for (ClerkPerformanceSnapshotVo snapshot : snapshots) {
            totalGmv = totalGmv.add(defaultZero(snapshot.getGmv()));
            continuedAmount = continuedAmount.add(defaultZero(snapshot.getContinuedOrderAmount()));
            refundAmount = refundAmount.add(defaultZero(snapshot.getRefundAmount()));
            estimatedRevenue = estimatedRevenue.add(defaultZero(snapshot.getEstimatedRevenue()));
            orderTotal += snapshot.getOrderCount();
            firstOrderTotal += snapshot.getFirstOrderCount();
            continuedOrderTotal += snapshot.getContinuedOrderCount();
            refundOrderTotal += snapshot.getRefundOrderCount();
            userTotal += snapshot.getUserCount();
            continuedUserTotal += snapshot.getContinuedUserCount();
        }
        summary.setTotalGmv(totalGmv);
        summary.setTotalOrderCount(orderTotal);
        summary.setTotalFirstOrderCount(firstOrderTotal);
        summary.setTotalContinuedOrderCount(continuedOrderTotal);
        summary.setTotalRefundOrderCount(refundOrderTotal);
        summary.setTotalEstimatedRevenue(estimatedRevenue);
        summary.setTotalUserCount(userTotal);
        summary.setTotalContinuedUserCount(continuedUserTotal);
        summary.setContinuedRate(calcPercentage(continuedOrderTotal, orderTotal));
        summary.setContinuedAmountRate(calcPercentage(continuedAmount, totalGmv));
        summary.setRefundRate(calcPercentage(refundOrderTotal, orderTotal));
        summary.setAverageTicketPrice(calcAverage(totalGmv, orderTotal));
        summary.setContinuedCustomerRate(calcPercentage(continuedUserTotal, userTotal));
        return summary;
    }

    private BigDecimal calculateEarningsAmount(String clerkId, List<PlayOrderInfoEntity> orders, String startTime,
            String endTime) {
        if (StrUtil.isBlank(clerkId) || CollectionUtil.isEmpty(orders)) {
            return BigDecimal.ZERO;
        }
        List<String> orderIds = orders.stream()
                .filter(this::isCompletedOrder)
                .map(PlayOrderInfoEntity::getId)
                .filter(StrUtil::isNotBlank)
                .collect(Collectors.toList());
        if (CollectionUtil.isEmpty(orderIds)) {
            return BigDecimal.ZERO;
        }
        String normalizedStart = DateRangeUtils.normalizeStartOptional(startTime);
        String normalizedEnd = DateRangeUtils.normalizeEndOptional(endTime);
        BigDecimal sum = earningsLineMapper.sumAmountByClerkAndOrderIds(clerkId, orderIds, normalizedStart,
                normalizedEnd);
        return defaultZero(sum);
    }

    private ClerkPerformanceSnapshotVo buildSnapshot(PlayClerkUserInfoEntity clerk, List<PlayOrderInfoEntity> orders,
            Map<String, String> levelNameMap, Map<String, String> groupNameMap, String startTime, String endTime) {
        ClerkPerformanceSnapshotVo snapshot = new ClerkPerformanceSnapshotVo();
        snapshot.setClerkId(clerk.getId());
        snapshot.setClerkNickname(clerk.getNickname());
        snapshot.setAvatar(clerk.getAvatar());
        snapshot.setSex(clerk.getSex());
        snapshot.setGroupName(groupNameMap.getOrDefault(clerk.getGroupId(), ""));
        snapshot.setLevelName(levelNameMap.getOrDefault(clerk.getLevelId(), ""));
        BigDecimal gmv = BigDecimal.ZERO;
        BigDecimal firstAmount = BigDecimal.ZERO;
        BigDecimal continuedAmount = BigDecimal.ZERO;
        BigDecimal rewardAmount = BigDecimal.ZERO;
        BigDecimal refundAmount = BigDecimal.ZERO;
        int firstCount = 0;
        int continuedCount = 0;
        int refundCount = 0;
        int expiredCount = 0;
        Map<String, Integer> userOrderMap = new HashMap<>();
        int orderCount = 0;
        for (PlayOrderInfoEntity order : orders) {
            if (!isCompletedOrder(order)) {
                continue;
            }
            orderCount++;
            BigDecimal finalAmount = defaultZero(order.getFinalAmount());
            gmv = gmv.add(finalAmount);
            userOrderMap.merge(order.getPurchaserBy(), 1, Integer::sum);
            if (OrderConstant.YesNoFlag.YES.getCode().equals(order.getFirstOrder())) {
                firstCount++;
                firstAmount = firstAmount.add(finalAmount);
            } else {
                continuedCount++;
                continuedAmount = continuedAmount.add(finalAmount);
            }
            if (OrderConstant.PlaceType.REWARD.getCode().equals(order.getPlaceType())) {
                rewardAmount = rewardAmount.add(finalAmount);
            }
            if (OrderConstant.OrderRefundFlag.REFUNDED.getCode().equals(order.getRefundType())) {
                refundCount++;
                refundAmount = refundAmount.add(defaultZero(order.getRefundAmount()));
            }
            if (OrderConstant.OrdersExpiredState.EXPIRED.getCode().equals(order.getOrdersExpiredState())) {
                expiredCount++;
            }
        }
        int userCount = userOrderMap.size();
        int continuedUserCount = (int) userOrderMap.values().stream().filter(cnt -> cnt > 1).count();
        BigDecimal estimatedRevenue = calculateEarningsAmount(clerk.getId(), orders, startTime, endTime);
        snapshot.setGmv(gmv);
        snapshot.setFirstOrderAmount(firstAmount);
        snapshot.setContinuedOrderAmount(continuedAmount);
        snapshot.setRewardAmount(rewardAmount);
        snapshot.setRefundAmount(refundAmount);
        snapshot.setEstimatedRevenue(estimatedRevenue);
        snapshot.setOrderCount(orderCount);
        snapshot.setFirstOrderCount(firstCount);
        snapshot.setContinuedOrderCount(continuedCount);
        snapshot.setRefundOrderCount(refundCount);
        snapshot.setExpiredOrderCount(expiredCount);
        snapshot.setUserCount(userCount);
        snapshot.setContinuedUserCount(continuedUserCount);
        snapshot.setContinuedRate(calcPercentage(continuedCount, orderCount));
        snapshot.setContinuedAmountRate(calcPercentage(continuedAmount, gmv));
        snapshot.setRefundRate(calcPercentage(refundCount, orderCount));
        snapshot.setContinuedCustomerRate(calcPercentage(continuedUserCount, userCount));
        snapshot.setAverageTicketPrice(calcAverage(gmv, orderCount));
        snapshot.setOnlineDuration(0L);
        return snapshot;
    }

    private List<PlayClerkUserInfoEntity> loadAccessibleClerks(ClerkPerformanceOverviewQueryVo vo) {
        List<String> accessibleIds =
                playPersonnelGroupInfoService.getValidClerkIdList(SecurityUtils.getLoginUser(), null);
        if (CollectionUtil.isEmpty(accessibleIds)) {
            return Collections.emptyList();
        }
        LambdaQueryWrapper<PlayClerkUserInfoEntity> wrapper =
                Wrappers.lambdaQuery(PlayClerkUserInfoEntity.class).in(PlayClerkUserInfoEntity::getId, accessibleIds)
                        .eq(PlayClerkUserInfoEntity::getClerkState, "1");
        if (StrUtil.isNotBlank(vo.getClerkId())) {
            wrapper.eq(PlayClerkUserInfoEntity::getId, vo.getClerkId());
        }
        if (StrUtil.isNotBlank(vo.getGroupId())) {
            wrapper.eq(PlayClerkUserInfoEntity::getGroupId, vo.getGroupId());
        }
        if (StrUtil.isNotBlank(vo.getSex())) {
            wrapper.eq(PlayClerkUserInfoEntity::getSex, vo.getSex());
        }
        if (StrUtil.isNotBlank(vo.getListingState())) {
            wrapper.eq(PlayClerkUserInfoEntity::getListingState, vo.getListingState());
        }
        return clerkUserInfoService.list(wrapper);
    }

    private DateRange resolveDateRange(List<String> endOrderTime) {
        String startStr;
        String endStr;
        if (CollectionUtil.isNotEmpty(endOrderTime) && endOrderTime.size() >= 2) {
            startStr = DateRangeUtils.normalizeStart(endOrderTime.get(0));
            endStr = DateRangeUtils.normalizeEnd(endOrderTime.get(1));
        } else {
            LocalDate end = LocalDate.now();
            LocalDate start = end.minusDays(6);
            startStr = start.format(DateRangeUtils.DATE_FORMATTER) + " 00:00:00";
            endStr = end.format(DateRangeUtils.DATE_FORMATTER) + " 23:59:59";
        }
        LocalDate startDate = LocalDate.parse(startStr.substring(0, 10), DateRangeUtils.DATE_FORMATTER);
        LocalDate endDate = LocalDate.parse(endStr.substring(0, 10), DateRangeUtils.DATE_FORMATTER);
        return new DateRange(startStr, endStr, startDate, endDate);
    }

    private BigDecimal calcPercentage(int numerator, int denominator) {
        if (denominator <= 0) {
            return BigDecimal.ZERO;
        }
        return BigDecimal.valueOf(numerator).multiply(BigDecimal.valueOf(100))
                .divide(BigDecimal.valueOf(denominator), 2, RoundingMode.HALF_UP);
    }

    private BigDecimal calcPercentage(BigDecimal numerator, BigDecimal denominator) {
        if (denominator == null || BigDecimal.ZERO.compareTo(denominator) == 0 || numerator == null) {
            return BigDecimal.ZERO;
        }
        return numerator.multiply(BigDecimal.valueOf(100)).divide(denominator, 2, RoundingMode.HALF_UP);
    }

    private BigDecimal calcAverage(BigDecimal numerator, int denominator) {
        if (denominator <= 0 || numerator == null) {
            return BigDecimal.ZERO;
        }
        return numerator.divide(BigDecimal.valueOf(denominator), 2, RoundingMode.HALF_UP);
    }

    private BigDecimal defaultZero(BigDecimal value) {
        return value == null ? BigDecimal.ZERO : value;
    }

    private boolean isCompletedOrder(PlayOrderInfoEntity order) {
        return order != null && OrderConstant.OrderStatus.COMPLETED.getCode().equals(order.getOrderStatus());
    }

    private static final class DateRange {
        private final String startTime;
        private final String endTime;
        private final LocalDate startDate;
        private final LocalDate endDate;

        private DateRange(String startTime, String endTime, LocalDate startDate, LocalDate endDate) {
            this.startTime = startTime;
            this.endTime = endTime;
            this.startDate = startDate;
            this.endDate = endDate;
        }
    }
}
