package com.fs.course.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fs.common.core.domain.R;
import com.fs.common.utils.DateUtils;
import com.fs.common.utils.PubFun;
import com.fs.common.utils.date.DateUtil;
import com.fs.common.utils.date.TimeTypeEnum;
import com.fs.course.domain.FsUserCoursePeriod;
import com.fs.course.domain.FsUserCoursePeriodDays;
import com.fs.course.domain.FsUserCourseVideo;
import com.fs.course.mapper.FsUserCoursePeriodDaysMapper;
import com.fs.course.mapper.FsUserCoursePeriodMapper;
import com.fs.course.mapper.FsUserCourseVideoMapper;
import com.fs.course.mapper.FsUserCourseVideoRedPackageMapper;
import com.fs.course.param.BatchAddCourseSectionParam;
import com.fs.course.param.CompanyRedPacketParam;
import com.fs.course.param.CourseAnalysisParam;
import com.fs.course.param.PeriodCountParam;
import com.fs.course.service.IFsUserCoursePeriodDaysService;
import com.fs.course.service.IFsUserCourseVideoService;
import com.fs.course.vo.FsPeriodCountVO;
import com.fs.course.vo.PeriodRedPacketVO;
import com.fs.course.vo.UpdateCourseTimeVo;
import com.fs.course.vo.newfs.FsCourseAnalysisCountVO;
import com.fs.his.mapper.FsUserMapper;
import com.fs.his.vo.OptionsVO;
import com.fs.system.domain.SysConfig;
import com.fs.system.mapper.SysConfigMapper;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 营期课程Service业务层处理
 *
 * @author fs
 * @date 2025-04-16
 */
@Service
@Slf4j
public class FsUserCoursePeriodDaysServiceImpl extends ServiceImpl<FsUserCoursePeriodDaysMapper, FsUserCoursePeriodDays> implements IFsUserCoursePeriodDaysService {

    private final FsUserCoursePeriodMapper fsUserCoursePeriodMapper;

    private final FsUserMapper fsUserMapper;
    private final SysConfigMapper sysConfigMapper;

    private final FsUserCoursePeriodDaysMapper fsUserCoursePeriodDaysMapper;

    @Lazy
    private final IFsUserCourseVideoService fsUserCourseVideoService;

    public FsUserCoursePeriodDaysServiceImpl(
            FsUserCoursePeriodMapper fsUserCoursePeriodMapper,
            FsUserMapper fsUserMapper,
            SysConfigMapper sysConfigMapper,
            FsUserCoursePeriodDaysMapper fsUserCoursePeriodDaysMapper,
            @Lazy IFsUserCourseVideoService fsUserCourseVideoService) {
        this.fsUserCoursePeriodMapper = fsUserCoursePeriodMapper;
        this.fsUserMapper = fsUserMapper;
        this.sysConfigMapper = sysConfigMapper;
        this.fsUserCoursePeriodDaysMapper = fsUserCoursePeriodDaysMapper;
        this.fsUserCourseVideoService = fsUserCourseVideoService;
    }

    @Autowired
    private FsUserCourseVideoRedPackageMapper fsUserCourseVideoRedPackageMapper;

    /**
     * 查询营期课程
     *
     * @param id 营期课程主键
     * @return 营期课程
     */
    @Override
    public FsUserCoursePeriodDays selectFsUserCoursePeriodDaysById(Long id)
    {
        return baseMapper.selectFsUserCoursePeriodDaysById(id);
    }

    /**
     * 查询营期课程列表
     *
     * @param fsUserCoursePeriodDays 营期课程
     * @return 营期课程
     */
    @Override
    public List<FsUserCoursePeriodDays> selectFsUserCoursePeriodDaysList(FsUserCoursePeriodDays fsUserCoursePeriodDays)
    {
        return baseMapper.selectFsUserCoursePeriodDaysList(fsUserCoursePeriodDays);
    }

    /**
     * 新增营期课程
     *
     * @param fsUserCoursePeriodDays 营期课程
     * @return 结果
     */
    @Override
    public int insertFsUserCoursePeriodDays(FsUserCoursePeriodDays fsUserCoursePeriodDays)
    {
        fsUserCoursePeriodDays.setCreateTime(DateUtils.getNowDate());
        return baseMapper.insert(fsUserCoursePeriodDays);
    }

    /**
     * 修改营期课程
     *
     * @param fsUserCoursePeriodDays 营期课程
     * @return 结果
     */
    @Override
    public int updateFsUserCoursePeriodDays(FsUserCoursePeriodDays fsUserCoursePeriodDays)
    {
        fsUserCoursePeriodDays.setUpdateTime(DateUtils.getNowDate());
        return baseMapper.updateById(fsUserCoursePeriodDays);
    }

    /**
     * 批量删除营期课程
     *
     * @param ids 需要删除的营期课程主键
     * @return 结果
     */
    @Override
    public int deleteFsUserCoursePeriodDaysByIds(Long[] ids)
    {
        int flag = 0;
        List<FsUserCoursePeriodDays> fsUserCoursePeriodDays = fsUserCoursePeriodDaysMapper.selectBatchIds(Arrays.asList(ids));
        List<Long> periodDayIds = fsUserCoursePeriodDays.stream().map(FsUserCoursePeriodDays::getId).collect(Collectors.toList());
        List<Long> getPeriodIds = fsUserCoursePeriodDays.stream().map(FsUserCoursePeriodDays::getPeriodId).collect(Collectors.toList());
        List<Long> videoIds = fsUserCoursePeriodDays.stream().map(FsUserCoursePeriodDays::getVideoId).collect(Collectors.toList());
        if(!periodDayIds.isEmpty()){
            flag = fsUserCoursePeriodDaysMapper.updateBatchDelFlag(periodDayIds.toArray(new Long[0]),1);
            //删除红包记录
            fsUserCourseVideoRedPackageMapper.deleteFsUserCourseVideoRedPackageByVedioIds(videoIds.toArray(new Long[0]),getPeriodIds.toArray(new Long[0]));
        }
        return flag;
    }

    /**
     * 删除营期课程信息
     *
     * @param id 营期课程主键
     * @return 结果
     */
    @Override
    public int deleteFsUserCoursePeriodDaysById(Long id)
    {
        return baseMapper.deleteFsUserCoursePeriodDaysById(id);
    }

    @Override
    public R addCourse(FsUserCoursePeriodDays entity) {
        FsUserCoursePeriod period = fsUserCoursePeriodMapper.selectFsUserCoursePeriodById(entity.getPeriodId());
        List<FsUserCoursePeriodDays> dayList = list(new QueryWrapper<FsUserCoursePeriodDays>().eq("period_id", period.getPeriodId()).eq("del_flag", "0"));

        // 如果开启了自由模式，则不需要检查营期天数限制
        if (period.getFreeMode() == null || period.getFreeMode() != 1) {
            long days;
            if (period.getPeriodType() == 2) {
                days = 1;
            } else {
                days = DateUtil.differenceTime(period.getPeriodStartingTime(), period.getPeriodEndTime(), TimeTypeEnum.DAY);
            }
            days++;
            if (dayList.size() + entity.getVideoIds().size() > days) return R.error("课程不能超过营期范围");
        }

        if (dayList.stream().anyMatch(e -> entity.getVideoIds().contains(e.getVideoId()))) return R.error("不能添加相同章节");
        AtomicInteger i = new AtomicInteger(0);
        FsUserCourseVideo fsUserCourseVideo = new FsUserCourseVideo();
        fsUserCourseVideo.setCourseId(entity.getCourseId());
        List<FsUserCourseVideo> videoList = fsUserCourseVideoService.selectFsUserCourseVideoListByCourseId(fsUserCourseVideo);
        Map<Long, FsUserCourseVideo> videoMap = PubFun.listToMapByGroupObject(videoList, FsUserCourseVideo::getVideoId);
        List<FsUserCoursePeriodDays> collect = entity.getVideoIds().stream().map(e -> {
            FsUserCourseVideo video = videoMap.get(e);
            FsUserCoursePeriodDays day = new FsUserCoursePeriodDays();
            day.setPeriodId(entity.getPeriodId());
            day.setLesson(dayList.size() + i.getAndIncrement());
            day.setDayDate(period.getPeriodStartingTime().plusDays(day.getLesson()));
            day.setCourseId(entity.getCourseId());
            if(entity.getStartTime() != null){
                day.setStartDateTime(LocalDateTime.of(day.getDayDate(), entity.getStartTime()));
            }else if(video.getViewStartTime() != null){
                day.setStartDateTime(LocalDateTime.of(day.getDayDate(), video.getViewStartTime()));
            }
            if(entity.getEndTime1() != null){
                day.setEndDateTime(LocalDateTime.of(day.getDayDate(), entity.getEndTime1()));
            }else if(video.getViewEndTime() != null){
                day.setEndDateTime(LocalDateTime.of(day.getDayDate(), video.getViewEndTime()));
            }
            SysConfig config = sysConfigMapper.selectConfigByConfigKey("joinTime.switch.config");
            if (ObjectUtils.isNotEmpty(config)&&config.getConfigValue().equals("1")){
                entity.setJoinTime(entity.getEndTime1());
            }
            if(entity.getJoinTime() != null){
                day.setLastJoinTime(LocalDateTime.of(day.getDayDate(), entity.getJoinTime()));
            }else if(video.getLastJoinTime() != null){
                day.setLastJoinTime(LocalDateTime.of(day.getDayDate(), video.getLastJoinTime()));
            }
            day.setVideoId(e);
            day.setCreateTime(new Date());

            // 设置课程状态：如果开启自由模式，默认为进行中（status=1）
            if (period.getFreeMode() != null && period.getFreeMode() == 1) {
                // 自由模式：直接设置为进行中
                day.setStatus(1);
            } else {
                // 固定模式：默认开启今天及以后的两天
                LocalDateTime compareDayTime = LocalDateTime.now();
                if(compareDayTime.isAfter(day.getStartDateTime()) && compareDayTime.isBefore(day.getEndDateTime())){
                    day.setStatus(1);
                } else if(compareDayTime.isBefore(day.getStartDateTime())){
                    day.setStatus(0);
                } else {
                    day.setStatus(2);
                }
            }
            return day;
        }).collect(Collectors.toList());
        super.saveBatch(collect);
        return R.ok();
    }

    /**
     * 批量添加课程小节
     *
     * @param param 批量添加参数
     * @return 操作结果
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public R batchAddCourseSections(BatchAddCourseSectionParam param) {
        try {
            // 2. 获取营期信息
            FsUserCoursePeriod period = fsUserCoursePeriodMapper.selectFsUserCoursePeriodById(param.getPeriodId());
            if (period == null) {
                return R.error("营期不存在");
            }

            // 3. 获取已存在的课程小节
            List<FsUserCoursePeriodDays> existingDays = list(new QueryWrapper<FsUserCoursePeriodDays>()
                    .eq("period_id", param.getPeriodId())
                    .eq("del_flag", "0")
                    .orderByAsc("lesson"));

            // 4. 计算营期总天数
            long totalDays;
            if (period.getPeriodType() == 2) {
                totalDays = 1;
            } else {
                totalDays = DateUtil.differenceTime(period.getPeriodStartingTime(), period.getPeriodEndTime(), TimeTypeEnum.DAY);
            }
            totalDays++;

            // 5. 验证是否超过营期范围（开启自由模式时跳过此验证）
            if (period.getFreeMode() == null || period.getFreeMode() != 1) {
                if (existingDays.size() + param.getVideoIds().size() > totalDays) {
                    return R.error("添加的课程数量超过营期范围，营期总天数：" + totalDays + "，当前已有：" + existingDays.size() + "，尝试添加：" + param.getVideoIds().size());
                }
            }

            // 6. 检查重复视频
            Set<Long> existingVideoIds = existingDays.stream()
                    .map(FsUserCoursePeriodDays::getVideoId)
                    .collect(Collectors.toSet());

            List<Long> duplicateVideoIds = param.getVideoIds().stream()
                    .filter(existingVideoIds::contains)
                    .collect(Collectors.toList());

            if (!duplicateVideoIds.isEmpty()) {
                if (param.getSkipDuplicate() != null && param.getSkipDuplicate()) {
                    // 跳过重复的视频
                    param.getVideoIds().removeAll(duplicateVideoIds);
                    log.info("跳过重复的视频ID：{}", duplicateVideoIds);
                    if (param.getVideoIds().isEmpty()) {
                        return R.ok("所有视频都已存在，跳过添加");
                    }
                } else {
                    return R.error("不能添加相同章节，重复的视频ID：" + duplicateVideoIds);
                }
            }

            // 7. 获取视频详情并按courseSort排序
            FsUserCourseVideo queryVideo = new FsUserCourseVideo();
            queryVideo.setCourseId(param.getCourseId());
            List<FsUserCourseVideo> videoList = fsUserCourseVideoService.selectFsUserCourseVideoListByCourseId(queryVideo);
            Map<Long, FsUserCourseVideo> videoMap = PubFun.listToMapByGroupObject(videoList, FsUserCourseVideo::getVideoId);

            // 8. 验证视频是否都存在
            for (Long videoId : param.getVideoIds()) {
                if (!videoMap.containsKey(videoId)) {
                    return R.error("视频ID：" + videoId + " 不存在于课程中");
                }
            }

            // 9. 根据courseSort对要添加的视频进行排序（从小到大）
            List<FsUserCourseVideo> sortedVideos = param.getVideoIds().stream()
                    .map(videoMap::get)
                    .sorted((v1, v2) -> {
                        Long sort1 = v1.getCourseSort() != null ? v1.getCourseSort() : Long.MAX_VALUE;
                        Long sort2 = v2.getCourseSort() != null ? v2.getCourseSort() : Long.MAX_VALUE;
                        return sort1.compareTo(sort2);
                    })
                    .collect(Collectors.toList());

            log.info("按courseSort排序后的视频顺序：");
            for (int i = 0; i < sortedVideos.size(); i++) {
                FsUserCourseVideo video = sortedVideos.get(i);
                log.info("  序号: {}, 视频ID: {}, 标题: {}, courseSort: {}",
                        i + 1, video.getVideoId(), video.getTitle(), video.getCourseSort());
            }

            // 10. 准备添加的小节数据，使用按courseSort排序后的视频列表
            List<FsUserCoursePeriodDays> newDays = new ArrayList<>();

            // 确定开始课次 - 从现有最大课次+1开始，确保连续性
            int startLesson;
            LocalDate startDate; // 新增：确定开始日期

            if (param.getStartLesson() != null) {
                startLesson = param.getStartLesson();
                // 如果指定了起始课次，仍然基于营期开始时间计算日期
                startDate = period.getPeriodStartingTime().plusDays(startLesson - 1);
            } else {
                // 获取现有最大课次，如果没有则从1开始
                startLesson = existingDays.stream()
                        .mapToInt(FsUserCoursePeriodDays::getLesson)
                        .max()
                        .orElse(0) + 1;

                // 关键修复：获取已存在课程的最后日期，从该日期的下一天开始
                if (!existingDays.isEmpty()) {
                    // 按日期排序，获取最后一个课程的日期
                    LocalDate lastDate = existingDays.stream()
                            .map(FsUserCoursePeriodDays::getDayDate)
                            .filter(date -> date != null)
                            .max(LocalDate::compareTo)
                            .orElse(period.getPeriodStartingTime().plusDays(startLesson - 2));

                    startDate = lastDate.plusDays(1); // 从最后日期的下一天开始
                    log.info("基于已存在课程的最后日期计算起始日期：最后日期={}, 新起始日期={}", lastDate, startDate);
                } else {
                    // 如果没有已存在的课程，从营期开始时间计算
                    startDate = period.getPeriodStartingTime();
                    log.info("营期暂无课程，从营期开始时间计算起始日期：{}", startDate);
                }
            }

            // 使用排序后的视频列表进行处理，确保按courseSort顺序添加
            for (int i = 0; i < sortedVideos.size(); i++) {
                FsUserCourseVideo video = sortedVideos.get(i);
                Long videoId = video.getVideoId();

                FsUserCoursePeriodDays newDay = new FsUserCoursePeriodDays();
                newDay.setPeriodId(param.getPeriodId());
                newDay.setCourseId(param.getCourseId());
                newDay.setVideoId(videoId);

                // 设置课次 - 严格按排序后的顺序递增，确保有序
                newDay.setLesson(startLesson + i);

                // 计算日期 - 修复：基于起始日期递增，而不是基于课次计算
                // 这样可以避免当已存在课程的日期与课次不对应时出现重复日期
                LocalDate courseDate = startDate.plusDays(i);
                newDay.setDayDate(courseDate);

                // 设置时间 - 优先使用参数中的时间，其次使用视频默认时间
                if (param.getStartTime() != null) {
                    newDay.setStartDateTime(LocalDateTime.of(courseDate, param.getStartTime()));
                } else if (video.getViewStartTime() != null) {
                    newDay.setStartDateTime(LocalDateTime.of(courseDate, video.getViewStartTime()));
                }

                if (param.getEndDateTime() != null) {
                    newDay.setEndDateTime(LocalDateTime.of(courseDate, param.getEndDateTime()));
                } else if (video.getViewEndTime() != null) {
                    newDay.setEndDateTime(LocalDateTime.of(courseDate, video.getViewEndTime()));
                }

                // 设置加入时间
                SysConfig config = sysConfigMapper.selectConfigByConfigKey("joinTime.switch.config");
                if (ObjectUtils.isNotEmpty(config) && config.getConfigValue().equals("1")) {
                    // 如果开启了配置，使用结束时间作为加入时间
                    newDay.setLastJoinTime(newDay.getEndDateTime());
                } else {
                    if (param.getJoinTime() != null) {
                        newDay.setLastJoinTime(LocalDateTime.of(courseDate, param.getJoinTime()));
                    } else if (video.getLastJoinTime() != null) {
                        newDay.setLastJoinTime(LocalDateTime.of(courseDate, video.getLastJoinTime()));
                    }
                }

                // 设置状态：如果开启自由模式，默认为进行中（status=1）
                if (period.getFreeMode() != null && period.getFreeMode() == 1) {
                    // 自由模式：直接设置为进行中
                    newDay.setStatus(1);
                } else {
                    // 固定模式：默认开启今天及以后的两天
                    LocalDate compareDay = LocalDate.now().plusDays(1);
                    if (courseDate.isBefore(compareDay)) {
                        newDay.setStatus(1); // 已开始
                    } else {
                        newDay.setStatus(0); // 未开始
                    }
                }

                newDay.setCreateTime(new Date());
                newDays.add(newDay);

                log.debug("准备添加课程：课次={}, 日期={}, 视频ID={}, 标题={}, courseSort={}",
                        newDay.getLesson(), newDay.getDayDate(), videoId, video.getTitle(), video.getCourseSort());
            }

            // 11. 批量保存，确保按courseSort排序的结果保存
            if (!newDays.isEmpty()) {
                // 再次按课次排序，确保数据库中的顺序正确（按照批量操作排序保障规范）
                newDays.sort(Comparator.comparing(FsUserCoursePeriodDays::getLesson));

                log.info("即将批量添加课程小节（按courseSort排序）：");
                for (int i = 0; i < newDays.size(); i++) {
                    FsUserCoursePeriodDays day = newDays.get(i);
                    FsUserCourseVideo video = videoMap.get(day.getVideoId());
                    log.info("  序号: {}, 课次: {}, 日期: {}, 视频ID: {}, 标题: {}, courseSort: {}",
                            i + 1, day.getLesson(), day.getDayDate(), day.getVideoId(),
                            video != null ? video.getTitle() : "Unknown",
                            video != null ? video.getCourseSort() : "null");
                }

                super.saveBatch(newDays);
                log.info("批量添加课程小节成功（按courseSort排序），营期ID：{}，添加数量：{}，课次范围：{}-{}",
                        param.getPeriodId(), newDays.size(),
                        newDays.get(0).getLesson(),
                        newDays.get(newDays.size() - 1).getLesson());
                return R.ok("成功按courseSort排序添加 " + newDays.size() + " 个课程小节，课次范围：" +
                        newDays.get(0).getLesson() + "-" + newDays.get(newDays.size() - 1).getLesson());
            } else {
                return R.ok("没有需要添加的课程小节");
            }

        } catch (Exception e) {
            log.error("批量添加课程小节失败，营期ID：{}，错误信息：{}", param.getPeriodId(), e.getMessage(), e);
            throw new RuntimeException("批量添加课程小节失败：" + e.getMessage(), e);
        }
    }

    @Override
    public R updateListCourseData(List<FsUserCoursePeriodDays> entity) {
        List<FsUserCoursePeriodDays> collect = entity.stream().map(e -> {
            FsUserCoursePeriodDays day = new FsUserCoursePeriodDays();
            day.setId(e.getId());
            day.setMoney(e.getMoney());
            return day;
        }).collect(Collectors.toList());
        super.updateBatchById(collect);
        return R.ok();
    }

    @Override
    public List<CompanyRedPacketParam> getCompanyByPeriod(Long periodId, Long videoId) {
        if(videoId == null ){
            return fsUserCoursePeriodMapper.selectCompanyByPeriod(periodId);
        }
        return fsUserCoursePeriodMapper.selectCompanyByPeriodVideo(periodId, videoId);
    }

    @Override
    public List<PeriodRedPacketVO> getPeriodRedPacketList(Long periodId, Long companyId) {
        return fsUserCoursePeriodMapper.selectPeriodRedPacket(periodId, companyId);
    }

    @Override
    public List<FsPeriodCountVO> periodCourseCount(PeriodCountParam param) {
        //1、查询课程视频信息
        FsUserCoursePeriodDays fsUserCoursePeriodDays = new FsUserCoursePeriodDays();
        fsUserCoursePeriodDays.setPeriodId(param.getPeriodId());
        fsUserCoursePeriodDays.setVideoIds(param.getVideoIdList());
        fsUserCoursePeriodDays.setMaxDate(param.getMaxDate());

        List<FsUserCoursePeriodDays> videoList = baseMapper.selectFsUserCoursePeriodDaysList(fsUserCoursePeriodDays);

        //2、查询统计
        CourseAnalysisParam courseAnalysisParam = new CourseAnalysisParam();
        courseAnalysisParam.setPeriodId(param.getPeriodId());
        courseAnalysisParam.setVideoIdList(param.getVideoIdList());
        courseAnalysisParam.setCompanyId(param.getCompanyId());
        List<FsCourseAnalysisCountVO> courseCountList = fsUserMapper.courseAnalysisWatchLog(courseAnalysisParam);
        List<FsCourseAnalysisCountVO> redPacketCountList = fsUserMapper.courseAnalysisRedPacketCount(courseAnalysisParam);
        List<FsCourseAnalysisCountVO> answerCountList = fsUserMapper.courseAnalysisAnswerCount(courseAnalysisParam);

        //3、转化为map
        Map<Long, FsCourseAnalysisCountVO> courseMap = courseCountList.stream()
                .collect(Collectors.toMap(
                        FsCourseAnalysisCountVO::getVideoId,
                        Function.identity()
                ));
        Map<Long, FsCourseAnalysisCountVO> redPacketMap = redPacketCountList.stream()
                .collect(Collectors.toMap(
                        FsCourseAnalysisCountVO::getVideoId,
                        Function.identity()
                ));
        Map<Long, FsCourseAnalysisCountVO> answerMap = answerCountList.stream()
                .collect(Collectors.toMap(
                        FsCourseAnalysisCountVO::getVideoId,
                        Function.identity()
                ));

        //4、处理数据
        return videoList.stream().map(v -> {
            FsPeriodCountVO allVO = new FsPeriodCountVO();
            BeanUtils.copyProperties(v, allVO);
            allVO.setTitle(v.getVideoName());

            FsCourseAnalysisCountVO countVO = getCourseAnalysisCountVO(v, courseMap, redPacketMap, answerMap);
            allVO.setCountDetailsVO(countVO);
            return allVO;
        }).collect(Collectors.toList());
    }

    @Override
    public R periodCourseMove(Long id, Long targetId) {
        // 参数校验
        if (id == null || targetId == null || id.equals(targetId)) {
            return R.error(400, "参数不合法");
        }

        // 批量查询数据
        List<Long> idList = Arrays.asList(id, targetId);
        List<FsUserCoursePeriodDays> fsUserCoursePeriodDays = baseMapper.selectBatchIds(idList);

        // 数据校验
        if (fsUserCoursePeriodDays.size() != 2) {
            return R.error(404, "当前数据或移动的目标数据不存在");
        }

        // 获取源数据和目标数据
        FsUserCoursePeriodDays source = fsUserCoursePeriodDays.get(0);
        FsUserCoursePeriodDays target = fsUserCoursePeriodDays.get(1);

        // 交换数据
        swapPeriodDaysData(source, target);

        // 批量更新
        try {
            fsUserCoursePeriodDaysMapper.batchUpdateCoursePeriodDays(Arrays.asList(source, target));
            return R.ok();
        } catch (RuntimeException e) {
            log.error("上移/下移异常，当前id：{}，目标id：{}", id, targetId, e);
            return R.error(500, "操作失败，请稍后重试");
        }
    }

    /**
     * 交换两个课程时间段的数据
     */
    private void swapPeriodDaysData(FsUserCoursePeriodDays source, FsUserCoursePeriodDays target) {
        // 保存源数据
        LocalDate tempDayDate = source.getDayDate();
        Integer tempLesson = source.getLesson();
        LocalDateTime tempStartDateTime = source.getStartDateTime();
        LocalDateTime tempEndDateTime = source.getEndDateTime();
        LocalDateTime tempLastJoinTime = source.getLastJoinTime();
        Integer status = source.getStatus();

        // 将目标数据赋给源
        source.setDayDate(target.getDayDate());
        source.setLesson(target.getLesson());
        source.setStartDateTime(target.getStartDateTime());
        source.setEndDateTime(target.getEndDateTime());
        source.setStatus(target.getStatus());
        source.setLastJoinTime(target.getLastJoinTime());

        // 将保存的源数据赋给目标
        target.setDayDate(tempDayDate);
        target.setLesson(tempLesson);
        target.setStatus(status);
        target.setStartDateTime(tempStartDateTime);
        target.setEndDateTime(tempEndDateTime);
        target.setLastJoinTime(tempLastJoinTime);
    }

    @Override
    public R updateCourseTime(UpdateCourseTimeVo vo) {
        // 1. 查询数据库，获取需要更新的营期日期记录列表
        List<FsUserCoursePeriodDays> list = fsUserCoursePeriodDaysMapper.selectBatchIds(vo.getIds());
        // 2. 生成从 startTime 到 endTime1 之间的所有日期列表
        List<LocalDate> dateList = new ArrayList<>();
        LocalDate startDate = vo.getStartTime().toLocalDate();
        LocalDate endDate = vo.getEndTime1().toLocalDate();
        // 计算天数差
        long daysBetween = ChronoUnit.DAYS.between(startDate, endDate);

        // 遍历并添加每一天到 dateList
        for (long i = 0; i <= daysBetween; i++) {
            dateList.add(startDate.plusDays(i));
        }
        // 检查日期数量和数据库记录数量是否一致，避免数组越界
        if (list.size() != dateList.size()) {
            //log.warn("营期日期记录数量或开始时间结束时间记录数量与选择修改的课程数量不匹配");
            return R.error("营期日期记录数量与选择修改的课程数量不匹配");
        }

        // 获取第一条记录的营期ID和课程ID，用于查询同营期的其他课程
        Long periodId = list.get(0).getPeriodId();
        Long firstId = list.get(0).getId();
        LocalDate newStartDate = dateList.get(0);

        // 检查新的开始日期是否与其他课程冲突
        FsUserCoursePeriodDays checkParam = new FsUserCoursePeriodDays();
        checkParam.setPeriodId(periodId);
        List<FsUserCoursePeriodDays> allCoursesInPeriod = fsUserCoursePeriodDaysMapper.selectFsUserCoursePeriodDaysList(checkParam);

        // 找出需要调整的后续课程（日期>=新开始日期 且 不在当前修改的列表中）
        List<FsUserCoursePeriodDays> coursesToAdjust = allCoursesInPeriod.stream()
            .filter(course -> !vo.getIds().contains(course.getId())) // 排除当前修改的课程
            .filter(course -> course.getDayDate() != null && !course.getDayDate().isBefore(newStartDate)) // 日期 >= 新开始日期
            .sorted(Comparator.comparing(FsUserCoursePeriodDays::getDayDate)) // 按日期排序
            .collect(Collectors.toList());

        // 计算需要调整的天数
        LocalDate lastDateInList = dateList.get(dateList.size() - 1);

        // 遍历数据库记录列表，为每个记录设置对应的日期和其他字段
        for (int i = 0; i < list.size(); i++) {
            FsUserCoursePeriodDays day = list.get(i);
            LocalDate currentDate = dateList.get(i); // 从生成的日期列表中获取对应日期
            // 设置营期日期
            day.setDayDate(currentDate);
            // 设置开始时间和结束时间
            day.setStartDateTime(LocalDateTime.of(currentDate, vo.getStartTime().toLocalTime()));
            day.setEndDateTime(LocalDateTime.of(currentDate, vo.getEndTime1().toLocalTime()));
            SysConfig config = sysConfigMapper.selectConfigByConfigKey("joinTime.switch.config");
            if (ObjectUtils.isNotEmpty(config) && "1".equals(config.getConfigValue())) {
                day.setLastJoinTime(LocalDateTime.of(currentDate, vo.getEndTime1().toLocalTime()));
            } else {
                day.setLastJoinTime(LocalDateTime.of(currentDate, vo.getJoinTime().toLocalTime()));
            }
            // 设置状态（根据当前时间与营期时间对比）
            LocalDateTime now = LocalDateTime.now();
            if (now.isAfter(day.getStartDateTime()) && now.isBefore(day.getEndDateTime())) {
                day.setStatus(1);
            } else if (now.isBefore(day.getStartDateTime())) {
                day.setStatus(0);
            } else {
                day.setStatus(2);
            }
        }

        // 调整后续课程的日期，确保连续不跳跃
        if (!coursesToAdjust.isEmpty()) {
            // 从最后一个修改的课程日期的下一天开始安排后续课程
            LocalDate nextAvailableDate = lastDateInList.plusDays(1);
            for (FsUserCoursePeriodDays course : coursesToAdjust) {
                // 获取原始时间（小时和分钟）
                LocalTime originalStartTime = course.getStartDateTime().toLocalTime();
                LocalTime originalEndTime = course.getEndDateTime().toLocalTime();
                LocalTime originalJoinTime = course.getLastJoinTime() != null ? course.getLastJoinTime().toLocalTime() : originalEndTime;

                // 设置新的日期（保持原有的时间点）
                course.setDayDate(nextAvailableDate);
                course.setStartDateTime(LocalDateTime.of(nextAvailableDate, originalStartTime));
                course.setEndDateTime(LocalDateTime.of(nextAvailableDate, originalEndTime));
                course.setLastJoinTime(LocalDateTime.of(nextAvailableDate, originalJoinTime));

                // 重新计算状态
                LocalDateTime now = LocalDateTime.now();
                if (now.isAfter(course.getStartDateTime()) && now.isBefore(course.getEndDateTime())) {
                    course.setStatus(1);
                } else if (now.isBefore(course.getStartDateTime())) {
                    course.setStatus(0);
                } else {
                    course.setStatus(2);
                }

                list.add(course); // 添加到批量更新列表

                // 下一个课程的日期为当前日期+1天
                nextAvailableDate = nextAvailableDate.plusDays(1);
            }
        }

        // 批量更新到数据库
        fsUserCoursePeriodDaysMapper.batchUpdateCoursePeriodDays(list);
        return R.ok();
    }

    @Override
    public R updateCourseDate(UpdateCourseTimeVo vo) {
        FsUserCoursePeriodDays day = getById(vo.getId());
        FsUserCoursePeriod period = fsUserCoursePeriodMapper.selectFsUserCoursePeriodById(day.getPeriodId());

        // 如果未开启自由模式，则需要检查日期是否在营期范围内
        if (period.getFreeMode() == null || period.getFreeMode() != 1) {
            if (!DateUtil.isWithinRangeSafe(vo.getDayDate(), period.getPeriodStartingTime(), period.getPeriodEndTime())) {
                return R.error("时间不在营期范围内");
            }
        }

        day.setDayDate(vo.getDayDate());
        day.setStartDateTime(LocalDateTime.of(day.getDayDate(), day.getStartDateTime().toLocalTime()));
        day.setEndDateTime(LocalDateTime.of(day.getDayDate(), day.getEndDateTime().toLocalTime()));
        //day.setStartJoinTime(LocalDateTime.of(day.getDayDate(), vo.getStartJoinTime().toLocalTime()));
        day.setLastJoinTime(LocalDateTime.of(day.getDayDate(), day.getLastJoinTime().toLocalTime()));
        // 设置状态：如果开启自由模式，默认为进行中（status=1）
        if (period.getFreeMode() != null && period.getFreeMode() == 1) {
            // 自由模式：直接设置为进行中
            day.setStatus(1);
        } else {
            // 固定模式：默认开启今天及以后的两天，为进行中
            LocalDateTime compareDayTime = LocalDateTime.now();
            if(compareDayTime.isAfter(day.getStartDateTime()) && compareDayTime.isBefore(day.getEndDateTime())){
                day.setStatus(1);
            } else if(compareDayTime.isBefore(day.getStartDateTime())){
                day.setStatus(0);
            } else {
                day.setStatus(2);
            }
        }
        updateById(day);
        return R.ok();
    }

    /**
     * 更新营期课程状态
     */
    @Transactional(rollbackFor = RuntimeException.class)
    @Override
    public void changePeriodCourseStatus() {
        // 开启
        fsUserCoursePeriodDaysMapper.startPeriodCourse(LocalDateTime.now());

        // 关闭
        fsUserCoursePeriodDaysMapper.endPeriodCourse(LocalDateTime.now());
    }

    @Override
    public long periodCourseByCount(PeriodCountParam param) {
        //1、查询课程视频信息
        FsUserCoursePeriodDays fsUserCoursePeriodDays = new FsUserCoursePeriodDays();
        fsUserCoursePeriodDays.setPeriodId(param.getPeriodId());
        fsUserCoursePeriodDays.setVideoIds(param.getVideoIdList());
        fsUserCoursePeriodDays.setMaxDate(param.getMaxDate());

        return baseMapper.selectFsUserCoursePeriodDaysCount(fsUserCoursePeriodDays);
    }

    @Override
    public List<Long> selectFsUserCoursePeriodDaysByTime(String periodSTime, String periodETime) {
        return fsUserCoursePeriodDaysMapper.selectFsUserCoursePeriodDaysByTime(periodSTime,periodETime);
    }

    @Override
    public List<FsUserCoursePeriodDays> selectFsUserCoursePeriodDaysByCourseId(Long courseId) {
        return fsUserCoursePeriodDaysMapper.selectFsUserCoursePeriodDaysByCourseId(courseId);
    }

    private static FsCourseAnalysisCountVO getCourseAnalysisCountVO(FsUserCoursePeriodDays v, Map<Long, FsCourseAnalysisCountVO> courseMap, Map<Long, FsCourseAnalysisCountVO> redPacketMap, Map<Long, FsCourseAnalysisCountVO> answerMap) {
        FsCourseAnalysisCountVO countVO = new FsCourseAnalysisCountVO();
        FsCourseAnalysisCountVO courseVO = courseMap.getOrDefault(v.getVideoId(), countVO);
        FsCourseAnalysisCountVO redPacketVO = redPacketMap.getOrDefault(v.getVideoId(), countVO);
        FsCourseAnalysisCountVO answerVO = answerMap.getOrDefault(v.getVideoId(), countVO);
        //单独赋值
        countVO.setVideoId(v.getVideoId()).setCourseWatchNum(courseVO.getCourseWatchNum()).setCourseCompleteNum(courseVO.getCourseCompleteNum())
                .setCompleteRate(courseVO.getCompleteRate() != null ? courseVO.getCompleteRate() : new BigDecimal(BigInteger.ZERO))
                .setCourseWatchTimes(courseVO.getCourseWatchTimes()).setCourseCompleteTimes(courseVO.getCourseCompleteTimes());
        countVO.setRedPacketNum(redPacketVO.getRedPacketNum())
                .setRedPacketAmount(redPacketVO.getRedPacketAmount() != null ? redPacketVO.getRedPacketAmount() : new BigDecimal(BigInteger.ZERO));
        countVO.setAnswerNum(answerVO.getAnswerNum()).setAnswerRightNum(answerVO.getAnswerRightNum())
                .setAnswerRightRate(answerVO.getAnswerRightRate()!=null ? answerVO.getAnswerRightRate() : new BigDecimal(BigInteger.ZERO))
                .setAnswerTimes(answerVO.getAnswerTimes()).setAnswerRightTimes(answerVO.getAnswerRightTimes());
        return countVO;
    }

    /**
     * 查询营期选项列表
     * @param params    参数
     * @return  list
     */
    @Override
    public List<OptionsVO> selectPeriodListByMap(Map<String, Object> params) {
        return baseMapper.selectPeriodListByMap(params);
    }
}
