package com.lesingle.edu.service.impl;

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.CourseStatus;
import com.lesingle.edu.common.enums.ScheduleStatus;
import com.lesingle.edu.common.enums.TenantPackageStatus;
import com.lesingle.edu.common.exception.BusinessException;
import com.lesingle.edu.dto.request.CourseCreateRequest;
import com.lesingle.edu.dto.request.CourseUpdateRequest;
import com.lesingle.edu.dto.response.CourseLessonResponse;
import com.lesingle.edu.dto.response.CourseResponse;
import com.lesingle.edu.dto.response.LessonStepResponse;
import com.lesingle.edu.entity.*;
import com.lesingle.edu.mapper.*;
import com.lesingle.edu.service.CourseLessonService;
import com.lesingle.edu.service.CoursePackageService;
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.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 课程包服务实现类
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class CoursePackageServiceImpl extends ServiceImpl<CoursePackageMapper, CoursePackage>
        implements CoursePackageService {

    private final CoursePackageMapper coursePackageMapper;
    private final CourseLessonService courseLessonService;
    private final com.lesingle.edu.common.mapper.CoursePackageMapper coursePackageVoMapper;
    private final CourseCollectionPackageMapper collectionPackageMapper;
    private final TenantPackageMapper tenantPackageMapper;
    private final ThemeMapper themeMapper;
    private final SchedulePlanMapper schedulePlanMapper;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public CoursePackage createCourse(Long tenantId, CourseCreateRequest request) {
        log.info("创建租户课程，tenantId={}, name={}", tenantId, request.getName());
        CoursePackage entity = buildEntityFromRequest(request);
        entity.setTenantId(tenantId);
        entity.setIsSystem(0);
        entity.setStatus(resolveInitialStatus(request));
        entity.setUsageCount(0);
        coursePackageMapper.insert(entity);
        log.info("课程创建成功，id={}", entity.getId());
        return entity;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public CoursePackage createSystemCourse(CourseCreateRequest request) {
        log.info("创建系统课程，name={}", request.getName());
        CoursePackage entity = buildEntityFromRequest(request);
        entity.setTenantId(null);
        entity.setIsSystem(1);
        entity.setStatus(resolveInitialStatus(request));
        entity.setUsageCount(0);
        coursePackageMapper.insert(entity);
        log.info("系统课程创建成功，id={}", entity.getId());
        return entity;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public CoursePackage updateCourse(Long id, CourseUpdateRequest request) {
        log.info("更新课程，id={}", id);
        CoursePackage entity = getCourseById(id);
        applyUpdateRequest(entity, request);
        coursePackageMapper.updateById(entity);
        log.info("课程更新成功，id={}", id);
        return entity;
    }

    @Override
    public CoursePackage getCourseById(Long id) {
        CoursePackage entity = coursePackageMapper.selectById(id);
        if (entity == null) {
            throw new BusinessException("课程不存在，id=" + id);
        }
        return entity;
    }

    @Override
    public CoursePackage getCourseByIdWithTenantCheck(Long id, Long tenantId) {
        CoursePackage entity = getCourseById(id);
        if (entity.getTenantId() != null && !entity.getTenantId().equals(tenantId)) {
            throw new BusinessException("无权访问该课程");
        }
        return entity;
    }

    @Override
    public CourseResponse getCourseByIdWithLessons(Long id) {
        CoursePackage entity = getCourseById(id);
        CourseResponse response = coursePackageVoMapper.toVO(entity);
        // 填充主题名称
        if (entity.getThemeId() != null) {
            Theme theme = themeMapper.selectById(entity.getThemeId());
            if (theme != null) {
                response.setThemeName(theme.getName());
                response.setThemeColor(theme.getColor());
            }
        }
        List<CourseLesson> lessons = courseLessonService.findByCourseId(id);
        List<CourseLessonResponse> lessonResponses = lessons.stream()
                .map(this::toLessonResponse)
                .collect(Collectors.toList());
        response.setCourseLessons(lessonResponses);
        return response;
    }

    @Override
    public Page<CoursePackage> getCoursePage(Long tenantId, Integer pageNum, Integer pageSize,
                                             String keyword, String category, String status) {
        int current = pageNum != null && pageNum > 0 ? pageNum : 1;
        int size = pageSize != null && pageSize > 0 ? pageSize : 10;
        Page<CoursePackage> page = new Page<>(current, size);
        LambdaQueryWrapper<CoursePackage> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(CoursePackage::getTenantId, tenantId);
        if (StringUtils.hasText(keyword)) {
            wrapper.and(w -> w
                    .like(CoursePackage::getName, keyword)
                    .or().like(CoursePackage::getPictureBookName, keyword)
                    .or().like(CoursePackage::getCode, keyword));
        }
        if (StringUtils.hasText(category)) {
            wrapper.eq(CoursePackage::getCategory, category);
        }
        if (StringUtils.hasText(status)) {
            wrapper.eq(CoursePackage::getStatus, status);
        }
        wrapper.orderByDesc(CoursePackage::getUpdatedAt);
        return coursePackageMapper.selectPage(page, wrapper);
    }

    @Override
    public Page<CoursePackage> getSystemCoursePage(Integer pageNum, Integer pageSize,
                                                   String keyword, String category, String status,
                                                   String gradeTags, boolean reviewOnly) {
        int current = pageNum != null && pageNum > 0 ? pageNum : 1;
        int size = pageSize != null && pageSize > 0 ? pageSize : 10;
        Page<CoursePackage> page = new Page<>(current, size);
        LambdaQueryWrapper<CoursePackage> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(CoursePackage::getIsSystem, 1);
        if (StringUtils.hasText(keyword)) {
            wrapper.and(w -> w
                    .like(CoursePackage::getName, keyword)
                    .or().like(CoursePackage::getPictureBookName, keyword)
                    .or().like(CoursePackage::getCode, keyword));
        }
        if (StringUtils.hasText(category)) {
            wrapper.eq(CoursePackage::getCategory, category);
        }
        if (StringUtils.hasText(status)) {
            wrapper.eq(CoursePackage::getStatus, status);
        }
        // 年级筛选：gradeTags 为逗号分隔的字符串，如 "小班，中班，大班"
        // 数据库存储为 JSON 数组 ["小班","中班"]，需要使用 JSON_CONTAINS 匹配
        // 多选年级应该是 OR 关系（包含任意一个所选年级即可）
        if (StringUtils.hasText(gradeTags)) {
            String[] selectedGrades = gradeTags.split(",");
            wrapper.and(w -> {
                for (int i = 0; i < selectedGrades.length; i++) {
                    String grade = selectedGrades[i].trim();
                    if (StringUtils.hasText(grade)) {
                        if (i == 0) {
                            w.apply("JSON_CONTAINS(grade_tags, {0})", "\"" + grade + "\"");
                        } else {
                            w.or().apply("JSON_CONTAINS(grade_tags, {0})", "\"" + grade + "\"");
                        }
                    }
                }
            });
        }
        if (reviewOnly) {
            wrapper.eq(CoursePackage::getStatus, CourseStatus.PENDING.getCode());
        }
        wrapper.orderByDesc(CoursePackage::getUpdatedAt);
        return coursePackageMapper.selectPage(page, wrapper);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteCourse(Long id) {
        getCourseById(id);
        coursePackageMapper.deleteById(id);
        log.info("课程删除成功，id={}", id);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void publishCourse(Long id) {
        CoursePackage entity = getCourseById(id);
        String st = entity.getStatus();
        if (CourseStatus.UNSUBMITTED.getCode().equals(st) || CourseStatus.PENDING.getCode().equals(st)) {
            entity.setStatus(CourseStatus.PUBLISHED.getCode());
            entity.setPublishedAt(LocalDateTime.now());
            coursePackageMapper.updateById(entity);
            log.info("课程发布成功，id={}", id);
            return;
        }
        throw new BusinessException("仅「未提交」或「待审核」状态的课程包可发布，当前状态不允许发布");
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void archiveCourse(Long id) {
        log.info("下架课程包，id={}", id);

        CoursePackage entity = getCourseById(id);

        // 检查课程包是否被排课使用
        Long scheduleCount = schedulePlanMapper.selectCount(
                new LambdaQueryWrapper<com.lesingle.edu.entity.SchedulePlan>()
                        .eq(com.lesingle.edu.entity.SchedulePlan::getCoursePackageId, id)
                        .ne(com.lesingle.edu.entity.SchedulePlan::getStatus, ScheduleStatus.CANCELLED.getCode())
        );

        if (scheduleCount > 0) {
            log.warn("下架课程包失败，有 {} 条排课记录正在使用此课程包，id={}", scheduleCount, id);
            throw new BusinessException("该课程包正在被 " + scheduleCount + " 条排课记录使用，无法下架");
        }

        entity.setStatus(CourseStatus.ARCHIVED.getCode());
        coursePackageMapper.updateById(entity);

        log.info("课程包下架成功，id={}", id);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void submitCourse(Long id) {
        CoursePackage entity = getCourseById(id);
        String st = entity.getStatus();
        if (CourseStatus.DRAFT.getCode().equals(st)) {
            throw new BusinessException("草稿状态请先完成全部步骤并保存为「未提交」后再提交审核");
        }
        if (CourseStatus.PENDING.getCode().equals(st)) {
            throw new BusinessException("课程已在审核中");
        }
        if (CourseStatus.PUBLISHED.getCode().equals(st)) {
            throw new BusinessException("课程已发布，无需提交审核");
        }
        entity.setStatus(CourseStatus.PENDING.getCode());
        entity.setSubmittedAt(LocalDateTime.now());
        coursePackageMapper.updateById(entity);
        log.info("课程提交审核成功，id={}", id);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void rejectCourse(Long id, String comment) {
        CoursePackage entity = getCourseById(id);
        entity.setStatus(CourseStatus.REJECTED.getCode());
        entity.setReviewComment(comment);
        entity.setReviewedAt(LocalDateTime.now());
        coursePackageMapper.updateById(entity);
        log.info("课程驳回成功，id={}", id);
    }

    @Override
    public List<CoursePackage> getCoursesByTenantId(Long tenantId) {
        return coursePackageMapper.selectList(
                new LambdaQueryWrapper<CoursePackage>()
                        .eq(CoursePackage::getTenantId, tenantId)
                        .eq(CoursePackage::getStatus, CourseStatus.PUBLISHED.getCode())
                        .orderByDesc(CoursePackage::getUsageCount));
    }

    @Override
    public List<CoursePackage> getTenantPackageCourses(Long tenantId, String keyword, String grade, String domain, String lessonType) {
        List<Long> collectionIds = tenantPackageMapper.selectList(
                new LambdaQueryWrapper<TenantPackage>()
                        .eq(TenantPackage::getTenantId, tenantId)
                        .eq(TenantPackage::getStatus, TenantPackageStatus.ACTIVE.getCode())
                        .isNotNull(TenantPackage::getCollectionId))
                .stream()
                .map(TenantPackage::getCollectionId)
                .distinct()
                .collect(Collectors.toList());

        if (collectionIds.isEmpty()) {
            return new ArrayList<>();
        }

        List<Long> packageIds = collectionPackageMapper.selectList(
                new LambdaQueryWrapper<CourseCollectionPackage>()
                        .in(CourseCollectionPackage::getCollectionId, collectionIds))
                .stream()
                .map(CourseCollectionPackage::getPackageId)
                .distinct()
                .collect(Collectors.toList());

        if (packageIds.isEmpty()) {
            return new ArrayList<>();
        }

        // lessonType 筛选：仅保留包含该类型环节的课程包
        if (StringUtils.hasText(lessonType)) {
            Set<Long> courseIdsWithLesson = courseLessonService.findCourseIdsByLessonType(lessonType).stream()
                    .collect(Collectors.toSet());
            packageIds = packageIds.stream().filter(courseIdsWithLesson::contains).collect(Collectors.toList());
            if (packageIds.isEmpty()) {
                return new ArrayList<>();
            }
        }

        LambdaQueryWrapper<CoursePackage> wrapper = new LambdaQueryWrapper<>();
        wrapper.in(CoursePackage::getId, packageIds)
                .eq(CoursePackage::getStatus, CourseStatus.PUBLISHED.getCode());

        if (StringUtils.hasText(keyword)) {
            wrapper.and(w -> w
                    .like(CoursePackage::getName, keyword)
                    .or().like(CoursePackage::getPictureBookName, keyword)
                    .or().like(CoursePackage::getCode, keyword));
        }
        if (StringUtils.hasText(grade)) {
            String gradeLower = grade.toLowerCase();
            wrapper.and(w -> w
                    .like(CoursePackage::getGradeTags, grade)
                    .or().like(CoursePackage::getGradeTags, gradeLower)
                    .or().like(CoursePackage::getGradeTags, "\"" + grade + "\"")
                    .or().like(CoursePackage::getGradeTags, "\"" + gradeLower + "\""));
        }
        if (StringUtils.hasText(domain)) {
            String domainLower = domain.toLowerCase();
            wrapper.and(w -> w
                    .like(CoursePackage::getDomainTags, domain)
                    .or().like(CoursePackage::getDomainTags, domainLower)
                    .or().like(CoursePackage::getDomainTags, "\"" + domain + "\"")
                    .or().like(CoursePackage::getDomainTags, "\"" + domainLower + "\""));
        }
        wrapper.orderByDesc(CoursePackage::getUsageCount);
        return coursePackageMapper.selectList(wrapper);
    }

    @Override
    public Page<CoursePackage> getTenantPackageCoursePage(Long tenantId, Integer pageNum, Integer pageSize,
                                                         String keyword, String grade, String domain, String lessonType, String status) {
        int current = pageNum != null && pageNum > 0 ? pageNum : 1;
        int size = pageSize != null && pageSize > 0 ? pageSize : 10;

        // 1. 学校 -> 套餐：查询租户已购买的套餐（collection）
        List<Long> collectionIds = tenantPackageMapper.selectList(
                new LambdaQueryWrapper<TenantPackage>()
                        .eq(TenantPackage::getTenantId, tenantId)
                        .eq(TenantPackage::getStatus, TenantPackageStatus.ACTIVE.getCode())
                        .isNotNull(TenantPackage::getCollectionId))
                .stream()
                .map(TenantPackage::getCollectionId)
                .distinct()
                .collect(Collectors.toList());

        if (collectionIds.isEmpty()) {
            return new Page<>(current, size, 0);
        }

        // 2. 套餐 -> 课程包：查询套餐下的课程包 ID
        List<Long> packageIds = collectionPackageMapper.selectList(
                new LambdaQueryWrapper<CourseCollectionPackage>()
                        .in(CourseCollectionPackage::getCollectionId, collectionIds))
                .stream()
                .map(CourseCollectionPackage::getPackageId)
                .distinct()
                .collect(Collectors.toList());

        if (packageIds.isEmpty()) {
            return new Page<>(current, size, 0);
        }

        // 2.5 lessonType 筛选：仅保留包含该类型环节的课程包
        if (StringUtils.hasText(lessonType)) {
            Set<Long> courseIdsWithLesson = courseLessonService.findCourseIdsByLessonType(lessonType).stream()
                    .collect(Collectors.toSet());
            packageIds = packageIds.stream().filter(courseIdsWithLesson::contains).collect(Collectors.toList());
            if (packageIds.isEmpty()) {
                return new Page<>(current, size, 0);
            }
        }

        // 3. 分页查询课程包（gradeTags 年级筛选，domainTags 领域筛选）
        Page<CoursePackage> page = new Page<>(current, size);
        LambdaQueryWrapper<CoursePackage> wrapper = new LambdaQueryWrapper<>();
        wrapper.in(CoursePackage::getId, packageIds)
                .eq(CoursePackage::getStatus, status != null ? status : CourseStatus.PUBLISHED.getCode());

        if (StringUtils.hasText(keyword)) {
            wrapper.and(w -> w
                    .like(CoursePackage::getName, keyword)
                    .or().like(CoursePackage::getPictureBookName, keyword)
                    .or().like(CoursePackage::getCode, keyword));
        }
        // 年级筛选：匹配 gradeTags（支持中文小班/中班/大班 或英文 SMALL/MIDDLE/BIG）
        if (StringUtils.hasText(grade)) {
            String gradeLower = grade.toLowerCase();
            wrapper.and(w -> w
                    .like(CoursePackage::getGradeTags, grade)
                    .or().like(CoursePackage::getGradeTags, gradeLower)
                    .or().like(CoursePackage::getGradeTags, "\"" + grade + "\"")
                    .or().like(CoursePackage::getGradeTags, "\"" + gradeLower + "\""));
        }
        // 领域筛选：匹配 domainTags（支持五大领域中文或英文码 HEALTH/LANGUAGE/SOCIAL/SCIENCE/ART）
        if (StringUtils.hasText(domain)) {
            String domainLower = domain.toLowerCase();
            wrapper.and(w -> w
                    .like(CoursePackage::getDomainTags, domain)
                    .or().like(CoursePackage::getDomainTags, domainLower)
                    .or().like(CoursePackage::getDomainTags, "\"" + domain + "\"")
                    .or().like(CoursePackage::getDomainTags, "\"" + domainLower + "\""));
        }
        wrapper.orderByDesc(CoursePackage::getUsageCount);
        return coursePackageMapper.selectPage(page, wrapper);
    }

    private static String resolveInitialStatus(CourseCreateRequest request) {
        if (!StringUtils.hasText(request.getStatus())) {
            return CourseStatus.DRAFT.getCode();
        }
        String code = request.getStatus();
        if (CourseStatus.UNSUBMITTED.getCode().equals(code) || CourseStatus.DRAFT.getCode().equals(code)) {
            return code;
        }
        return CourseStatus.DRAFT.getCode();
    }

    private CoursePackage buildEntityFromRequest(CourseCreateRequest request) {
        CoursePackage entity = new CoursePackage();
        entity.setName(request.getName());
        entity.setCode(request.getCode());
        entity.setDescription(request.getDescription());
        entity.setCoverUrl(request.getCoverUrl());
        entity.setCoverImagePath(request.getCoverImagePath());
        entity.setCategory(request.getCategory());
        entity.setAgeRange(request.getAgeRange());
        entity.setDifficultyLevel(request.getDifficultyLevel());
        entity.setDurationMinutes(request.getDurationMinutes());
        entity.setObjectives(request.getObjectives());
        entity.setCoreContent(request.getCoreContent());
        entity.setIntroSummary(request.getIntroSummary());
        entity.setIntroHighlights(request.getIntroHighlights());
        entity.setIntroGoals(request.getIntroGoals());
        entity.setIntroSchedule(request.getIntroSchedule());
        entity.setIntroKeyPoints(request.getIntroKeyPoints());
        entity.setIntroMethods(request.getIntroMethods());
        entity.setIntroEvaluation(request.getIntroEvaluation());
        entity.setIntroNotes(request.getIntroNotes());
        entity.setScheduleRefData(emptyToNull(request.getScheduleRefData()));
        entity.setEnvironmentConstruction(request.getEnvironmentConstruction());
        entity.setThemeId(request.getThemeId());
        entity.setPictureBookName(request.getPictureBookName());
        entity.setEbookPaths(emptyToNull(request.getEbookPaths()));
        entity.setAudioPaths(emptyToNull(request.getAudioPaths()));
        entity.setVideoPaths(emptyToNull(request.getVideoPaths()));
        entity.setOtherResources(emptyToNull(request.getOtherResources()));
        entity.setPptPath(request.getPptPath());
        entity.setPptName(request.getPptName());
        entity.setPosterPaths(request.getPosterPaths());
        entity.setTools(request.getTools());
        entity.setStudentMaterials(request.getStudentMaterials());
        entity.setLessonPlanData(emptyToNull(request.getLessonPlanData()));
        entity.setActivitiesData(emptyToNull(request.getActivitiesData()));
        entity.setAssessmentData(emptyToNull(request.getAssessmentData()));
        entity.setGradeTags(emptyToNull(request.getGradeTags()));
        entity.setDomainTags(emptyToNull(request.getDomainTags()));
        entity.setHasCollectiveLesson(request.getHasCollectiveLesson() != null && request.getHasCollectiveLesson() ? 1 : 0);
        return entity;
    }

    private void applyUpdateRequest(CoursePackage entity, CourseUpdateRequest request) {
        if (StringUtils.hasText(request.getName())) entity.setName(request.getName());
        if (request.getCode() != null) entity.setCode(request.getCode());
        if (request.getDescription() != null) entity.setDescription(request.getDescription());
        if (request.getCoverUrl() != null) entity.setCoverUrl(request.getCoverUrl());
        if (request.getCoverImagePath() != null) entity.setCoverImagePath(request.getCoverImagePath());
        if (request.getCategory() != null) entity.setCategory(request.getCategory());
        if (request.getAgeRange() != null) entity.setAgeRange(request.getAgeRange());
        if (request.getDifficultyLevel() != null) entity.setDifficultyLevel(request.getDifficultyLevel());
        if (request.getDurationMinutes() != null) entity.setDurationMinutes(request.getDurationMinutes());
        if (request.getObjectives() != null) entity.setObjectives(request.getObjectives());
        if (StringUtils.hasText(request.getStatus())) entity.setStatus(request.getStatus());
        if (request.getCoreContent() != null) entity.setCoreContent(request.getCoreContent());
        if (request.getIntroSummary() != null) entity.setIntroSummary(request.getIntroSummary());
        if (request.getIntroHighlights() != null) entity.setIntroHighlights(request.getIntroHighlights());
        if (request.getIntroGoals() != null) entity.setIntroGoals(request.getIntroGoals());
        if (request.getIntroSchedule() != null) entity.setIntroSchedule(request.getIntroSchedule());
        if (request.getIntroKeyPoints() != null) entity.setIntroKeyPoints(request.getIntroKeyPoints());
        if (request.getIntroMethods() != null) entity.setIntroMethods(request.getIntroMethods());
        if (request.getIntroEvaluation() != null) entity.setIntroEvaluation(request.getIntroEvaluation());
        if (request.getIntroNotes() != null) entity.setIntroNotes(request.getIntroNotes());
        if (request.getScheduleRefData() != null) entity.setScheduleRefData(emptyToNull(request.getScheduleRefData()));
        if (request.getEnvironmentConstruction() != null) entity.setEnvironmentConstruction(request.getEnvironmentConstruction());
        if (request.getThemeId() != null) entity.setThemeId(request.getThemeId());
        if (request.getPictureBookName() != null) entity.setPictureBookName(request.getPictureBookName());
        if (request.getEbookPaths() != null) entity.setEbookPaths(emptyToNull(request.getEbookPaths()));
        if (request.getAudioPaths() != null) entity.setAudioPaths(emptyToNull(request.getAudioPaths()));
        if (request.getVideoPaths() != null) entity.setVideoPaths(emptyToNull(request.getVideoPaths()));
        if (request.getOtherResources() != null) entity.setOtherResources(emptyToNull(request.getOtherResources()));
        if (request.getPptPath() != null) entity.setPptPath(request.getPptPath());
        if (request.getPptName() != null) entity.setPptName(request.getPptName());
        if (request.getPosterPaths() != null) entity.setPosterPaths(request.getPosterPaths());
        if (request.getTools() != null) entity.setTools(request.getTools());
        if (request.getStudentMaterials() != null) entity.setStudentMaterials(request.getStudentMaterials());
        if (request.getLessonPlanData() != null) entity.setLessonPlanData(emptyToNull(request.getLessonPlanData()));
        if (request.getActivitiesData() != null) entity.setActivitiesData(emptyToNull(request.getActivitiesData()));
        if (request.getAssessmentData() != null) entity.setAssessmentData(emptyToNull(request.getAssessmentData()));
        if (request.getGradeTags() != null) entity.setGradeTags(emptyToNull(request.getGradeTags()));
        if (request.getDomainTags() != null) entity.setDomainTags(emptyToNull(request.getDomainTags()));
        if (request.getHasCollectiveLesson() != null) entity.setHasCollectiveLesson(request.getHasCollectiveLesson() ? 1 : 0);
    }

    /**
     * 将空字符串转为 null，避免 MySQL JSON 列报错 "The document is empty"
     */
    private static String emptyToNull(String value) {
        return StringUtils.hasText(value) ? value : null;
    }

    private CourseLessonResponse toLessonResponse(CourseLesson lesson) {
        List<LessonStep> steps = courseLessonService.findSteps(lesson.getId());
        List<LessonStepResponse> stepResponses = steps.stream()
                .map(s -> LessonStepResponse.builder()
                        .id(s.getId())
                        .lessonId(s.getLessonId())
                        .name(s.getName())
                        .content(s.getContent())
                        .duration(s.getDuration())
                        .objective(s.getObjective())
                        .resourceIds(s.getResourceIds())
                        .sortOrder(s.getSortOrder())
                        .createdAt(s.getCreatedAt())
                        .updatedAt(s.getUpdatedAt())
                        .build())
                .collect(Collectors.toList());

        return CourseLessonResponse.builder()
                .id(lesson.getId())
                .courseId(lesson.getCourseId())
                .lessonType(lesson.getLessonType())
                .name(lesson.getName())
                .description(lesson.getDescription())
                .duration(lesson.getDuration())
                .videoPath(lesson.getVideoPath())
                .videoName(lesson.getVideoName())
                .pptPath(lesson.getPptPath())
                .pptName(lesson.getPptName())
                .pdfPath(lesson.getPdfPath())
                .pdfName(lesson.getPdfName())
                .objectives(lesson.getObjectives())
                .preparation(lesson.getPreparation())
                .extension(lesson.getExtension())
                .reflection(lesson.getReflection())
                .assessmentData(lesson.getAssessmentData())
                .useTemplate(lesson.getUseTemplate())
                .sortOrder(lesson.getSortOrder())
                .steps(stepResponses)
                .createdAt(lesson.getCreatedAt())
                .updatedAt(lesson.getUpdatedAt())
                .build();
    }
}
