package com.sundot.airport.exam.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.sundot.airport.common.cache.StatisticsCacheConfig;
import com.sundot.airport.common.core.domain.entity.SysDept;
import com.sundot.airport.common.core.domain.entity.SysRole;
import com.sundot.airport.common.core.domain.model.LoginUser;
import com.sundot.airport.common.enums.RoleTypeEnum;
import com.sundot.airport.common.utils.SecurityUtils;
import com.sundot.airport.exam.domain.DailyTask;
import com.sundot.airport.exam.dto.*;
import com.sundot.airport.exam.enums.DailyTaskStatusEnum;
import com.sundot.airport.exam.mapper.DailyTaskMapper;
import com.sundot.airport.exam.service.IDashboardService;
import com.sundot.airport.system.mapper.SysDeptMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

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

/**
 * <b>功能名：</b>DashboardServiceImpl<br>
 * <b>说明：</b> 大屏统计Service实现 <br>
 * <b>著作权：</b> Copyright (C) 2025 SUNDOT CORPORATION<br>
 * <b>修改履历：
 *
 * @author 2025-01-19
 */
@Service
public class DashboardServiceImpl implements IDashboardService {

    private static final Logger log = LoggerFactory.getLogger(DashboardServiceImpl.class);

    @Autowired
    private DailyTaskMapper dailyTaskMapper;

    @Autowired
    private SysDeptMapper sysDeptMapper;

    private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
    private static final DateTimeFormatter DATETIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    @Override
    @Cacheable(value = StatisticsCacheConfig.DASHBOARD_CACHE,
            key = "'overview:' + T(com.sundot.airport.common.utils.SecurityUtils).getUserId() + ':' + (#query != null ? #query.toString() : 'default')")
    public DashboardOverviewDTO getOverview(DashboardQueryDTO query) {
        DashboardOverviewDTO dto = new DashboardOverviewDTO();

        // 使用startDate/endDate查询，若未指定则默认今天
        if (query == null) {
            query = new DashboardQueryDTO();
        }
        ensureDateRange(query);

        List<DailyTask> allTasks = queryTasks(query);

        dto.setTotalTasks((long) allTasks.size());

        long completed = allTasks.stream()
                .filter(t -> DailyTaskStatusEnum.COMPLETED.getCode().equals(t.getDtStatus()))
                .count();
        dto.setCompletedTasks(completed);

        double completionRate = allTasks.size() > 0
                ? (completed * 100.0 / allTasks.size()) : 0.0;
        dto.setCompletionRate(Math.round(completionRate * 100.0) / 100.0);

        double avgScore = allTasks.stream()
                .filter(t -> DailyTaskStatusEnum.COMPLETED.getCode().equals(t.getDtStatus()))
                .filter(t -> t.getDtTotalScore() != null)
                .mapToDouble(t -> t.getDtTotalScore().doubleValue())
                .average()
                .orElse(0.0);
        dto.setAvgScore(Math.round(avgScore * 100.0) / 100.0);

        long expired = allTasks.stream()
                .filter(t -> DailyTaskStatusEnum.EXPIRED.getCode().equals(t.getDtStatus()))
                .count();
        dto.setExpiredTasks(expired);

        // 环比增长（与相同长度的上一周期比较）
        DashboardOverviewDTO.GrowthData growth = computeGrowth(query, allTasks);
        dto.setGrowth(growth);

        return dto;
    }

    @Override
    @Cacheable(value = StatisticsCacheConfig.DASHBOARD_CACHE,
            key = "'trend:' + T(com.sundot.airport.common.utils.SecurityUtils).getUserId() + ':' + (#query != null ? #query.toString() : 'default')")
    public DashboardTrendDTO getTrend(DashboardQueryDTO query) {
        DashboardTrendDTO dto = new DashboardTrendDTO();

        int days = query.getDays() != null ? query.getDays() : 30;
        LocalDate endDate = LocalDate.now();
        LocalDate startDate = endDate.minusDays(days - 1);

        // 查询任务
        List<DailyTask> allTasks = queryTasks(query);

        // 生成日期列表
        List<String> dates = new ArrayList<>();
        List<Long> totalTasks = new ArrayList<>();
        List<Long> completedTasks = new ArrayList<>();
        List<Long> expiredTasks = new ArrayList<>();
        List<Double> avgScores = new ArrayList<>();

        for (LocalDate date = startDate; !date.isAfter(endDate); date = date.plusDays(1)) {
            LocalDate currentDate = date;
            dates.add(date.format(DATE_FORMATTER));

            // 统计当天数据
            List<DailyTask> dayTasks = allTasks.stream()
                    .filter(t -> isSameDate(t.getDtBusinessDate(), currentDate))
                    .collect(Collectors.toList());

            totalTasks.add((long) dayTasks.size());
            completedTasks.add(dayTasks.stream()
                    .filter(t -> DailyTaskStatusEnum.COMPLETED.getCode().equals(t.getDtStatus()))
                    .count());
            expiredTasks.add(dayTasks.stream()
                    .filter(t -> DailyTaskStatusEnum.EXPIRED.getCode().equals(t.getDtStatus()))
                    .count());

            // 计算当天平均分
            double avgScore = dayTasks.stream()
                    .filter(t -> DailyTaskStatusEnum.COMPLETED.getCode().equals(t.getDtStatus()))
                    .filter(t -> t.getDtTotalScore() != null)
                    .mapToDouble(t -> t.getDtTotalScore().doubleValue())
                    .average()
                    .orElse(0.0);
            avgScores.add(Math.round(avgScore * 100.0) / 100.0);
        }

        dto.setDates(dates);
        dto.setTotalTasks(totalTasks);
        dto.setCompletedTasks(completedTasks);
        dto.setExpiredTasks(expiredTasks);
        dto.setAvgScores(avgScores);

        return dto;
    }

    @Override
    @Cacheable(value = StatisticsCacheConfig.DASHBOARD_CACHE,
            key = "'deptRanking:' + T(com.sundot.airport.common.utils.SecurityUtils).getUserId() + ':' + (#query != null ? #query.toString() : 'default')")
    public List<DashboardDeptRankingDTO> getDeptRanking(DashboardQueryDTO query) {
        // 查询任务
        List<DailyTask> allTasks = queryTasks(query);

        // 按部门分组统计
        Map<Long, List<DailyTask>> deptTasksMap = allTasks.stream()
                .filter(t -> t.getDtDeptId() != null)
                .collect(Collectors.groupingBy(DailyTask::getDtDeptId));

        List<DashboardDeptRankingDTO> rankings = new ArrayList<>();
        for (Map.Entry<Long, List<DailyTask>> entry : deptTasksMap.entrySet()) {
            DashboardDeptRankingDTO ranking = new DashboardDeptRankingDTO();
            ranking.setDeptId(entry.getKey());

            List<DailyTask> deptTasks = entry.getValue();
            ranking.setDeptName(deptTasks.get(0).getDtDeptName());
            ranking.setTotalTasks((long) deptTasks.size());

            long completed = deptTasks.stream()
                    .filter(t -> DailyTaskStatusEnum.COMPLETED.getCode().equals(t.getDtStatus()))
                    .count();
            ranking.setCompletedTasks(completed);

            double completionRate = deptTasks.size() > 0
                    ? (completed * 100.0 / deptTasks.size()) : 0.0;
            ranking.setCompletionRate(Math.round(completionRate * 100.0) / 100.0);

            // 计算平均分
            double avgScore = deptTasks.stream()
                    .filter(t -> DailyTaskStatusEnum.COMPLETED.getCode().equals(t.getDtStatus()))
                    .filter(t -> t.getDtTotalScore() != null)
                    .mapToDouble(t -> t.getDtTotalScore().doubleValue())
                    .average()
                    .orElse(0.0);
            ranking.setAvgScore(Math.round(avgScore * 100.0) / 100.0);

            long expired = deptTasks.stream()
                    .filter(t -> DailyTaskStatusEnum.EXPIRED.getCode().equals(t.getDtStatus()))
                    .count();
            ranking.setExpiredTasks(expired);

            rankings.add(ranking);
        }

        // 排序
        String sortBy = query.getSortBy() != null ? query.getSortBy() : "completionRate";
        if ("completionRate".equals(sortBy)) {
            rankings.sort(Comparator.comparing(DashboardDeptRankingDTO::getCompletionRate).reversed());
        } else if ("avgScore".equals(sortBy)) {
            rankings.sort(Comparator.comparing(DashboardDeptRankingDTO::getAvgScore).reversed());
        } else {
            rankings.sort(Comparator.comparing(DashboardDeptRankingDTO::getTotalTasks).reversed());
        }

        // 设置排名
        for (int i = 0; i < rankings.size(); i++) {
            rankings.get(i).setRank(i + 1);
        }

        // 返回Top N
        int topN = query.getTopN() != null ? query.getTopN() : 10;
        return rankings.stream().limit(topN).collect(Collectors.toList());
    }

    @Override
    @Cacheable(value = StatisticsCacheConfig.DASHBOARD_CACHE,
            key = "'roleStats:' + T(com.sundot.airport.common.utils.SecurityUtils).getUserId() + ':' + (#query != null ? #query.toString() : 'default')")
    public List<DashboardRoleStatsDTO> getRoleStats(DashboardQueryDTO query) {
        // 查询任务
        List<DailyTask> allTasks = queryTasks(query);

        // 按角色分组统计（处理多角色情况）
        Map<String, List<DailyTask>> roleTasksMap = allTasks.stream()
                .filter(t -> t.getDtRole() != null && !t.getDtRole().isEmpty())
                .flatMap(t -> {
                    // 处理多角色（逗号分隔）
                    String[] roles = t.getDtRole().split(",");
                    return Arrays.stream(roles).map(role ->
                        new AbstractMap.SimpleEntry<>(role.trim(), t));
                })
                .collect(Collectors.groupingBy(
                        Map.Entry::getKey,
                        Collectors.mapping(Map.Entry::getValue, Collectors.toList())
                ));

        List<DashboardRoleStatsDTO> stats = new ArrayList<>();
        for (Map.Entry<String, List<DailyTask>> entry : roleTasksMap.entrySet()) {
            DashboardRoleStatsDTO stat = new DashboardRoleStatsDTO();
            stat.setRoleName(entry.getKey());

            List<DailyTask> roleTasks = entry.getValue();
            stat.setTotalTasks((long) roleTasks.size());

            long completed = roleTasks.stream()
                    .filter(t -> DailyTaskStatusEnum.COMPLETED.getCode().equals(t.getDtStatus()))
                    .count();
            stat.setCompletedTasks(completed);

            double completionRate = roleTasks.size() > 0
                    ? (completed * 100.0 / roleTasks.size()) : 0.0;
            stat.setCompletionRate(Math.round(completionRate * 100.0) / 100.0);

            // 计算平均分
            double avgScore = roleTasks.stream()
                    .filter(t -> DailyTaskStatusEnum.COMPLETED.getCode().equals(t.getDtStatus()))
                    .filter(t -> t.getDtTotalScore() != null)
                    .mapToDouble(t -> t.getDtTotalScore().doubleValue())
                    .average()
                    .orElse(0.0);
            stat.setAvgScore(Math.round(avgScore * 100.0) / 100.0);

            // TODO: 计算正确率需要查询任务详情表
            stat.setAccuracyRate(0.0);

            long expired = roleTasks.stream()
                    .filter(t -> DailyTaskStatusEnum.EXPIRED.getCode().equals(t.getDtStatus()))
                    .count();
            stat.setExpiredTasks(expired);

            // 统计该角色的用户数（去重）
            long userCount = roleTasks.stream()
                    .map(DailyTask::getDtUserId)
                    .filter(Objects::nonNull)
                    .distinct()
                    .count();
            stat.setUserCount(userCount);

            stats.add(stat);
        }

        // 按任务数降序排序
        stats.sort(Comparator.comparing(DashboardRoleStatsDTO::getTotalTasks).reversed());

        return stats;
    }

    @Override
    @Cacheable(value = StatisticsCacheConfig.DASHBOARD_CACHE,
            key = "'expiredAnalysis:' + T(com.sundot.airport.common.utils.SecurityUtils).getUserId() + ':' + (#query != null ? #query.toString() : 'default')")
    public DashboardExpiredAnalysisDTO getExpiredAnalysis(DashboardQueryDTO query) {
        DashboardExpiredAnalysisDTO dto = new DashboardExpiredAnalysisDTO();

        // 查询所有逾期任务
        List<DailyTask> allTasks = queryTasks(query);
        List<DailyTask> expiredTasks = allTasks.stream()
                .filter(t -> DailyTaskStatusEnum.EXPIRED.getCode().equals(t.getDtStatus()))
                .collect(Collectors.toList());

        dto.setTotalExpired((long) expiredTasks.size());

        if (expiredTasks.isEmpty()) {
            dto.setByExpireDays(new ArrayList<>());
            dto.setByDept(new ArrayList<>());
            dto.setByRole(new ArrayList<>());
            return dto;
        }

        // 按逾期天数分组
        String groupBy = query.getGroupBy() != null ? query.getGroupBy() : "expireDays";

        if ("expireDays".equals(groupBy) || groupBy == null) {
            List<DashboardExpiredAnalysisDTO.ExpiredGroupItem> byDays = new ArrayList<>();
            LocalDate now = LocalDate.now();

            // 统计各时间段的逾期任务
            long day1 = expiredTasks.stream().filter(t -> getExpireDays(t, now) <= 1).count();
            long day3 = expiredTasks.stream().filter(t -> getExpireDays(t, now) > 1 && getExpireDays(t, now) <= 3).count();
            long day7 = expiredTasks.stream().filter(t -> getExpireDays(t, now) > 3 && getExpireDays(t, now) <= 7).count();
            long day7Plus = expiredTasks.stream().filter(t -> getExpireDays(t, now) > 7).count();

            byDays.add(createGroupItem("1天内", day1, expiredTasks.size()));
            byDays.add(createGroupItem("1-3天", day3, expiredTasks.size()));
            byDays.add(createGroupItem("3-7天", day7, expiredTasks.size()));
            byDays.add(createGroupItem("7天以上", day7Plus, expiredTasks.size()));

            dto.setByExpireDays(byDays);
        }

        // 按部门分组
        Map<String, Long> deptCountMap = expiredTasks.stream()
                .filter(t -> t.getDtDeptName() != null)
                .collect(Collectors.groupingBy(DailyTask::getDtDeptName, Collectors.counting()));

        List<DashboardExpiredAnalysisDTO.ExpiredGroupItem> byDept = deptCountMap.entrySet().stream()
                .map(e -> createGroupItem(e.getKey(), e.getValue(), expiredTasks.size()))
                .sorted(Comparator.comparing(DashboardExpiredAnalysisDTO.ExpiredGroupItem::getCount).reversed())
                .collect(Collectors.toList());
        dto.setByDept(byDept);

        // 按角色分组
        Map<String, Long> roleCountMap = expiredTasks.stream()
                .filter(t -> t.getDtRole() != null && !t.getDtRole().isEmpty())
                .flatMap(t -> Arrays.stream(t.getDtRole().split(",")))
                .map(String::trim)
                .collect(Collectors.groupingBy(role -> role, Collectors.counting()));

        List<DashboardExpiredAnalysisDTO.ExpiredGroupItem> byRole = roleCountMap.entrySet().stream()
                .map(e -> createGroupItem(e.getKey(), e.getValue(), expiredTasks.size()))
                .sorted(Comparator.comparing(DashboardExpiredAnalysisDTO.ExpiredGroupItem::getCount).reversed())
                .collect(Collectors.toList());
        dto.setByRole(byRole);

        return dto;
    }

    /**
     * 创建分组项
     */
    private DashboardExpiredAnalysisDTO.ExpiredGroupItem createGroupItem(String name, long count, int total) {
        DashboardExpiredAnalysisDTO.ExpiredGroupItem item = new DashboardExpiredAnalysisDTO.ExpiredGroupItem();
        item.setName(name);
        item.setCount(count);
        double percentage = total > 0 ? (count * 100.0 / total) : 0.0;
        item.setPercentage(Math.round(percentage * 100.0) / 100.0);
        return item;
    }

    /**
     * 计算逾期天数
     */
    private long getExpireDays(DailyTask task, LocalDate now) {
        if (task.getDtExpireTime() == null) return 0;
        try {
            LocalDateTime expireTime = dateToLocalDateTime(task.getDtExpireTime());
            if (expireTime == null) return 0;
            return ChronoUnit.DAYS.between(expireTime.toLocalDate(), now);
        } catch (Exception e) {
            return 0;
        }
    }

    @Override
    @Cacheable(value = StatisticsCacheConfig.DASHBOARD_CACHE,
            key = "'userRanking:' + T(com.sundot.airport.common.utils.SecurityUtils).getUserId() + ':' + (#query != null ? #query.toString() : 'default')")
    public List<DashboardUserRankingDTO> getUserRanking(DashboardQueryDTO query) {
        // 查询任务
        List<DailyTask> allTasks = queryTasks(query);

        // 按用户分组统计
        Map<Long, List<DailyTask>> userTasksMap = allTasks.stream()
                .filter(t -> t.getDtUserId() != null)
                .collect(Collectors.groupingBy(DailyTask::getDtUserId));

        List<DashboardUserRankingDTO> rankings = new ArrayList<>();
        for (Map.Entry<Long, List<DailyTask>> entry : userTasksMap.entrySet()) {
            DashboardUserRankingDTO ranking = new DashboardUserRankingDTO();
            ranking.setUserId(entry.getKey());

            List<DailyTask> userTasks = entry.getValue();
            if (userTasks.isEmpty()) continue;

            // 取第一个任务的用户信息
            DailyTask firstTask = userTasks.get(0);
            ranking.setUserName(firstTask.getDtUserName());
            ranking.setDeptName(firstTask.getDtDeptName());
            ranking.setRoleName(firstTask.getDtRole());

            ranking.setTotalTasks((long) userTasks.size());

            long completed = userTasks.stream()
                    .filter(t -> DailyTaskStatusEnum.COMPLETED.getCode().equals(t.getDtStatus()))
                    .count();
            ranking.setCompletedTasks(completed);

            double completionRate = userTasks.size() > 0
                    ? (completed * 100.0 / userTasks.size()) : 0.0;
            ranking.setCompletionRate(Math.round(completionRate * 100.0) / 100.0);

            // 计算平均分
            double avgScore = userTasks.stream()
                    .filter(t -> DailyTaskStatusEnum.COMPLETED.getCode().equals(t.getDtStatus()))
                    .filter(t -> t.getDtTotalScore() != null)
                    .mapToDouble(t -> t.getDtTotalScore().doubleValue())
                    .average()
                    .orElse(0.0);
            ranking.setAvgScore(Math.round(avgScore * 100.0) / 100.0);

            // TODO: 计算正确率需要查询任务详情表
            ranking.setAccuracyRate(0.0);

            long expired = userTasks.stream()
                    .filter(t -> DailyTaskStatusEnum.EXPIRED.getCode().equals(t.getDtStatus()))
                    .count();
            ranking.setExpiredTasks(expired);

            rankings.add(ranking);
        }

        // 排序
        String sortBy = query.getSortBy() != null ? query.getSortBy() : "completedTasks";
        if ("completedTasks".equals(sortBy)) {
            rankings.sort(Comparator.comparing(DashboardUserRankingDTO::getCompletedTasks).reversed());
        } else if ("avgScore".equals(sortBy)) {
            rankings.sort(Comparator.comparing(DashboardUserRankingDTO::getAvgScore).reversed());
        } else if ("accuracyRate".equals(sortBy)) {
            rankings.sort(Comparator.comparing(DashboardUserRankingDTO::getAccuracyRate).reversed());
        }

        // 设置排名和奖牌
        for (int i = 0; i < rankings.size(); i++) {
            rankings.get(i).setRank(i + 1);
        }

        // 返回Top N
        int topN = query.getTopN() != null ? query.getTopN() : 10;
        return rankings.stream().limit(topN).collect(Collectors.toList());
    }

    @Override
    public List<DashboardUserRankingDTO> getAllUsersRanking(DashboardQueryDTO query) {
        // 如果没有指定时间范围，默认查询当天数据
        if (query == null) {
            query = new DashboardQueryDTO();
        }
        ensureDateRange(query);

        // 查询任务
        List<DailyTask> allTasks = queryTasks(query);

        // 按用户分组统计
        Map<Long, List<DailyTask>> userTasksMap = allTasks.stream()
                .filter(t -> t.getDtUserId() != null)
                .collect(Collectors.groupingBy(DailyTask::getDtUserId));

        List<DashboardUserRankingDTO> rankings = new ArrayList<>();
        for (Map.Entry<Long, List<DailyTask>> entry : userTasksMap.entrySet()) {
            DashboardUserRankingDTO ranking = new DashboardUserRankingDTO();
            ranking.setUserId(entry.getKey());

            List<DailyTask> userTasks = entry.getValue();
            if (userTasks.isEmpty()) continue;

            // 取第一个任务的用户信息
            DailyTask firstTask = userTasks.get(0);
            ranking.setUserName(firstTask.getDtUserName());
            ranking.setDeptName(firstTask.getDtDeptName());
            ranking.setRoleName(firstTask.getDtRole());

            ranking.setTotalTasks((long) userTasks.size());

            long completed = userTasks.stream()
                    .filter(t -> DailyTaskStatusEnum.COMPLETED.getCode().equals(t.getDtStatus()))
                    .count();
            ranking.setCompletedTasks(completed);

            double completionRate = userTasks.size() > 0
                    ? (completed * 100.0 / userTasks.size()) : 0.0;
            ranking.setCompletionRate(Math.round(completionRate * 100.0) / 100.0);

            // 计算平均分
            double avgScore = userTasks.stream()
                    .filter(t -> DailyTaskStatusEnum.COMPLETED.getCode().equals(t.getDtStatus()))
                    .filter(t -> t.getDtTotalScore() != null)
                    .mapToDouble(t -> t.getDtTotalScore().doubleValue())
                    .average()
                    .orElse(0.0);
            ranking.setAvgScore(Math.round(avgScore * 100.0) / 100.0);

            // TODO: 计算正确率需要查询任务详情表
            ranking.setAccuracyRate(0.0);

            long expired = userTasks.stream()
                    .filter(t -> DailyTaskStatusEnum.EXPIRED.getCode().equals(t.getDtStatus()))
                    .count();
            ranking.setExpiredTasks(expired);

            rankings.add(ranking);
        }

        // 排序
        String sortBy = query.getSortBy() != null ? query.getSortBy() : "completedTasks";
        if ("completedTasks".equals(sortBy)) {
            rankings.sort(Comparator.comparing(DashboardUserRankingDTO::getCompletedTasks).reversed());
        } else if ("avgScore".equals(sortBy)) {
            rankings.sort(Comparator.comparing(DashboardUserRankingDTO::getAvgScore).reversed());
        } else if ("accuracyRate".equals(sortBy)) {
            rankings.sort(Comparator.comparing(DashboardUserRankingDTO::getAccuracyRate).reversed());
        } else if ("completionRate".equals(sortBy)) {
            rankings.sort(Comparator.comparing(DashboardUserRankingDTO::getCompletionRate).reversed());
        }

        // 设置排名
        for (int i = 0; i < rankings.size(); i++) {
            rankings.get(i).setRank(i + 1);
        }

        // 返回所有用户数据（不限制topN，由分页控制）
        return rankings;
    }

    @Override
    public List<DashboardRecentTaskDTO> getRecentTasks(DashboardQueryDTO query) {
        // 查询最近完成的任务
        int limit = query.getLimit() != null ? query.getLimit() : 20;

        LambdaQueryWrapper<DailyTask> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(DailyTask::getDelStatus, 0);
        wrapper.eq(DailyTask::getDtStatus, DailyTaskStatusEnum.COMPLETED.getCode());
        wrapper.orderByDesc(DailyTask::getDtSubmitTime);
        wrapper.last("LIMIT " + limit);

        List<DailyTask> recentTasks = dailyTaskMapper.selectList(wrapper);

        return recentTasks.stream().map(task -> {
            DashboardRecentTaskDTO dto = new DashboardRecentTaskDTO();
            dto.setTaskId(task.getDtId());
            dto.setUserName(task.getDtUserName());
            dto.setDeptName(task.getDtDeptName());
            dto.setRoleName(task.getDtRole());
            dto.setScore(task.getDtTotalScore() != null ? task.getDtTotalScore().doubleValue() : 0.0);
            dto.setStatus(task.getDtStatus());
            dto.setSubmitTime(formatDateTime(task.getDtSubmitTime()));

            // 计算答题耗时
            if (task.getDtStartTime() != null && task.getDtSubmitTime() != null) {
                try {
                    LocalDateTime startTime = dateToLocalDateTime(task.getDtStartTime());
                    LocalDateTime submitTime = dateToLocalDateTime(task.getDtSubmitTime());
                    if (startTime != null && submitTime != null) {
                        long timeCost = ChronoUnit.SECONDS.between(startTime, submitTime);
                        dto.setTimeCost(timeCost);
                    } else {
                        dto.setTimeCost(0L);
                    }
                } catch (Exception e) {
                    dto.setTimeCost(0L);
                }
            }

            return dto;
        }).collect(Collectors.toList());
    }

    @Override
    public List<UserSearchResultDTO> searchUsers(DashboardQueryDTO query) {
        // TODO: 实现用户搜索逻辑
        // 需要关联用户表查询,暂时返回空列表
        return new ArrayList<>();
    }

    @Override
    @Cacheable(value = StatisticsCacheConfig.DASHBOARD_CACHE,
            key = "'filterOptions:' + T(com.sundot.airport.common.utils.SecurityUtils).getUserId()")
    public FilterOptionsDTO getFilterOptions() {
        FilterOptionsDTO dto = new FilterOptionsDTO();

        List<SysDept> depts;
        Set<Long> permittedDeptIds = getPermittedDeptIds();
        if (permittedDeptIds == null) {
            // 全部数据权限，返回所有部门
            depts = sysDeptMapper.selectDeptList(new SysDept());
        } else if (permittedDeptIds.isEmpty()) {
            // 无部门权限（安检员等），返回空
            depts = Collections.emptyList();
        } else {
            // 有部门限制，只返回权限范围内的部门
            List<SysDept> allDepts = sysDeptMapper.selectDeptList(new SysDept());
            depts = allDepts.stream()
                    .filter(d -> permittedDeptIds.contains(d.getDeptId()))
                    .collect(Collectors.toList());
        }

        List<FilterOptionsDTO.DeptOption> deptOptions = depts.stream()
                .map(dept -> {
                    FilterOptionsDTO.DeptOption option = new FilterOptionsDTO.DeptOption();
                    option.setDeptId(dept.getDeptId());
                    option.setDeptName(dept.getDeptName());
                    return option;
                })
                .collect(Collectors.toList());
        dto.setDepts(deptOptions);

        // TODO: 查询所有角色
        dto.setRoles(new ArrayList<>());

        return dto;
    }

    // ==================== 私有辅助方法 ====================

    /**
     * 确保query有日期范围，若未指定startDate/endDate且未指定timeRange，则默认今天
     */
    private void ensureDateRange(DashboardQueryDTO query) {
        boolean hasDateRange = (query.getStartDate() != null && !query.getStartDate().isEmpty())
                || (query.getEndDate() != null && !query.getEndDate().isEmpty());
        boolean hasTimeRange = query.getTimeRange() != null && !query.getTimeRange().isEmpty();

        if (!hasDateRange && !hasTimeRange) {
            String today = LocalDate.now().format(DATE_FORMATTER);
            query.setStartDate(today);
            query.setEndDate(today);
        }
    }

    /**
     * 计算环比增长数据
     */
    private DashboardOverviewDTO.GrowthData computeGrowth(DashboardQueryDTO query, List<DailyTask> currentTasks) {
        DashboardOverviewDTO.GrowthData growth = new DashboardOverviewDTO.GrowthData();
        try {
            LocalDate startDate = LocalDate.parse(query.getStartDate(), DATE_FORMATTER);
            LocalDate endDate = LocalDate.parse(query.getEndDate(), DATE_FORMATTER);
            long periodDays = ChronoUnit.DAYS.between(startDate, endDate) + 1;

            // 上一周期
            LocalDate prevEnd = startDate.minusDays(1);
            LocalDate prevStart = prevEnd.minusDays(periodDays - 1);

            DashboardQueryDTO prevQuery = new DashboardQueryDTO();
            prevQuery.setDeptIds(query.getDeptIds());
            prevQuery.setRoles(query.getRoles());
            prevQuery.setUserIds(query.getUserIds());
            prevQuery.setStartDate(prevStart.format(DATE_FORMATTER));
            prevQuery.setEndDate(prevEnd.format(DATE_FORMATTER));

            List<DailyTask> prevTasks = queryTasks(prevQuery);

            // 任务数增长
            long currentTotal = currentTasks.size();
            long prevTotal = prevTasks.size();
            growth.setTotalTasksGrowth(calcGrowthRate(currentTotal, prevTotal));

            // 完成数增长
            long currentCompleted = currentTasks.stream()
                    .filter(t -> DailyTaskStatusEnum.COMPLETED.getCode().equals(t.getDtStatus())).count();
            long prevCompleted = prevTasks.stream()
                    .filter(t -> DailyTaskStatusEnum.COMPLETED.getCode().equals(t.getDtStatus())).count();
            growth.setCompletedTasksGrowth(calcGrowthRate(currentCompleted, prevCompleted));

            // 平均分增长
            double currentAvg = currentTasks.stream()
                    .filter(t -> DailyTaskStatusEnum.COMPLETED.getCode().equals(t.getDtStatus()))
                    .filter(t -> t.getDtTotalScore() != null)
                    .mapToDouble(t -> t.getDtTotalScore().doubleValue())
                    .average().orElse(0.0);
            double prevAvg = prevTasks.stream()
                    .filter(t -> DailyTaskStatusEnum.COMPLETED.getCode().equals(t.getDtStatus()))
                    .filter(t -> t.getDtTotalScore() != null)
                    .mapToDouble(t -> t.getDtTotalScore().doubleValue())
                    .average().orElse(0.0);
            growth.setAvgScoreGrowth(calcGrowthRate((long) (currentAvg * 100), (long) (prevAvg * 100)));

        } catch (Exception e) {
            growth.setTotalTasksGrowth(0.0);
            growth.setCompletedTasksGrowth(0.0);
            growth.setAvgScoreGrowth(0.0);
        }
        return growth;
    }

    private double calcGrowthRate(long current, long previous) {
        if (previous == 0) {
            return current > 0 ? 100.0 : 0.0;
        }
        return Math.round((current - previous) * 10000.0 / previous) / 100.0;
    }

    /**
     * 判断当前用户是否拥有全部数据权限（管理员、站长、质检科）
     */
    private boolean hasAllDataPermission() {
        try {
            LoginUser loginUser = SecurityUtils.getLoginUser();
            if (loginUser == null) return false;
            // userId=1 是超级管理员
            if (loginUser.getUserId() == 1L) return true;
            List<SysRole> roles = loginUser.getUser().getRoles();
            if (roles == null || roles.isEmpty()) return false;
            List<String> roleKeys = roles.stream().map(SysRole::getRoleKey).collect(Collectors.toList());
            return roleKeys.contains(RoleTypeEnum.admin.getCode())
                    || roleKeys.contains(RoleTypeEnum.test.getCode())
                    || roleKeys.contains(RoleTypeEnum.zhijianke.getCode());
        } catch (Exception e) {
            log.warn("获取登录用户信息失败", e);
            return false;
        }
    }

    /**
     * 获取当前用户有权限查看的部门ID集合，null 表示可查看全部
     */
    private Set<Long> getPermittedDeptIds() {
        if (hasAllDataPermission()) {
            return null; // 全部数据
        }
        try {
            LoginUser loginUser = SecurityUtils.getLoginUser();
            Long deptId = loginUser.getDeptId();
            List<SysRole> roles = loginUser.getUser().getRoles();
            List<String> roleKeys = roles.stream().map(SysRole::getRoleKey).collect(Collectors.toList());

            // 科长：本科室 + 所有下级部门
            if (roleKeys.contains(RoleTypeEnum.kezhang.getCode())) {
                Set<Long> deptIds = new HashSet<>();
                deptIds.add(deptId);
                List<SysDept> children = sysDeptMapper.selectChildrenDeptById(deptId);
                if (children != null) {
                    children.forEach(d -> deptIds.add(d.getDeptId()));
                }
                return deptIds;
            }
            // 班组长：本班组
            if (roleKeys.contains(RoleTypeEnum.banzuzhang.getCode())) {
                Set<Long> deptIds = new HashSet<>();
                deptIds.add(deptId);
                return deptIds;
            }
            // 安检员或其他角色：只看自己（返回空集合，在queryTasks中走userId过滤）
            return Collections.emptySet();
        } catch (Exception e) {
            log.warn("获取用户权限部门失败", e);
            return Collections.emptySet();
        }
    }

    /**
     * 查询任务列表（含数据权限过滤）
     */
    private List<DailyTask> queryTasks(DashboardQueryDTO query) {
        LambdaQueryWrapper<DailyTask> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(DailyTask::getDelStatus, 0);

        // ===== 数据权限过滤 =====
        Set<Long> permittedDeptIds = getPermittedDeptIds();
        if (permittedDeptIds != null) {
            if (permittedDeptIds.isEmpty()) {
                // 无部门权限，只能看自己的数据
                try {
                    wrapper.eq(DailyTask::getDtUserId, SecurityUtils.getUserId());
                } catch (Exception e) {
                    // 获取不到用户，返回空
                    return Collections.emptyList();
                }
            } else {
                wrapper.in(DailyTask::getDtDeptId, permittedDeptIds);
            }
        }
        // permittedDeptIds == null 表示全部数据，不加部门过滤

        // ===== 前端筛选条件 =====
        if (query != null) {
            // 按部门筛选（包含所有子部门），在权限范围内进一步缩小
            if (query.getDeptIds() != null && !query.getDeptIds().isEmpty()) {
                String[] deptIdArray = query.getDeptIds().split(",");
                Set<Long> selectedDeptIds = new HashSet<>();
                for (String idStr : deptIdArray) {
                    Long deptId = Long.parseLong(idStr.trim());
                    selectedDeptIds.add(deptId);
                    List<SysDept> children = sysDeptMapper.selectChildrenDeptById(deptId);
                    if (children != null) {
                        children.forEach(d -> selectedDeptIds.add(d.getDeptId()));
                    }
                }
                // 如果有权限限制，取交集；否则直接用前端选择
                if (permittedDeptIds != null && !permittedDeptIds.isEmpty()) {
                    selectedDeptIds.retainAll(permittedDeptIds);
                }
                if (selectedDeptIds.isEmpty()) {
                    return Collections.emptyList();
                }
                wrapper.in(DailyTask::getDtDeptId, selectedDeptIds);
            }

            // 按角色筛选
            if (query.getRoles() != null && !query.getRoles().isEmpty()) {
                wrapper.like(DailyTask::getDtRole, query.getRoles());
            }

            // 按用户ID筛选
            if (query.getUserIds() != null && !query.getUserIds().isEmpty()) {
                String[] userIdArray = query.getUserIds().split(",");
                List<Long> userIds = Arrays.stream(userIdArray)
                        .map(String::trim)
                        .map(Long::parseLong)
                        .collect(Collectors.toList());
                wrapper.in(DailyTask::getDtUserId, userIds);
            }

            // 按时间范围筛选
            if (query.getTimeRange() != null && !query.getTimeRange().isEmpty()) {
                LocalDate today = LocalDate.now();
                LocalDate startDate = null;
                LocalDate endDate = today;

                switch (query.getTimeRange()) {
                    case "today":
                        startDate = today;
                        break;
                    case "week":
                        startDate = today.minusDays(today.getDayOfWeek().getValue() - 1);
                        break;
                    case "month":
                        startDate = today.withDayOfMonth(1);
                        break;
                }

                if (startDate != null) {
                    Date startDateTime = Date.from(startDate.atStartOfDay(ZoneId.systemDefault()).toInstant());
                    Date endDateTime = Date.from(endDate.plusDays(1).atStartOfDay(ZoneId.systemDefault()).toInstant());
                    wrapper.ge(DailyTask::getDtBusinessDate, startDateTime);
                    wrapper.lt(DailyTask::getDtBusinessDate, endDateTime);
                }
            }

            // 按自定义日期范围筛选
            if (query.getStartDate() != null && !query.getStartDate().isEmpty()) {
                try {
                    LocalDate startDate = LocalDate.parse(query.getStartDate(), DATE_FORMATTER);
                    Date startDateTime = Date.from(startDate.atStartOfDay(ZoneId.systemDefault()).toInstant());
                    wrapper.ge(DailyTask::getDtBusinessDate, startDateTime);
                } catch (Exception e) {
                    // 日期格式错误,忽略
                }
            }

            if (query.getEndDate() != null && !query.getEndDate().isEmpty()) {
                try {
                    LocalDate endDate = LocalDate.parse(query.getEndDate(), DATE_FORMATTER);
                    Date endDateTime = Date.from(endDate.plusDays(1).atStartOfDay(ZoneId.systemDefault()).toInstant());
                    wrapper.lt(DailyTask::getDtBusinessDate, endDateTime);
                } catch (Exception e) {
                    // 日期格式错误,忽略
                }
            }
        }

        return dailyTaskMapper.selectList(wrapper);
    }

    /**
     * 判断日期是否在指定范围内
     */
    private boolean isDateInRange(Date date, LocalDate startDate, LocalDate endDate) {
        if (date == null) return false;
        try {
            LocalDate localDate = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
            return !localDate.isBefore(startDate) && !localDate.isAfter(endDate);
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 判断是否为同一天
     */
    private boolean isSameDate(Date date, LocalDate targetDate) {
        if (date == null) return false;
        try {
            LocalDate localDate = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
            return localDate.equals(targetDate);
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 将Date转换为字符串 (yyyy-MM-dd HH:mm:ss)
     */
    private String formatDateTime(Date date) {
        if (date == null) return null;
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        return sdf.format(date);
    }

    /**
     * 将Date转换为LocalDateTime
     */
    private LocalDateTime dateToLocalDateTime(Date date) {
        if (date == null) return null;
        return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
    }
}
