package com.inspect.calender.service;

import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.inspect.base.core.enums.ExecStatus;
import com.inspect.base.core.enums.IntervalType;
import com.inspect.base.core.utils.StringUtils;
import com.inspect.calender.domain.DailyTaskStatsDTO;
import com.inspect.calender.domain.MonthlyTaskStatsDTO;
import com.inspect.calender.enums.TaskStateEnum;
import com.inspect.task.domain.PatrolTask;
import com.inspect.task.mapper.PatrolTaskMapper;
import com.inspect.taskstatus.mapper.PatrolTaskStatusMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.time.*;
import java.time.temporal.TemporalAdjusters;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 任务执行日历处理器
 */
@Service
@Slf4j
public class TaskCalendarGenerator {
    /*
     * 未来任务缓存的过期时间
     * 预生成未来1个月计划（本月和下一个月）
     */
    private static final Integer FUTURE_CALENDAR = 60;
    @Resource
    private PatrolTaskMapper patrolTaskMapper;
    private final LoadingCache<Integer, Map<LocalDate, List<DailyTaskStatsDTO>>> futureCalendarCache =
            Caffeine.newBuilder()
                    .maximumSize(5)
                    .expireAfterWrite(12, TimeUnit.HOURS)
                    .build(this::generate);
    @Resource
    private PatrolTaskStatusMapper patrolTaskStatusMapper;

    /**
     * 定时任务：每天凌晨或任务变更后主动刷新缓存
     */
    @Scheduled(cron = "0 50 0 * * ?")
    public void refreshCalendarCache() {
        log.info("[任务执行日历]定时刷新未来任务日历缓存");
        futureCalendarCache.invalidateAll();
        futureCalendarCache.refresh(FUTURE_CALENDAR);
    }

    /**
     * 任务信息变更时，主动刷新日历缓存
     */
    public void onTaskChange() {
        log.info("[任务执行日历]任务信息变化，日历缓存失效");
        futureCalendarCache.invalidateAll();
    }

    /**
     * 获取指定年的任务统计信息（已执行、执行中、未执行）
     *
     * @param year
     * @return
     */
    public List<MonthlyTaskStatsDTO> getMonthlyStatsByYear(int year) {
        List<MonthlyTaskStatsDTO> existingStats = patrolTaskStatusMapper.countMonthlyStatsByYear(year);
        // 获取未来预生成的计划任务（缓存）
        Map<LocalDate, List<DailyTaskStatsDTO>> futureCalendarMap = futureCalendarCache.get(FUTURE_CALENDAR);
        // 验证缓存数据是否为空
        if (futureCalendarMap == null) {
            return existingStats;
        }

        LocalDateTime currentTime = LocalDateTime.now();
        LocalDate currentDate = currentTime.toLocalDate();
        for (Map.Entry<LocalDate, List<DailyTaskStatsDTO>> entry : futureCalendarMap.entrySet()) {
            LocalDate date = entry.getKey();
            List<DailyTaskStatsDTO> futureTasks = entry.getValue();

            // 检查当前日期是否在指定年份范围内
            if (date.getYear() == year) {
                long count = 0;
                if (date.equals(currentDate)) {
                    if (futureTasks != null) {
                        count = futureTasks.stream()
                                .filter(dto -> dto.getStartTime() != null)
                                .filter(dto -> dto.getStartTime().isAfter(currentTime))
                                .count();
                    }
                } else if (date.isAfter(currentDate)) {
                    if (futureTasks != null) {
                        count = futureTasks.size();
                    }
                }
                // 确保月份索引在有效范围内
                int monthIndex = date.getMonthValue() - 1;
                if (monthIndex < existingStats.size()) {
                    MonthlyTaskStatsDTO monthlyStats = existingStats.get(date.getMonthValue() - 1);
                    monthlyStats.setPending(monthlyStats.getPending() + count);
                    monthlyStats.setTotal(monthlyStats.getTotal() + count);
                }
            }
        }

        return existingStats;
    }

    /**
     * 获取指定年月的完整日历，包含已生成的任务和未来生成的任务
     *
     * @param year  年
     * @param month 月
     * @return 日历列表
     */
    public List<DailyTaskStatsDTO> getFullCalendar(int year, int month) {
        return getFullCalendar(year, month, 0);
    }

    /**
     * 获取指定年月日的完整日历，包含已生成的任务和未来生成的任务
     *
     * @param year  年
     * @param month 月
     * @param day   日
     * @return
     */
    public List<DailyTaskStatsDTO> getFullCalendar(int year, int month, int day) {
        List<DailyTaskStatsDTO> existingList;
        if (day == 0) {
            existingList = patrolTaskStatusMapper.selectDailyStatsByYearAndMonth(year, month);
        } else {
            existingList = patrolTaskStatusMapper.selectDailyStatsByYearAndMonthAndDay(year, month, day);
        }
        existingList.forEach(DailyTaskStatsDTO::formatTimeAndStatus);

        // 将已生成任务按日期分组
        Map<LocalDate, List<DailyTaskStatsDTO>> fullCalendarMap = existingList.stream()
                .collect(Collectors.groupingBy(dto -> dto.getStartTime().toLocalDate(), TreeMap::new, Collectors.toList()));

        // 2. 获取未来预生成的计划任务（缓存）
        Map<LocalDate, List<DailyTaskStatsDTO>> futureCalendarMap = futureCalendarCache.get(FUTURE_CALENDAR);

        if (day != 0) {
            LocalDate targetDate = LocalDate.of(year, month, day);
            Map<LocalDate, List<DailyTaskStatsDTO>> tempMap = new TreeMap<>();
            tempMap.computeIfAbsent(targetDate, k -> new ArrayList<>()).addAll(futureCalendarMap.getOrDefault(targetDate, new ArrayList<>()));
            futureCalendarMap = tempMap;
        }
        LocalDateTime currentTime = LocalDateTime.now();
        LocalDate currentDate = currentTime.toLocalDate();
        for (Map.Entry<LocalDate, List<DailyTaskStatsDTO>> entry : futureCalendarMap.entrySet()) {
            LocalDate date = entry.getKey();
            List<DailyTaskStatsDTO> futureTasks = entry.getValue();
            // 检查当前日期是否在指定年月范围内
            if (date.getYear() == year && date.getMonthValue() == month) {
                List<DailyTaskStatsDTO> existingTasks = fullCalendarMap.computeIfAbsent(date, k -> new ArrayList<>());
                // 将未来计划任务添加到当前日期的任务列表中（如果该任务尚未存在）
                for (DailyTaskStatsDTO futureTask : futureTasks) {
                    if (date.equals(currentDate)) {
                        // 检查在当前时间之前的任务舍弃不添加
                        if (currentTime.isBefore(futureTask.getStartTime())) {
                            existingTasks.add(futureTask);
                        }
                    } else {
                        existingTasks.add(futureTask);
                    }
                }
            }
        }
        // 对每一天的任务按开始时间排序
        fullCalendarMap.values().forEach(list ->
                list.sort(Comparator.comparing(DailyTaskStatsDTO::getStartTime)));

        return fullCalendarMap.values()
                .stream()
                .flatMap(List::stream)
                .collect(Collectors.toList());
    }

    /**
     * 获取未来 1 个月天的任务执行日历
     * (本月和下个月)
     */
    public Map<LocalDate, List<DailyTaskStatsDTO>> generate(Integer days) {
        Map<LocalDate, List<DailyTaskStatsDTO>> calendarMap = new TreeMap<>();
        LocalDateTime beginTime = LocalDate.now().atTime(0, 0, 0);

        LocalDateTime endTime = LocalDate.now().plusMonths(1)
                .with(TemporalAdjusters.lastDayOfMonth())
                .atTime(23, 59, 59);

        // 获取所有启用的任务
        List<PatrolTask> patrolTaskList = patrolTaskMapper.selectPatrolTaskList(PatrolTask.builder().isEnable("0").build());
        log.info("查询所有启用任务计划，数量：{}", patrolTaskList.size());

        // 逐个任务解析，生成未来任务
        for (PatrolTask task : patrolTaskList) {
            if (StringUtils.isEmpty(task.getDevType())) {
                continue;
            }
            try {
                // 1.定期执行
                if (ExecStatus.REGULAR.getCode().equals(task.getExecutionStatus())) {
                    if (task.getFixedStartTime() != null) {
                        LocalDateTime startTime = task.getFixedStartTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
                        // 判断开始时间是否在范围内
                        if (startTime.isAfter(beginTime) && startTime.isBefore(endTime)) {
                            DailyTaskStatsDTO dto = DailyTaskStatsDTO.builder()
                                    .id(task.getTaskCode())
                                    .name(task.getTaskName())
                                    .taskState(TaskStateEnum.PENDING.getCode())
                                    .startTime(startTime)
                                    .build();
                            dto.formatTimeAndStatus();

                            calendarMap.computeIfAbsent(startTime.toLocalDate(), k -> new ArrayList<>()).add(dto);
                        }
                        log.info("定时任务{}【{}】生成完毕", task.getTaskId(), task.getTaskName());
                    }
                }
                // 2.周期执行
                else if (ExecStatus.PERIODIC.getCode().equals(task.getExecutionStatus())) {
                    LocalDateTime cycleStartDateTime = task.getCycleStartTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
                    LocalDateTime cycleEndTime = task.getCycleEndTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();

                    LocalDateTime startLdt = beginTime.isBefore(cycleStartDateTime) ? cycleStartDateTime : beginTime;
                    LocalDateTime endLdt = endTime.isBefore(cycleEndTime) ? endTime : cycleEndTime;

                    String[] executeTimes = task.getCycleExecuteTime().split(StringUtils.COMMA);
                    // 按周
                    if (!StringUtils.isEmpty(task.getCycleWeek())) {
                        String[] weeks = task.getCycleWeek().split(StringUtils.COMMA);
                        // 解析星期列表
                        List<DayOfWeek> weekDays = Arrays.stream(weeks).map(this::parseDayOfWeek).collect(Collectors.toList());

                        log.info("周期任务（周）{}【{}】生成开始，开始时间：{}，结束时间：{}，执行时间：{}", task.getTaskId(), task.getTaskName(), startLdt, endLdt, executeTimes);
                        // 判断范围内的每一天的星期是否为目标星期
                        for (LocalDateTime date = startLdt; date.isBefore(endLdt); date = date.plusDays(1)) {
                            // 判断是否为目标星期
                            for (int i = 0; i < weekDays.size(); i++) {
                                DayOfWeek weekDay = weekDays.get(i);
                                if (date.getDayOfWeek().equals(weekDay)) {
                                    LocalTime executeTime = LocalTime.parse(executeTimes[i]);
                                    LocalDateTime startTime = LocalDateTime.of(date.toLocalDate(), executeTime);
                                    DailyTaskStatsDTO dto = DailyTaskStatsDTO.builder()
                                            .id(task.getTaskCode())
                                            .name(task.getTaskName())
                                            .taskState(TaskStateEnum.PENDING.getCode())
                                            .startTime(startTime)
                                            .build();
                                    dto.formatTimeAndStatus();

                                    calendarMap.computeIfAbsent(startTime.toLocalDate(), k -> new ArrayList<>()).add(dto);
                                }
                            }
                        }
                        log.info("周期任务（周）{}【{}】生成完毕", task.getTaskId(), task.getTaskName());
                    }
                    // 按月
                    else if (!StringUtils.isEmpty(task.getCycleMonth())) {
                        String[] monthDays = task.getCycleMonth().split(StringUtils.COMMA);
                        List<Integer> targetDays = Arrays.stream(monthDays).map(Integer::parseInt).collect(Collectors.toList());

                        log.info("周期任务（月）{}【{}】生成开始，开始时间：{}，结束时间：{}，执行时间：{}", task.getTaskId(), task.getTaskName(), startLdt, endLdt, executeTimes);
                        for (LocalDateTime date = startLdt; date.isBefore(endLdt); date = date.plusDays(1)) {
                            // 判断是否为目标日期
                            for (int i = 0; i < targetDays.size(); i++) {
                                int targetDay = targetDays.get(i);
                                if (date.getDayOfMonth() == targetDay) {
                                    LocalTime executeTime = LocalTime.parse(executeTimes[i]);
                                    LocalDateTime startTime = LocalDateTime.of(date.toLocalDate(), executeTime);
                                    DailyTaskStatsDTO dto = DailyTaskStatsDTO.builder()
                                            .id(task.getTaskCode())
                                            .name(task.getTaskName())
                                            .taskState(TaskStateEnum.PENDING.getCode())
                                            .startTime(startTime)
                                            .build();
                                    dto.formatTimeAndStatus();

                                    calendarMap.computeIfAbsent(startTime.toLocalDate(), k -> new ArrayList<>()).add(dto);
                                }
                            }
                        }
                        log.info("周期任务（月）{}【{}】生成完毕", task.getTaskId(), task.getTaskName());
                    }
                }
                // 3.间隔执行
                else if (ExecStatus.INTERVAL.getCode().equals(task.getExecutionStatus())) {
                    // 小时间隔
                    if (IntervalType.BY_HOUR.getCode().equals(task.getIntervalType())) {
                        // 按小时执行
                        int intervalNumber = task.getIntervalNumber();
                        if (intervalNumber > 0) {
                            LocalDateTime intervalStartTime = task.getIntervalStartTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
                            LocalDateTime intervalEndTime = task.getIntervalEndTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
                            LocalTime executeTime = LocalTime.parse(task.getIntervalExecuteTime());

                            LocalDateTime startLdt = intervalStartTime;
                            LocalDateTime endLdt = endTime.isAfter(intervalEndTime) ? intervalEndTime : endTime;

                            startLdt = LocalDateTime.of(startLdt.toLocalDate(), executeTime);
                            for (LocalDateTime date = startLdt; date.isBefore(endLdt); date = date.plusHours(intervalNumber)) {
                                if (date.isBefore(beginTime)) {
                                    continue;
                                }
                                DailyTaskStatsDTO dto = DailyTaskStatsDTO.builder()
                                        .name(task.getTaskName())
                                        .taskState(TaskStateEnum.PENDING.getCode())
                                        .startTime(date)
                                        .build();
                                dto.formatTimeAndStatus();

                                calendarMap.computeIfAbsent(date.toLocalDate(), k -> new ArrayList<>()).add(dto);
                            }
                        }

                    }
                    log.info("间隔任务（月）{}【{}】生成完毕", task.getTaskId(), task.getTaskName());
                }

            } catch (Exception e) {
                // 无效任务，跳过该任务
                log.error("任务异常，解析失败，taskId:{}", task.getTaskId(), e);
            }
        }
        int totalCount = calendarMap.values().stream()
                .mapToInt(List::size)
                .sum();
        log.info("未来任务生成完毕，总任务数量：{}", totalCount);
        // 将每一天的任务按开始时间排序
        calendarMap.values().forEach(list -> list.sort(Comparator.comparing(DailyTaskStatsDTO::getStartTime)));
        return calendarMap;
    }

    /**
     * 获取指定年月日的未来计划
     * @param year
     * @param month
     * @param day
     * @return
     */
    public List<DailyTaskStatsDTO> getFutureCalendar(int year, int month, int day) {
        Map<LocalDate, List<DailyTaskStatsDTO>> futureCalendarMap = futureCalendarCache.get(FUTURE_CALENDAR);
        LocalDate date = LocalDate.of(year, month, day);
        List<DailyTaskStatsDTO> existingTasks = futureCalendarMap.computeIfAbsent(date, k -> new ArrayList<>());
        return existingTasks.stream()
                .filter(task -> task.getStartTime().isAfter(LocalDateTime.now()))
                .collect(Collectors.toList());
    }

    /**
     * 将中文星期转换为DayOfWeek枚举
     */
    private DayOfWeek parseDayOfWeek(String week) {
        switch (week) {
            case "星期一":
                return DayOfWeek.MONDAY;
            case "星期二":
                return DayOfWeek.TUESDAY;
            case "星期三":
                return DayOfWeek.WEDNESDAY;
            case "星期四":
                return DayOfWeek.THURSDAY;
            case "星期五":
                return DayOfWeek.FRIDAY;
            case "星期六":
                return DayOfWeek.SATURDAY;
            case "星期日":
                return DayOfWeek.SUNDAY;
            default:
                throw new IllegalArgumentException("不支持的星期: " + week);
        }
    }
}
