package cn.teammodel.utils;

import cn.hutool.core.lang.Pair;
import cn.teammodel.common.CommonConstant;
import cn.teammodel.config.exception.ServiceException;
import cn.teammodel.model.entity.school.School;
import lombok.AllArgsConstructor;
import lombok.Data;

import java.time.*;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalAdjusters;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 学校中年级,学年,日期相关的工具类
 * @author winter
 * &#064;create  2023-11-28 10:15
 */
public class SchoolDateUtil {

    /**
    * 通过当前时间与学校的学期安排获取当前的学年
     * 学校学期:
     * <pre>
     * "semesters": [
     *         {
     *           "name": "下学期",
     *           "start": 0,
     *           "month": 3,
     *           "day": 1,
     *           "id": "2"
     *         },
     *         {
     *           "name": "上学期",
     *           "start": 1,
     *           "month": 9,
     *           "day": 1,
     *           "id": "1"
     *         }
     *         <pre/>
     * <p>
     *     示例: 传参 2023.11.28 返回 2023学年上学期
     *     注意: 处理跨年问题 -> 2024.1.1, 应该也是 2023学年上学期,同时你需要注意,semesters 可能不止两个学期
     * </p>
    */
    public static semesterModel getSemesterByNow(List<School.Semester> semesters, LocalDate date) {
        if (semesters == null || semesters.isEmpty() || date == null) {
            throw new ServiceException("semesters and date must not be null or empty.");
        }

        // 将 semester 根据大小排序
        semesters.sort(Comparator.comparing(s -> LocalDate.of(date.getYear(), s.getMonth(), s.getDay())));
        School.Semester start = semesters.stream().filter(s -> s.getStart() == 1).findFirst().orElseThrow(() -> new ServiceException("学期异常, 未找到新学年开学日期"));
        LocalDate startDate = LocalDate.of(date.getYear(), start.getMonth(), start.getDay());

        // i = 0 的日期是开学最早, i = size - 1 是开学最晚
        int size = semesters.size();
        // 拿到最晚开学的日期,将年份 - 1后,与头节点组成可跨年区间
        School.Semester lastSemester = semesters.get(size - 1);
        School.Semester firstSemester = semesters.get(0);
        LocalDate firstSemesterDate = LocalDate.of(date.getYear(), firstSemester.getMonth(), firstSemester.getDay());

        // 动态替换
        LocalDate lastSemesterStart = LocalDate.of(date.getYear() - 1, lastSemester.getMonth(), lastSemester.getDay());
        String lastSemesterId = lastSemester.getId();

        for (School.Semester semester : semesters) {
            // 特判头尾的提起,分别将学期区间提前和延后一年(不用延后),例如 2023.3.1 与 2023.9.1 我们分别向添加一个节点,划分其为 3 个区间:
            // 2022.9.1 - 2023.3.1, 2023.3.1 - 2023.9.1, 2023.9.1 - 2024.3.1
            LocalDate curSemesterStart = LocalDate.of(date.getYear(), semester.getMonth(), semester.getDay());

            // 跨年的情况
            if(date.isBefore(firstSemesterDate)) {
                // 这里是去年的最后一个开学日期(last 还未开始迭代)
                String academicYearId = generateCurAcademicId(lastSemesterStart.getYear(), lastSemesterId);
                return new semesterModel(academicYearId, lastSemesterId, lastSemesterStart.atStartOfDay(), semester.getId(), curSemesterStart.atStartOfDay());
            }

            if (date.isEqual(lastSemesterStart) || date.isAfter(lastSemesterStart) && date.isBefore(curSemesterStart)) {
                // 如果当前时间在开学时期之前, 学年则是去年
                if (date.isBefore(startDate)) {
                    String academicYearId = generateCurAcademicId(date.getYear() - 1, lastSemesterId);
                    return new semesterModel(academicYearId, lastSemesterId, lastSemesterStart.atStartOfDay(), semester.getId(), curSemesterStart.atStartOfDay());
                }
                String academicYearId = generateCurAcademicId(date.getYear(), lastSemesterId);
                return new semesterModel(academicYearId, lastSemesterId, lastSemesterStart.atStartOfDay(), semester.getId(), curSemesterStart.atStartOfDay());
            }
            // 迭代
            lastSemesterStart = curSemesterStart;
            lastSemesterId = semester.getId();
        }

        // 剩下的时间段, 学年应该就是最后一个学年. 学期末就应该是下一年(year + 1)的第一个节点
        String academicYearId = generateCurAcademicId(date.getYear(), lastSemester.getId());
        return new semesterModel(
                academicYearId,
                lastSemester.getId(),
                LocalDate.of(date.getYear(), lastSemester.getMonth(), lastSemester.getDay()).atStartOfDay(),
                firstSemester.getId(),
                LocalDate.of(date.getYear() + 1, firstSemester.getMonth(), firstSemester.getDay()).atStartOfDay()
        );
    }

    /**
    * 返回 ID -> 学年(组合 id: 学年-semesterId -> 2023-{semesterId})
    */
    private static String generateCurAcademicId(int academicYear, String semesterId) {
        return academicYear + CommonConstant.DASH + semesterId;
    }

    public static String calculateAcademicYearId(List<School.Semester> semesters,  LocalDate date) {
        return getSemesterByNow(semesters, date).getAcademicYearId();
    }
    /**
     * 计算一学期相对的周数<br/>
     * 如果 timeStamp 为 null, 则返回当前学期的总周数
     */
    public static long calculateWeekNum(LocalDateTime startDateTime, LocalDateTime endDateTime, Long timeStamp) {
        if (timeStamp == null) {
            return ChronoUnit.WEEKS.between(startDateTime, endDateTime) + 1;
        } else {
            LocalDateTime curDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(timeStamp), ZoneOffset.of("+08:00"));
            startDateTime = startDateTime.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY))
                    .withHour(0)
                    .withMinute(0)
                    .withSecond(0)
                    .withNano(0);
            // 如果传参没在学期范围内, 返回 -1
            if (curDateTime.isBefore(startDateTime) || curDateTime.isAfter(endDateTime)) {
                return -1;
            }
            return ChronoUnit.WEEKS.between(startDateTime, curDateTime) + 1;
        }
    }

    /**
    * 创建初始化的 Map
    */
    public static Map<Long, Integer> createEmptyWeekMap(long totalWeek) {
        Map<Long, Integer> initialMap = new HashMap<>();
        for (long i = 1; i <= totalWeek; i++) {
            initialMap.put(i, 0);
        }
        return initialMap;
    }

    public static Pair<Long, Long> adjustTimeRangeToCurrentWeekIfNull(Long startTime, Long endTime) {
        if (startTime == null || endTime == null) {
            LocalDateTime mondayOfCurWeek = LocalDateTime.now().with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY))
                    .withHour(0)
                    .withMinute(0)
                    .withSecond(0)
                    .withNano(0);
            startTime = mondayOfCurWeek.atZone(ZoneOffset.systemDefault()).toInstant().toEpochMilli();
            endTime = Instant.now().toEpochMilli();
        }
        return new Pair<>(startTime, endTime);
    }


    @Data
    @AllArgsConstructor
    public static class semesterModel {
        private String academicYearId;
        private String startSemesterId;
        private LocalDateTime startDatetime;
        private String endSemesterId;
        private LocalDateTime endDatetime;
    }

}
