package com.lesingle.edu.service.impl;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.lesingle.edu.common.enums.GenericStatus;
import com.lesingle.edu.common.enums.ScheduleSourceType;
import com.lesingle.edu.common.enums.ScheduleStatus;
import com.lesingle.edu.common.enums.ScheduleRepeatType;
import com.lesingle.edu.common.enums.ErrorCode;
import com.lesingle.edu.common.exception.BusinessException;
import com.lesingle.edu.dto.request.SchedulePlanCreateRequest;
import com.lesingle.edu.dto.request.SchedulePlanUpdateRequest;
import com.lesingle.edu.dto.request.ScheduleCreateByClassesRequest;
import com.lesingle.edu.dto.response.CalendarViewResponse;
import com.lesingle.edu.dto.response.ConflictCheckResult;
import com.lesingle.edu.dto.response.LessonTypeInfo;
import com.lesingle.edu.dto.response.SchedulePlanResponse;
import com.lesingle.edu.entity.Clazz;
import com.lesingle.edu.entity.CourseLesson;
import com.lesingle.edu.entity.CoursePackage;
import com.lesingle.edu.entity.SchedulePlan;
import com.lesingle.edu.entity.Teacher;
import com.lesingle.edu.enums.LessonTypeEnum;
import com.lesingle.edu.mapper.ClazzMapper;
import com.lesingle.edu.mapper.CoursePackageMapper;
import com.lesingle.edu.mapper.SchedulePlanMapper;
import com.lesingle.edu.mapper.TeacherMapper;
import com.lesingle.edu.service.CourseLessonService;
import com.lesingle.edu.service.ScheduleConflictService;
import com.lesingle.edu.service.SchoolScheduleService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 学校端排课服务实现
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class SchoolScheduleServiceImpl extends ServiceImpl<SchedulePlanMapper, SchedulePlan>
        implements SchoolScheduleService {

    private final SchedulePlanMapper schedulePlanMapper;
    private final ScheduleConflictService scheduleConflictService;
    private final CourseLessonService courseLessonService;
    private final CoursePackageMapper courseMapper;
    private final ClazzMapper clazzMapper;
    private final TeacherMapper teacherMapper;

    /**
     * 最大重复周数限制
     */
    private static final int MAX_REPEAT_WEEKS = 52;

    @Override
    @Transactional
    public List<SchedulePlan> createSchedule(Long tenantId, SchedulePlanCreateRequest request) {
        log.info("创建排课: tenantId={}, name={}", tenantId, request.getName());

        // 生成排课日期列表
        List<LocalDate> dates = generateScheduleDates(request);
        log.info("生成排课日期数量: {}", dates.size());

        // 检测冲突
        List<ConflictCheckResult.ConflictInfo> allConflicts = new ArrayList<>();
        for (LocalDate date : dates) {
            ConflictCheckResult result = scheduleConflictService.checkConflict(
                    tenantId, request.getClassId(), request.getTeacherId(),
                    date, request.getScheduledTime());
            if (result.getHasConflict()) {
                allConflicts.addAll(result.getConflicts());
            }
        }

        if (!allConflicts.isEmpty()) {
            log.warn("排课冲突: 共 {} 个冲突", allConflicts.size());
            throw new BusinessException(ErrorCode.INVALID_PARAMETER,
                    "排课时间冲突，请调整排课时间或日期");
        }

        // 创建排课记录
        List<SchedulePlan> plans = new ArrayList<>();
        for (LocalDate date : dates) {
            SchedulePlan plan = new SchedulePlan();
            plan.setTenantId(tenantId);
            plan.setName(request.getName());
            plan.setClassId(request.getClassId());
            plan.setCourseId(request.getCourseId());
            plan.setCoursePackageId(request.getCoursePackageId());
            plan.setLessonType(request.getLessonType());
            plan.setTeacherId(request.getTeacherId());
            plan.setScheduledDate(date);
            plan.setScheduledTime(request.getScheduledTime());
            plan.setWeekDay(date.getDayOfWeek().getValue());
            plan.setRepeatType(request.getRepeatType() != null ? request.getRepeatType() : "NONE");
            plan.setRepeatEndDate(request.getRepeatEndDate());
            plan.setSource(StringUtils.hasText(request.getSource()) ? request.getSource() : ScheduleSourceType.SCHOOL.getCode());
            plan.setNote(request.getNote());
            plan.setStatus(GenericStatus.ACTIVE.getCode());
            plan.setReminderSent(0);

            schedulePlanMapper.insert(plan);
            plans.add(plan);
        }

        log.info("排课创建成功，共创建 {} 条记录", plans.size());
        return plans;
    }

    @Override
    @Transactional
    public SchedulePlan updateSchedule(Long id, Long tenantId, SchedulePlanUpdateRequest request) {
        log.info("更新排课: id={}, tenantId={}", id, tenantId);

        SchedulePlan plan = getScheduleById(id, tenantId);

        // 如果更新了时间相关的字段，需要检测冲突
        if (request.getScheduledDate() != null || request.getScheduledTime() != null) {
            LocalDate date = request.getScheduledDate() != null ? request.getScheduledDate() : plan.getScheduledDate();
            String time = request.getScheduledTime() != null ? request.getScheduledTime() : plan.getScheduledTime();
            Long classId = plan.getClassId();
            Long teacherId = request.getTeacherId() != null ? request.getTeacherId() : plan.getTeacherId();

            ConflictCheckResult result = scheduleConflictService.checkConflict(
                    tenantId, classId, teacherId, date, time, id);
            if (result.getHasConflict()) {
                throw new BusinessException(ErrorCode.INVALID_PARAMETER, "排课时间冲突");
            }
        }

        // 更新字段
        if (StringUtils.hasText(request.getName())) {
            plan.setName(request.getName());
        }
        if (request.getCourseId() != null) {
            plan.setCourseId(request.getCourseId());
        }
        if (request.getTeacherId() != null) {
            plan.setTeacherId(request.getTeacherId());
        }
        if (request.getScheduledDate() != null) {
            plan.setScheduledDate(request.getScheduledDate());
            plan.setWeekDay(request.getScheduledDate().getDayOfWeek().getValue());
        }
        if (StringUtils.hasText(request.getScheduledTime())) {
            plan.setScheduledTime(request.getScheduledTime());
        }
        if (request.getWeekDay() != null) {
            plan.setWeekDay(request.getWeekDay());
        }
        if (StringUtils.hasText(request.getRepeatType())) {
            plan.setRepeatType(request.getRepeatType());
        }
        if (request.getRepeatEndDate() != null) {
            plan.setRepeatEndDate(request.getRepeatEndDate());
        }
        if (request.getNote() != null) {
            plan.setNote(request.getNote());
        }
        if (StringUtils.hasText(request.getStatus())) {
            plan.setStatus(request.getStatus());
        }

        schedulePlanMapper.updateById(plan);
        log.info("排课更新成功: id={}", id);
        return plan;
    }

    @Override
    @Transactional
    public void cancelSchedule(Long id, Long tenantId) {
        log.info("取消排课: id={}, tenantId={}", id, tenantId);

        SchedulePlan plan = getScheduleById(id, tenantId);
        plan.setStatus(ScheduleStatus.CANCELLED.getCode());
        schedulePlanMapper.updateById(plan);

        log.info("排课取消成功: id={}", id);
    }

    @Override
    @Transactional
    public void deleteSchedule(Long id, Long tenantId) {
        log.info("删除排课：id={}, tenantId={}", id, tenantId);

        SchedulePlan plan = getScheduleById(id, tenantId);

        // 只允许删除已取消的排课
        if (!ScheduleStatus.isCancelled(plan.getStatus())) {
            throw new BusinessException(ErrorCode.INVALID_PARAMETER,
                    "只能删除已取消的排课");
        }

        schedulePlanMapper.deleteById(plan);
        log.info("排课删除成功：id={}", id);
    }

    @Override
    public SchedulePlan getScheduleById(Long id, Long tenantId) {
        SchedulePlan plan = schedulePlanMapper.selectById(id);
        if (plan == null) {
            throw new BusinessException(ErrorCode.DATA_NOT_FOUND, "排课不存在");
        }
        if (!tenantId.equals(plan.getTenantId())) {
            throw new BusinessException(ErrorCode.FORBIDDEN, "无权访问该排课");
        }
        return plan;
    }

    @Override
    public Page<SchedulePlan> getSchedulePage(Long tenantId, Integer pageNum, Integer pageSize,
                                              LocalDate startDate, LocalDate endDate,
                                              Long classId, Long teacherId, String status) {
        log.debug("查询排课列表: tenantId={}, classId={}, teacherId={}", tenantId, classId, teacherId);

        Page<SchedulePlan> page = new Page<>(pageNum, pageSize);
        LambdaQueryWrapper<SchedulePlan> wrapper = new LambdaQueryWrapper<>();

        wrapper.eq(SchedulePlan::getTenantId, tenantId);

        if (startDate != null) {
            wrapper.ge(SchedulePlan::getScheduledDate, startDate);
        }
        if (endDate != null) {
            wrapper.le(SchedulePlan::getScheduledDate, endDate);
        }
        if (classId != null) {
            wrapper.eq(SchedulePlan::getClassId, classId);
        }
        if (teacherId != null) {
            wrapper.eq(SchedulePlan::getTeacherId, teacherId);
        }
        if (StringUtils.hasText(status)) {
            if (GenericStatus.ACTIVE.getCode().equalsIgnoreCase(status)) {
                wrapper.in(SchedulePlan::getStatus, GenericStatus.ACTIVE.getCode(), ScheduleStatus.SCHEDULED.getCode());
            } else {
                wrapper.eq(SchedulePlan::getStatus, status);
            }
        }

        wrapper.orderByAsc(SchedulePlan::getScheduledDate, SchedulePlan::getScheduledTime);

        return schedulePlanMapper.selectPage(page, wrapper);
    }

    @Override
    public Map<String, Object> getTimetable(Long tenantId, Long classId, LocalDate startDate, LocalDate endDate) {
        log.debug("获取课程表: tenantId={}, classId={}, startDate={}, endDate={}",
                tenantId, classId, startDate, endDate);

        // 默认查询本周
        if (startDate == null) {
            startDate = LocalDate.now().minusDays(LocalDate.now().getDayOfWeek().getValue() - 1);
        }
        if (endDate == null) {
            endDate = startDate.plusDays(6);
        }

        // 查询排课数据
        LambdaQueryWrapper<SchedulePlan> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(SchedulePlan::getTenantId, tenantId)
                .ge(SchedulePlan::getScheduledDate, startDate)
                .le(SchedulePlan::getScheduledDate, endDate)
                .ne(SchedulePlan::getStatus, ScheduleStatus.CANCELLED.getCode());

        if (classId != null) {
            wrapper.eq(SchedulePlan::getClassId, classId);
        }

        wrapper.orderByAsc(SchedulePlan::getScheduledDate, SchedulePlan::getScheduledTime);

        List<SchedulePlan> plans = schedulePlanMapper.selectList(wrapper);

        // 转换为带展示字段的 Response
        List<SchedulePlanResponse> responses = plans.stream()
                .map(this::toSchedulePlanResponse)
                .collect(Collectors.toList());

        // 按日期分组（使用 dateKey 字符串，便于前端按 YYYY-MM-DD 查找）
        Map<String, List<SchedulePlanResponse>> groupedByDate = new LinkedHashMap<>();
        for (SchedulePlanResponse r : responses) {
            if (r.getScheduledDate() != null) {
                String dateKey = r.getScheduledDate().toString();
                groupedByDate.computeIfAbsent(dateKey, k -> new ArrayList<>()).add(r);
            }
        }

        // 按星期几分组
        Map<Integer, List<SchedulePlanResponse>> groupedByWeekDay = responses.stream()
                .filter(r -> r.getWeekDay() != null)
                .collect(Collectors.groupingBy(SchedulePlanResponse::getWeekDay, TreeMap::new, Collectors.toList()));

        Map<String, Object> result = new LinkedHashMap<>();
        result.put("startDate", startDate);
        result.put("endDate", endDate);
        result.put("byDate", groupedByDate);
        result.put("byWeekDay", groupedByWeekDay);
        result.put("total", plans.size());

        return result;
    }

    @Override
    @Transactional
    public List<SchedulePlan> batchCreateSchedules(Long tenantId, List<SchedulePlanCreateRequest> requests) {
        log.info("批量创建排课: tenantId={}, count={}", tenantId, requests.size());

        List<SchedulePlan> allPlans = new ArrayList<>();
        for (SchedulePlanCreateRequest request : requests) {
            List<SchedulePlan> plans = createSchedule(tenantId, request);
            allPlans.addAll(plans);
        }

        log.info("批量排课创建成功，共创建 {} 条记录", allPlans.size());
        return allPlans;
    }

    @Override
    public ConflictCheckResult checkConflict(Long tenantId, Long classId, Long teacherId,
                                             LocalDate scheduledDate, String scheduledTime) {
        log.debug("检测冲突: tenantId={}, classId={}, teacherId={}, date={}, time={}",
                tenantId, classId, teacherId, scheduledDate, scheduledTime);
        return scheduleConflictService.checkConflict(tenantId, classId, teacherId, scheduledDate, scheduledTime);
    }

    @Override
    public List<LessonTypeInfo> getCoursePackageLessonTypes(Long tenantId, Long coursePackageId) {
        log.info("获取课程包的课程类型列表: tenantId={}, coursePackageId={}", tenantId, coursePackageId);

        // 1. 优先从 course_lesson 表获取课程环节类型（course_package_course 表已废弃）
        List<CourseLesson> lessons = courseLessonService.findByCourseId(coursePackageId);

        if (!lessons.isEmpty()) {
            Map<String, LessonTypeInfo> lessonTypeMap = new LinkedHashMap<>();
            for (CourseLesson lesson : lessons) {
                String code = lesson.getLessonType();
                if (!StringUtils.hasText(code)) continue;
                String displayName = getLessonTypeDisplayName(code);
                lessonTypeMap.compute(code, (k, v) -> {
                    if (v == null) {
                        return LessonTypeInfo.builder()
                                .lessonType(code)
                                .lessonTypeName(displayName)
                                .count(1L)
                                .build();
                    }
                    v.setCount(v.getCount() + 1);
                    return v;
                });
            }
            List<LessonTypeInfo> result = new ArrayList<>(lessonTypeMap.values());
            log.info("课程包课程类型统计完成(来自course_lesson): coursePackageId={}, types={}", coursePackageId, result.size());
            return result;
        }

        // 2. 无 course_lesson 时，从 course_package.schedule_ref_data 解析
        CoursePackage pkg = courseMapper.selectById(coursePackageId);
        if (pkg == null) {
            log.warn("课程包不存在: coursePackageId={}", coursePackageId);
            return new ArrayList<>();
        }

        String scheduleRefData = pkg.getScheduleRefData();
        if (!StringUtils.hasText(scheduleRefData)) {
            log.warn("课程包无排课参考数据: coursePackageId={}", coursePackageId);
            return new ArrayList<>();
        }

        Map<String, LessonTypeInfo> lessonTypeMap = new LinkedHashMap<>();
        try {
            JSONArray jsonArray = JSON.parseArray(scheduleRefData);
            if (jsonArray != null && !jsonArray.isEmpty()) {
                for (int i = 0; i < jsonArray.size(); i++) {
                    JSONObject item = jsonArray.getJSONObject(i);
                    if (item != null && item.containsKey("lessonType")) {
                        String lessonTypeName = item.getString("lessonType");
                        if (StringUtils.hasText(lessonTypeName)) {
                            String code = convertLessonTypeNameToCode(lessonTypeName);
                            lessonTypeMap.compute(code, (k, v) -> {
                                if (v == null) {
                                    return LessonTypeInfo.builder()
                                            .lessonType(code)
                                            .lessonTypeName(lessonTypeName)
                                            .count(1L)
                                            .build();
                                }
                                v.setCount(v.getCount() + 1);
                                return v;
                            });
                        }
                    }
                }
            }
        } catch (Exception e) {
            log.warn("解析课程排课参考数据失败: coursePackageId={}, error={}", coursePackageId, e.getMessage());
        }

        List<LessonTypeInfo> result = new ArrayList<>(lessonTypeMap.values());
        log.info("课程包课程类型统计完成(来自schedule_ref_data): coursePackageId={}, types={}", coursePackageId, result.size());
        return result;
    }

    private String getLessonTypeDisplayName(String code) {
        LessonTypeEnum e = LessonTypeEnum.fromCode(code);
        return e != null ? e.getDescription() : code;
    }

    /**
     * 将中文课程类型名称转换为英文代码
     */
    private String convertLessonTypeNameToCode(String lessonTypeName) {
        if (lessonTypeName == null) {
            return "UNKNOWN";
        }

        return switch (lessonTypeName) {
            case "导入课" -> "INTRODUCTION";
            case "集体课" -> "COLLECTIVE";
            case "五大领域语言课" -> "LANGUAGE";
            case "五大领域艺术课" -> "ART";
            case "五大领域科学课" -> "SCIENCE";
            case "五大领域社会课" -> "SOCIETY";
            case "五大领域健康课" -> "HEALTH";
            default -> "UNKNOWN";
        };
    }

    @Override
    @Transactional
    public List<SchedulePlan> createSchedulesByClasses(Long tenantId, ScheduleCreateByClassesRequest request) {
        log.info("批量创建排课（按班级）: tenantId={}, classCount={}",
                tenantId, request.getClassIds().size());

        // 验证课程类型
        if (!com.lesingle.edu.enums.LessonTypeEnum.isValidCode(request.getLessonType())) {
            throw new BusinessException(ErrorCode.INVALID_PARAMETER, "无效的课程类型: " + request.getLessonType());
        }

        List<SchedulePlan> allPlans = new ArrayList<>();

        // 为每个班级创建一条排课记录
        for (Long classId : request.getClassIds()) {
            SchedulePlanCreateRequest createRequest = new SchedulePlanCreateRequest();
            createRequest.setName(generateScheduleName(request));
            createRequest.setClassId(classId);
            createRequest.setCourseId(request.getCourseId());
            createRequest.setCoursePackageId(request.getCoursePackageId());
            createRequest.setLessonType(request.getLessonType());
            createRequest.setTeacherId(request.getTeacherId());
            createRequest.setScheduledDate(request.getScheduledDate());
            createRequest.setScheduledTime(request.getScheduledTime());
            createRequest.setRepeatType(request.getRepeatType());
            createRequest.setRepeatEndDate(request.getRepeatEndDate());
            createRequest.setSource(ScheduleSourceType.SCHOOL.getCode());
            createRequest.setNote(request.getNote());

            // 检测冲突
            ConflictCheckResult result = scheduleConflictService.checkConflict(
                    tenantId, classId, request.getTeacherId(),
                    request.getScheduledDate(), request.getScheduledTime());
            if (result.getHasConflict()) {
                log.warn("班级 {} 排课冲突，跳过该班级", classId);
                continue;
            }

            // 创建单次排课
            List<SchedulePlan> plans = createSchedule(tenantId, createRequest);
            allPlans.addAll(plans);
        }

        log.info("批量排课创建成功，共创建 {} 条记录", allPlans.size());
        return allPlans;
    }

    @Override
    public CalendarViewResponse getCalendarViewData(Long tenantId, LocalDate startDate, LocalDate endDate,
                                                       Long classId, Long teacherId) {
        log.info("获取日历视图数据: tenantId={}, startDate={}, endDate={}, classId={}, teacherId={}",
                tenantId, startDate, endDate, classId, teacherId);

        // 默认查询当月
        if (startDate == null) {
            startDate = LocalDate.now().withDayOfMonth(1);
        }
        if (endDate == null) {
            endDate = startDate.plusMonths(1).minusDays(1);
        }

        // 查询排课数据
        LambdaQueryWrapper<SchedulePlan> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(SchedulePlan::getTenantId, tenantId)
                .ge(SchedulePlan::getScheduledDate, startDate)
                .le(SchedulePlan::getScheduledDate, endDate)
                .ne(SchedulePlan::getStatus, ScheduleStatus.CANCELLED.getCode());

        if (classId != null) {
            wrapper.eq(SchedulePlan::getClassId, classId);
        }
        if (teacherId != null) {
            wrapper.eq(SchedulePlan::getTeacherId, teacherId);
        }

        wrapper.orderByAsc(SchedulePlan::getScheduledDate, SchedulePlan::getScheduledTime);

        List<SchedulePlan> plans = schedulePlanMapper.selectList(wrapper);

        // 按日期分组
        Map<String, List<CalendarViewResponse.DayScheduleItem>> schedulesByDate = new LinkedHashMap<>();
        DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");

        for (SchedulePlan plan : plans) {
            String dateKey = plan.getScheduledDate().format(dateFormatter);
            CalendarViewResponse.DayScheduleItem item = CalendarViewResponse.DayScheduleItem.builder()
                    .id(plan.getId())
                    .className(getClassNameForCalendar(plan))
                    .coursePackageName(getCoursePackageName(plan))
                    .lessonType(plan.getLessonType())
                    .lessonTypeName(getLessonTypeName(plan))
                    .teacherName(getTeacherName(plan))
                    .scheduledTime(plan.getScheduledTime())
                    .status(plan.getStatus())
                    .build();

            schedulesByDate.computeIfAbsent(dateKey, k -> new ArrayList<>()).add(item);
        }

        return CalendarViewResponse.builder()
                .startDate(startDate)
                .endDate(endDate)
                .schedules(schedulesByDate)
                .build();
    }

    /**
     * 生成排课名称
     */
    private String generateScheduleName(ScheduleCreateByClassesRequest request) {
        // TODO: 根据课程包名称和课程类型生成排课名称
        return request.getLessonType();
    }

    @Override
    public SchedulePlanResponse toSchedulePlanResponse(SchedulePlan plan) {
        String className = null;
        if (plan.getClassId() != null) {
            Clazz clazz = clazzMapper.selectById(plan.getClassId());
            className = clazz != null ? clazz.getName() : null;
        }
        String courseName = null;
        String coursePackageName = null;
        if (plan.getCourseId() != null) {
            CoursePackage cp = courseMapper.selectById(plan.getCourseId());
            if (cp != null) {
                courseName = cp.getName();
            }
        }
        if (plan.getCoursePackageId() != null && !plan.getCoursePackageId().equals(plan.getCourseId())) {
            CoursePackage pkg = courseMapper.selectById(plan.getCoursePackageId());
            coursePackageName = pkg != null ? pkg.getName() : null;
        } else if (plan.getCourseId() != null && courseName != null) {
            coursePackageName = courseName;
        }
        String teacherName = null;
        if (plan.getTeacherId() != null) {
            Teacher teacher = teacherMapper.selectById(plan.getTeacherId());
            teacherName = teacher != null ? teacher.getName() : null;
        }
        String lessonTypeName = plan.getLessonType() != null ? getLessonTypeDisplayName(plan.getLessonType()) : "";
        return SchedulePlanResponse.builder()
                .id(plan.getId())
                .tenantId(plan.getTenantId())
                .name(plan.getName())
                .classId(plan.getClassId())
                .className(className)
                .courseId(plan.getCourseId())
                .courseName(courseName)
                .coursePackageId(plan.getCoursePackageId())
                .coursePackageName(coursePackageName != null ? coursePackageName : courseName)
                .lessonType(plan.getLessonType())
                .lessonTypeName(lessonTypeName)
                .teacherId(plan.getTeacherId())
                .teacherName(teacherName)
                .scheduledDate(plan.getScheduledDate())
                .scheduledTime(plan.getScheduledTime())
                .weekDay(plan.getWeekDay())
                .repeatType(plan.getRepeatType())
                .repeatEndDate(plan.getRepeatEndDate())
                .source(plan.getSource())
                .note(plan.getNote())
                .status(plan.getStatus())
                .createdAt(plan.getCreatedAt())
                .updatedAt(plan.getUpdatedAt())
                .build();
    }

    /**
     * 获取班级名称（用于日历显示）
     */
    private String getClassNameForCalendar(SchedulePlan plan) {
        if (plan.getClassId() == null) return "";
        Clazz clazz = clazzMapper.selectById(plan.getClassId());
        return clazz != null ? clazz.getName() : "班级" + plan.getClassId();
    }

    /**
     * 获取课程包名称（用于日历显示）
     */
    private String getCoursePackageName(SchedulePlan plan) {
        Long id = plan.getCoursePackageId() != null ? plan.getCoursePackageId() : plan.getCourseId();
        if (id == null) return "";
        CoursePackage pkg = courseMapper.selectById(id);
        return pkg != null ? pkg.getName() : "课程包" + id;
    }

    /**
     * 获取课程类型名称
     */
    private String getLessonTypeName(SchedulePlan plan) {
        if (plan.getLessonType() != null) {
            com.lesingle.edu.enums.LessonTypeEnum typeEnum =
                    com.lesingle.edu.enums.LessonTypeEnum.fromCode(plan.getLessonType());
            if (typeEnum != null) {
                return typeEnum.getDescription();
            }
        }
        return "";
    }

    /**
     * 获取教师名称（用于日历显示）
     */
    private String getTeacherName(SchedulePlan plan) {
        if (plan.getTeacherId() == null) return "";
        Teacher teacher = teacherMapper.selectById(plan.getTeacherId());
        return teacher != null ? teacher.getName() : "教师" + plan.getTeacherId();
    }

    /**
     * 根据重复规则生成排课日期列表
     */
    private List<LocalDate> generateScheduleDates(SchedulePlanCreateRequest request) {
        List<LocalDate> dates = new ArrayList<>();

        LocalDate startDate = request.getScheduledDate();
        if (startDate == null) {
            throw new BusinessException(ErrorCode.INVALID_PARAMETER, "排课日期不能为空");
        }

        String repeatType = request.getRepeatType();
        if (!StringUtils.hasText(repeatType) || ScheduleRepeatType.NONE.getCode().equals(repeatType)) {
            // 单次排课
            dates.add(startDate);
            return dates;
        }

        // 重复排课
        LocalDate repeatEndDate = request.getRepeatEndDate();
        if (repeatEndDate == null) {
            throw new BusinessException(ErrorCode.INVALID_PARAMETER, "重复排课截止日期不能为空");
        }

        // 检查重复周数限制
        long weeks = java.time.temporal.ChronoUnit.WEEKS.between(startDate, repeatEndDate);
        if (weeks > MAX_REPEAT_WEEKS) {
            throw new BusinessException(ErrorCode.INVALID_PARAMETER,
                    "重复周数不能超过 " + MAX_REPEAT_WEEKS + " 周");
        }

        if (ScheduleRepeatType.WEEKLY.getCode().equals(repeatType)) {
            // 每周重复
            LocalDate current = startDate;
            while (!current.isAfter(repeatEndDate)) {
                dates.add(current);
                current = current.plusWeeks(1);
            }
        } else if (ScheduleRepeatType.BIWEEKLY.getCode().equals(repeatType)) {
            // 双周重复
            LocalDate current = startDate;
            while (!current.isAfter(repeatEndDate)) {
                dates.add(current);
                current = current.plusWeeks(2);
            }
        } else if (ScheduleRepeatType.DAILY.getCode().equals(repeatType)) {
            // 每日重复
            LocalDate current = startDate;
            while (!current.isAfter(repeatEndDate)) {
                dates.add(current);
                current = current.plusDays(1);
            }
        } else {
            // 未知类型，默认单次
            dates.add(startDate);
        }

        return dates;
    }

}
