package com.yonge.cooleshow.biz.dal.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.yonge.cooleshow.auth.api.client.SysUserFeignService;
import com.yonge.cooleshow.auth.api.entity.SysUser;
import com.yonge.cooleshow.biz.dal.dao.CourseScheduleDao;
import com.yonge.cooleshow.biz.dal.dao.CourseScheduleRepliedDao;
import com.yonge.cooleshow.biz.dal.dao.CourseScheduleStudentPaymentDao;
import com.yonge.cooleshow.biz.dal.dao.TeacherFreeTimeDao;
import com.yonge.cooleshow.biz.dal.dto.PracticeScheduleDto;
import com.yonge.cooleshow.biz.dal.dto.req.OrderReq;
import com.yonge.cooleshow.biz.dal.dto.search.HomeworkSearch;
import com.yonge.cooleshow.biz.dal.dto.search.MyCourseSearch;
import com.yonge.cooleshow.biz.dal.dto.search.PracticeTeacherSearch;
import com.yonge.cooleshow.biz.dal.entity.*;
import com.yonge.cooleshow.biz.dal.enums.*;
import com.yonge.cooleshow.biz.dal.service.*;
import com.yonge.cooleshow.biz.dal.support.PageUtil;
import com.yonge.cooleshow.biz.dal.support.WrapperUtil;
import com.yonge.cooleshow.biz.dal.vo.*;
import com.yonge.cooleshow.biz.dal.vo.res.OrderCreateRes;
import com.yonge.cooleshow.common.constant.SysConfigConstant;
import com.yonge.cooleshow.common.entity.HttpResponseResult;
import com.yonge.cooleshow.common.exception.BizException;
import com.yonge.toolset.mybatis.page.PageInfo;
import com.yonge.toolset.utils.date.DateUtil;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.annotations.Param;
import org.redisson.api.RMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.temporal.TemporalAdjusters;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 老师课程表(CourseSchedule)表服务实现类
 *
 * @author hgw
 * @since 2022-03-18 15:29:11
 */
@Service("courseScheduleService")
public class CourseScheduleServiceImpl extends ServiceImpl<CourseScheduleDao, CourseSchedule> implements CourseScheduleService {

    private final static Logger log = LoggerFactory.getLogger(CourseScheduleServiceImpl.class);

    @Autowired
    private SysUserFeignService sysUserFeignService;
    @Autowired
    private HolidaysFestivalsService holidaysFestivalsService;
    @Autowired
    private SysConfigService sysConfigService;
    @Autowired
    private CourseScheduleRepliedDao repliedDao;
    @Autowired
    private CourseScheduleStudentPaymentDao paymentDao;
    @Autowired
    private TeacherFreeTimeService teacherFreeTimeService;
    @Autowired
    private CourseGroupService courseGroupService;
    @Autowired
    private CourseScheduleStudentPaymentService courseScheduleStudentPaymentService;
    @Autowired
    private CourseScheduleTeacherSalaryService courseScheduleTeacherSalaryService;
    @Autowired
    private TeacherFreeTimeDao teacherFreeTimeDao;

    @Override
    public CourseScheduleDao getDao() {
        return this.baseMapper;
    }

    /**
     * 老师端-首页-我的课程-直播课
     *
     * @param param 传入参数
     *              <p> - classDate 年月
     *              <p> - row 条数
     *              <p> - page 页数
     *              <p> - status 课程状态 NOT_START未开始 ING进行中 COMPLETE已完成
     *              <p> - subjectId 声部id
     */
    public PageInfo<TeacherLiveCourseInfoVo> queryTeacherLiveCourse(Map<String, Object> param) {
        String status = WrapperUtil.toStr(param, "status");
        if (StringUtils.isNotBlank(status)) {
            CourseScheduleEnum.existCourseState(status, "查询条件错误，课程状态不正确");
        }
        param.put("status", status);
        String classDate = WrapperUtil.toStr(param, "classDate", "查询时间不能为空!");
        //本月的第一天
        LocalDate firstDay;
        try {
            String[] classDateSp = classDate.split("-");
            firstDay = LocalDate.of(Integer.parseInt(classDateSp[0]), Integer.parseInt(classDateSp[1]), 1);
        } catch (Exception e) {
            throw new BizException("查询时间格式不正确 [" + classDate + "]");
        }
        //本月的最后一天
        LocalDate lastDay = firstDay.with(TemporalAdjusters.lastDayOfMonth());
        //查询该月的所有课程
        param.put("startDate", firstDay.toString());
        param.put("endDate", lastDay.toString());
        param.put("teacherId", getSysUser().getId());
        param.put("type", CourseScheduleEnum.LIVE.getCode());
        Page<TeacherLiveCourseInfoVo> pageInfo = PageUtil.getPageInfo(param);
        pageInfo.setAsc("a.start_time_");
        IPage<TeacherLiveCourseInfoVo> page = baseMapper.queryLiveTeacherCourse(pageInfo, param);
        return PageUtil.pageInfo(page);
    }

    /**
     * 校验该学生大于当前时间并未开始的课程时间和传入时间段有没有交集
     *
     * @param studentId 学生id
     * @param startTime 新增课程 开始时间
     * @param endTime   新增课程 结束时间
     * @return true 被占用 false 没有被占用
     */
    public boolean checkStudentCourseTime(Long studentId, Date startTime, Date endTime) {
        WrapperUtil.checkObj(studentId, "学生id不能为空!");
        WrapperUtil.checkObj(startTime, "开始时间不能为空!");
        //获取日期-目前上课没有跨天情况
        String ymd = DateUtil.format(startTime, DateUtil.DEFAULT_PATTERN);
        //查询大于当前时间并未开始的课程
        Map<String, Object> param = new HashMap<>();
        param.put("studentId", studentId);
        param.put("greaterDate", new Date());
        param.put("classDate", ymd);
        param.put("status", CourseScheduleEnum.NOT_START.getCode());
        List<CourseSchedule> list = baseMapper.queryStudentCourse(param);
        return checkCourseTime(list, CourseSchedule::getStartTime, CourseSchedule::getEndTime, startTime, endTime);
    }

    /**
     * 校验老师这个时间段有没有被占用-校验课时
     *
     * @param teacherId 老师id
     * @param startTime 新增课程 开始时间
     * @param endTime   新增课程 结束时间
     * @return true 被占用 false 没有被占用
     */
    public boolean checkTeacherCourseTime(Long teacherId, Date startTime, Date endTime) {
        WrapperUtil.checkObj(teacherId, "老师id不能为空!");
        WrapperUtil.checkObj(startTime, "开始时间不能为空!");
        //获取日期-目前上课没有跨天情况
        String ymd = DateUtil.format(startTime, DateUtil.DEFAULT_PATTERN);
        //查询老师当天所有的课程时间数据
        List<CourseSchedule> list = this.list(Wrappers.<CourseSchedule>lambdaQuery()
                .eq(CourseSchedule::getTeacherId, teacherId)
                .eq(CourseSchedule::getClassDate, ymd));
        return checkCourseTime(list, CourseSchedule::getStartTime, CourseSchedule::getEndTime, startTime, endTime);
    }

    /**
     * 检查传入时间列表的时间是否与新增课程时间有交集
     * <p>案例1：
     * <p>list列表
     * <p> 8:00 ~ 09:00 课程
     * <p> 9:00 ~ 10:00 课程
     * <p>新增课程 09:10~ 09:40
     * <p>返回 true
     *
     * <p>案例2：
     * <p>list列表
     * <p> 8:00 ~ 09:00 课程
     * <p> 9:00 ~ 10:00 课程
     * <p>新增课程 10:00~ 10:40
     * <p>返回 false
     *
     * @param list         时间列表
     * @param startTimeFun 获取现课程开始时间
     * @param endTimeFun   获取现课程结束时间
     * @param startTime    新增课程开始时间
     * @param endTime      新增课程结束时间
     * @return true 有交集 false 没有交集
     */
    public <T> boolean checkCourseTime(List<T> list, Function<T, Date> startTimeFun, Function<T, Date> endTimeFun, Date startTime, Date endTime) {
        WrapperUtil.checkObj(startTime, "开始时间不能为空!");
        WrapperUtil.checkObj(endTime, "结束时间不能为空!");
        // 如果没有课程，直接返回false
        if (CollectionUtils.isEmpty(list)) {
            return false;
        }
        boolean result;
        //匹配时间是否有交集
        for (T t : list) {
            result = WrapperUtil.inInterSection(startTimeFun.apply(t), endTimeFun.apply(t), startTime, endTime);
            if (result) {
                return true;
            }
        }
        return false;
    }

    /**
     * 创建直播课时选课时的日历
     * <P>主要查询该老师目前的课程时间
     * <P>根据传入的单课时时长将时间分片后来匹配数据库中课程时间，得到每日空余时间
     *
     * @param param 传入参数
     *              <p> - teacherId 老师id
     *              <p> - year 年
     *              <p> - month 月
     *              <p> - singleCourseMinutes 单课时时长(不包含休息时间)
     *              <p> - freeCourseMinutes 单课时休息时长
     * @return 返回传入时间当月每日的剩余时间段
     */
    public List<CourseCalendarEntity> createLiveCourseCalendar(Map<String, Object> param) {
        //校验数据
        Long teacherId = WrapperUtil.toLong(param, "teacherId", "老师id不能为空!");
        Integer year = WrapperUtil.toInt(param, "year", "日历的时间年份不能为空!");
        Integer month = WrapperUtil.toInt(param, "month", "日历的时间月份不能为空!");
        Integer singleCourseMinutes = WrapperUtil.toInt(param, "singleCourseMinutes", "单课时时长不能为空!");
        Integer freeCourseMinutes = WrapperUtil.toInt(param, "freeCourseMinutes");
        if (singleCourseMinutes < 25) {
            throw new BizException("单课时时长不能小于25分钟!");
        }
        //获取每月1号
        LocalDate firstDay = LocalDate.of(year, month, 1);
        //根据firstDay计算出该月的最后一天
        LocalDate lastDay = firstDay.with(TemporalAdjusters.lastDayOfMonth());
        //获取老师的课程  key：日期-年月日 value：课程时间-开始时间，结束时间
        Map<String, List<CourseTimeEntity>> nowCourseMap = getTeacherCourseTime(teacherId, firstDay.toString(), lastDay.toString());
        //生成日历数据
        List<CourseCalendarEntity> courseCalendarEntities = generateCourseData(firstDay, lastDay, singleCourseMinutes, freeCourseMinutes);
        //对比课程数据，筛选出空余的课程时间
        opsCourseCalendarData(nowCourseMap, courseCalendarEntities, 0);
        return courseCalendarEntities;
    }

    /**
     * 对比课程数据，筛选出空余的课程时间
     *
     * @param nowCourseMap           现有课程
     * @param courseCalendarEntities 课程时间日历
     * @param skipHoliday            是否跳过节假日  0:不跳过 1跳过
     */
    private void opsCourseCalendarData(Map<String, List<CourseTimeEntity>> nowCourseMap, List<CourseCalendarEntity> courseCalendarEntities, Integer skipHoliday) {
        //储存节假日数据，避免重复查询
        Map<Integer, List<String>> holidayMap = new HashMap<>();
        courseCalendarEntities.forEach(calendarEntity -> {
            //获取课程数据,并剔除交集时间段数据
            List<CourseTimeEntity> nowCourse = nowCourseMap.get(calendarEntity.getDate());
            if (CollectionUtils.isEmpty(nowCourse)) {
                //当日没课
                calendarEntity.setFullCourse(0);//  0:未满 1满
            } else {
                //将日历时间与课程时间进行对比，如果有交集则将日历的时间数据删除
                Iterator<CourseTimeEntity> iterator = calendarEntity.getCourseTime().iterator();
                while (iterator.hasNext()) {
                    CourseTimeEntity next = iterator.next();
                    boolean flag = checkCourseTime(nowCourse, CourseTimeEntity::getStartTime, CourseTimeEntity::getEndTime, next.getStartTime(), next.getEndTime());
                    if (flag) {
                        iterator.remove();
                    }
                }
                //如果日历时间集合数据删没了，那么就证明当天课程已经满了  0:未满 1满
                if (CollectionUtils.isEmpty(calendarEntity.getCourseTime())) {
                    calendarEntity.setFullCourse(1);
                } else {
                    calendarEntity.setFullCourse(0);
                }
            }
            //获取节假日信息
            int calendarYear = LocalDate.parse(calendarEntity.getDate()).getYear();
            List<String> holiday = Optional.of(calendarYear)
                    .map(holidayMap::get)
                    .filter(CollectionUtils::isNotEmpty)
                    .orElseGet(() -> {
                        List<String> list = getHoliday(calendarYear);
                        holidayMap.put(calendarYear, list);
                        return list;
                    });
            //是否是节假日  0:不是 1是
            calendarEntity.setHoliday(holiday.contains(calendarEntity.getDate()) ? 1 : 0);
            calendarEntity.setSkipHoliday(skipHoliday);//直播课日历只展示节假日 不跳过节假日
        });
    }

    /**
     * 获取老师的课程时间数据
     *
     * @param teacherId 老师id
     * @param startDate 开始时间
     * @param endDate   结束时间
     * @return key：日期-年月日 value：课程时间-开始时间，结束时间
     */
    private Map<String, List<CourseTimeEntity>> getTeacherCourseTime(Long teacherId, String startDate, String endDate) {
        //查询该老师指定时间段的课程
        List<CourseSchedule> courseList = this.list(Wrappers.<CourseSchedule>lambdaQuery()
                .eq(CourseSchedule::getTeacherId, teacherId)
                .ge(CourseSchedule::getClassDate, startDate)
                .le(CourseSchedule::getClassDate, endDate)
        );
        return getCourseListMap(courseList);
    }

    /**
     * 根据开始日期 结束日期 单结课时长 生成一个周期的每日课程时间数据
     *
     * @param firstDay            开始日期年月日
     * @param lastDay             最后一天的日期年月日
     * @param singleCourseMinutes 单结课时长
     * @param freeCourseMinutes   单课时休息时长
     */
    public List<CourseCalendarEntity> generateCourseData(LocalDate firstDay, LocalDate lastDay, Integer singleCourseMinutes, Integer freeCourseMinutes) {
        //获取上课的最早时间和最晚时间,将每日时间切片
        String sysStartTime = sysConfigService.findConfigValue(SysConfigConstant.COURSE_START_SETTING);
        String sysEndTime = sysConfigService.findConfigValue(SysConfigConstant.COURSE_END_SETTING);
        if (StringUtils.isBlank(sysStartTime) || StringUtils.isBlank(sysEndTime)) {
            throw new BizException("未查询到平台设置的每日最早上/最晚上课时间设置！");
        }
        //每日最早上课时间
        Date dayStartTime = DateUtil.toDateTime(firstDay + " " + sysStartTime + ":00");
        //每日最晚下课时间
        Date dayEndTime = DateUtil.toDateTime(firstDay + " " + sysEndTime + ":00");
        //每日日期数据
        List<CourseCalendarEntity> list = new ArrayList<>();
        //生成每日课程时间段 -根据单节课时长 及每日最早最晚时间 生成一天的时间切片
        List<CourseTimeEntity> dayTime = generateDayTime(singleCourseMinutes, freeCourseMinutes, dayStartTime, dayEndTime);
        int addDay = 0;
        //获取每日日期数据
        while (firstDay.isBefore(lastDay) || firstDay.isEqual(lastDay)) {
            CourseCalendarEntity entity = new CourseCalendarEntity();
            List<CourseTimeEntity> times = new ArrayList<>();
            //获取当前日期
            entity.setDate(firstDay.toString());
            //将每日上课时间时间添加到日历中
            opsCourseDayTime(addDay, entity, times, dayTime);
            list.add(entity);
            firstDay = firstDay.plusDays(1L);
            addDay++;
        }

        return list;
    }

    /**
     * 生成每日课程时间段数据
     * <p>根据单节课时长 及每日最早最晚时间 生成一天的时间切片
     *
     * @param singleCourseMinutes 单节课时长
     * @param freeCourseMinutes   单课时休息时长
     * @param startTime           每日最早上课时间
     * @param endTime             每日最晚下课时间
     */
    public List<CourseTimeEntity> generateDayTime(Integer singleCourseMinutes, Integer freeCourseMinutes, Date startTime, Date endTime) {
        List<CourseTimeEntity> result = new ArrayList<>();
        //划分每日课程时间
        Date edate = startTime;
        boolean flag = true;
        while (flag) {
            if (edate.getTime() > endTime.getTime()) {
                flag = false;
            } else {
                CourseTimeEntity timeEntity = new CourseTimeEntity();
                timeEntity.setStartTime(edate);
                //加时间得到课程结束时间
                edate = DateUtil.addMinutes(edate, singleCourseMinutes);
                timeEntity.setEndTime(edate);
                //再加上单节课休息时间
                edate = DateUtil.addMinutes(edate, freeCourseMinutes);
                result.add(timeEntity);
            }
        }
        result.remove(result.size() - 1);
        return result;
    }

    /**
     * 根据年份获取当年节假日
     *
     * @param year 年
     * @return 年月日
     */
    @Override
    public List<String> getHoliday(int year) {
        HolidaysFestivals holidaysFestivals = holidaysFestivalsService.queryByYear(year);
        if (Objects.isNull(holidaysFestivals)) {
            return Collections.emptyList();
        }
        if (StringUtils.isBlank(holidaysFestivals.getHolidaysFestivalsJson())) {
            return Collections.emptyList();
        }
        return WrapperUtil.toList(holidaysFestivals.getHolidaysFestivalsJson());
    }

    /**
     * 生成陪练课日历-用于学生购买指定老师陪练课
     *
     * @param param 传入参数
     *              <p> - teacherId 老师id
     *              <p> - studentId 学生id
     *              <p> - year 年
     *              <p> - month 月
     * @return 返回传入时间当月每日的剩余时间段
     */
    public List<CourseCalendarEntity> createPracticeCourseCalender(Map<String, Object> param) {
        //校验数据
        Long teacherId = WrapperUtil.toLong(param, "teacherId", "老师id不能为空!");
        Long studentId = WrapperUtil.toLong(param, "studentId", "学生id不能为空!");
        Integer year = WrapperUtil.toInt(param, "year", "日历的时间年份不能为空!");
        Integer month = WrapperUtil.toInt(param, "month", "日历的时间月份不能为空!");

        //获取开始日期
        LocalDate firstDay = LocalDate.of(year, month, 1);
        //根据firstDay计算出该月的最后一天
        LocalDate lastDay = firstDay.with(TemporalAdjusters.lastDayOfMonth());
        //查询老师陪练课设置
        TeacherFreeTime teacherTime = teacherFreeTimeService.getOne(Wrappers.<TeacherFreeTime>lambdaQuery()
                .eq(TeacherFreeTime::getDefaultFlag, true)
                .eq(TeacherFreeTime::getTeacherId, teacherId)
        );
        Optional.ofNullable(teacherTime).orElseThrow(() -> new BizException("未查询到老师陪练课设置！"));
        //根据老师设置的陪练课时间生成当月日历
        List<CourseCalendarEntity> courseCalendarEntities = generatePracticeCalendar(teacherTime, firstDay, lastDay);
        //查询老师及当前学生的课时
        Map<String, List<CourseTimeEntity>> nowCourseMap = getAllPracticeCourseTime(teacherId, studentId, firstDay.toString(), lastDay.toString());
        //和生成的日历对比,筛选出空余的课程时间
        opsCourseCalendarData(nowCourseMap, courseCalendarEntities, teacherTime.getSkipHolidayFlag() ? 1 : 0);
        return courseCalendarEntities;
    }

    /**
     * 生成老师陪练课日历
     * <p>陪练课的日历需要查询老师设置的陪练课时间，根据设置的时间来生成日历
     */
    private List<CourseCalendarEntity> generatePracticeCalendar(TeacherFreeTime teacherTime, LocalDate firstDay, LocalDate lastDay) {
        //将老师设置的陪练课时间结构化   key:：数字周几 1~7  value：具体的时间 开始时间-结束时间
        Map<Integer, JSONArray> teacherPracticeTime = getTeacherPracticeTime(teacherTime);
        //每日日期数据
        List<CourseCalendarEntity> list = new ArrayList<>();

        //获取每日日期数据
        while (firstDay.isBefore(lastDay) || firstDay.isEqual(lastDay)) {
            CourseCalendarEntity entity = new CourseCalendarEntity();
            //获取当前日期
            entity.setDate(firstDay.toString());
            //获取当前日期周几
            int weekNum = firstDay.getDayOfWeek().getValue();
            JSONArray jsonArray = teacherPracticeTime.get(weekNum);
            List<CourseTimeEntity> timeEntities = jsonArray.stream()
                    .map(t -> getCourseTimeEntity(entity, (JSONObject) t))
                    .collect(Collectors.toList());
            entity.setCourseTime(timeEntities);
            list.add(entity);
            firstDay = firstDay.plusDays(1L);
        }
        return list;
    }

    private CourseTimeEntity getCourseTimeEntity(CourseCalendarEntity entity, JSONObject t) {
        //拼接时间
        BiFunction<String, String, Date> canCatTime = (date, time) -> DateUtil.toDateTime(date + " " + time);
        CourseTimeEntity timeEntity = new CourseTimeEntity();
        timeEntity.setStartTime(canCatTime.apply(entity.getDate(), t.getString("startTime")));
        timeEntity.setEndTime(canCatTime.apply(entity.getDate(), t.getString("endTime")));
        return timeEntity;
    }

    /**
     * 将每日上课时间时间添加到日历中
     *
     * @param addDay       添加的天数
     * @param entity       实体数据
     * @param times        课程数据集合
     * @param timeEntities 每日上课时间集合
     */
    private void opsCourseDayTime(int addDay, CourseCalendarEntity entity, List<CourseTimeEntity> times, List<CourseTimeEntity> timeEntities) {
        timeEntities.forEach(time -> {
            CourseTimeEntity courseTimeEntity = new CourseTimeEntity();
            courseTimeEntity.setStartTime(DateUtil.addDays(time.getStartTime(), addDay));
            courseTimeEntity.setEndTime(DateUtil.addDays(time.getEndTime(), addDay));
            times.add(courseTimeEntity);
        });
        entity.setCourseTime(times);
    }

    /**
     * 将老师设置的陪练课时间结构化
     *
     * @return key:：数字周几 1~7  value：具体的时间 开始时间-结束时间
     */
    private Map<Integer, JSONArray> getTeacherPracticeTime(TeacherFreeTime teacherTime) {
        Map<Integer, JSONArray> teacherPracticeTime = new HashMap<>();
        //将老师设置的陪练课时间放入map中
        BiConsumer<String, Integer> timeCon = (timeStr, weekNum) -> {
            if (StringUtils.isBlank(timeStr)) {
                return;
            }
            JSONArray objects = JSONObject.parseArray(timeStr);
            if (CollectionUtils.isNotEmpty(objects)) {
                teacherPracticeTime.put(weekNum, objects);
            }
        };
        timeCon.accept(teacherTime.getMonday(), 1);
        timeCon.accept(teacherTime.getTuesday(), 2);
        timeCon.accept(teacherTime.getWednesday(), 3);
        timeCon.accept(teacherTime.getThursday(), 4);
        timeCon.accept(teacherTime.getFriday(), 5);
        timeCon.accept(teacherTime.getSaturday(), 6);
        timeCon.accept(teacherTime.getSunday(), 7);
        return teacherPracticeTime;
    }

    /**
     * 查询老师及当前学生的课时和目前直播课排课锁定在缓存的的课时
     *
     * @param teacherId 老师id
     * @param studentId 学员id
     */
    private Map<String, List<CourseTimeEntity>> getAllPracticeCourseTime(Long teacherId, Long studentId, String startDate, String endDate) {
        //查询该老师指定时间段的课程
        List<CourseSchedule> courseList = this.list(Wrappers.<CourseSchedule>lambdaQuery()
                .eq(CourseSchedule::getTeacherId, teacherId)
                .ge(CourseSchedule::getClassDate, startDate)
                .le(CourseSchedule::getClassDate, endDate)
        );
        if (CollectionUtils.isEmpty(courseList)) {
            courseList = new ArrayList<>();
        }
        //查询学生的课程
        Map<String, Object> param = new HashMap<>();
        param.put("studentId", studentId);
        param.put("startClassDate", startDate);
        param.put("endClassDate", endDate);
        List<CourseSchedule> studentCourse = baseMapper.queryStudentCourse(param);
        //将数据合并
        if (CollectionUtils.isNotEmpty(studentCourse)) {
            courseList.addAll(studentCourse);
        }
        //查询当前老师直播课当前排课锁定的课时缓存-因为在学生购买老师课程时候老师可能正在排直播课，并且也将时间已经预排好了，所以需要查询缓存中的课时
        RMap<Long, List<CourseTimeEntity>> lockTimeCache = courseGroupService.getLiveLockTimeCache(teacherId);
        if (lockTimeCache.isExists()) {
            List<CourseSchedule> lockCourseList = new ArrayList<>();
            List<CourseTimeEntity> timeEntities = lockTimeCache.get(teacherId);
            if (CollectionUtils.isNotEmpty(timeEntities)) {
                timeEntities.forEach(courseTimeEntity -> {
                    CourseSchedule lockCourse = new CourseSchedule();
                    String ymd = DateUtil.dateToString(courseTimeEntity.getStartTime());
                    lockCourse.setClassDate(DateUtil.toDate(ymd));
                    lockCourse.setStartTime(courseTimeEntity.getStartTime());
                    lockCourse.setEndTime(courseTimeEntity.getEndTime());
                    lockCourseList.add(lockCourse);
                });
            }
            if (CollectionUtils.isNotEmpty(lockCourseList)) {
                courseList.addAll(lockCourseList);
            }
        }
        return getCourseListMap(courseList);
    }

    /**
     * 将课时数据结构化
     *
     * @param courseList 课程列表
     * @return key：课程日期-年月日  value：课程
     */
    private Map<String, List<CourseTimeEntity>> getCourseListMap(List<CourseSchedule> courseList) {
        Map<String, List<CourseTimeEntity>> nowCourse = new HashMap<>();
        if (CollectionUtils.isNotEmpty(courseList)) {
            WrapperUtil.groupList(courseList, CourseSchedule::getClassDate)
                    .forEach((k, v) -> {
                        List<CourseTimeEntity> value = new ArrayList<>();
                        v.forEach(course -> {
                            CourseTimeEntity courseTimeEntity = new CourseTimeEntity();
                            courseTimeEntity.setStartTime(course.getStartTime());
                            courseTimeEntity.setEndTime(course.getEndTime());
                            value.add(courseTimeEntity);
                        });
                        nowCourse.put(DateUtil.dateToString(k), value);
                    });
        }
        return nowCourse;
    }

    /**
     * 获取当前登录人信息
     */
    private SysUser getSysUser() {
        return Optional.ofNullable(sysUserFeignService.queryUserInfo())
                .orElseThrow(() -> new BizException("用户不存在"));
    }

    private SysUser getSysUser(Long userId) {
        return Optional.ofNullable(userId)
                .map(sysUserFeignService::queryUserById)
                .orElseThrow(() -> new BizException("用户不存在"));
    }

    /**
     * 老师端-首页-我的课程-陪练课
     * search:{"classMonth":"2022-03","status":"COMPLETE","subjectId":1}
     * <p>
     * 老师端-首页-课后评价
     * search:{"classMonth":"2022-03","replied":0,"studentName":"测试王"}
     * <p>
     * 老师端-我的-我的主页
     * search:{"classMonth":"2022-03"}
     * <p>
     * 老师端-课表
     * search:{"classDate":"2022-03-27"}
     * search:{"classMonth":"2022-03"}
     * <p>
     * 老师端-课表-陪练课
     * search:{"classDate":"2022-03-27"}
     *
     * @Description: 根据老师id查询购课学员
     * @Author: cy
     * @Date: 2022/4/11
     */
    public IPage<MyCourseVo> queryTeacherPracticeCourse(IPage<MyCourseVo> page, MyCourseSearch search) {
        //查询所有已评价学生id
        List<Long> studentList = repliedDao.selectAll();

        Integer replied = search.getReplied();
        if (replied != null) {
            //按评价筛选 0:未评价 1:已评价
            if (replied == 0) {
                //查询所有购课用户
                List<Long> userList = paymentDao.selectAll();
                //取差集
                userList.removeAll(studentList);
                if (userList.isEmpty()) {
                    return page.setRecords(new ArrayList<>());
                }
                search.setRepliedIds(userList);
            }

            if (replied == 1) {
                search.setRepliedIds(studentList);
            }
        }
        return page.setRecords(baseMapper.queryTeacherPracticeCourse(page, monthToDate(search)));
    }

    @Override
    public Integer countTeacherNoDecorateHomework(HomeworkSearch homeworkSearch) {

        // 陪练课 课程状态为完成，有学生考勤记录 没有布置作业
        return baseMapper.countTeacherNoDecorateHomework(homeworkSearch);
    }

    /**
     * 按月份计算开始结束日期
     *
     * @param search
     * @return
     */
    public MyCourseSearch monthToDate(MyCourseSearch search) {
        String classMonth = search.getClassMonth();
        if (StringUtils.isBlank(classMonth)) {
            return search;
        }
        String[] classDateSp = classMonth.split("-");
        LocalDate firstDay;
        try {
            firstDay = LocalDate.of(Integer.parseInt(classDateSp[0]), Integer.parseInt(classDateSp[1]), 1);
        } catch (Exception e) {
            throw new BizException("查询时间格式不正确 [" + classMonth + "]");
        }
        //本月的最后一天
        LocalDate lastDay = firstDay.with(TemporalAdjusters.lastDayOfMonth());
        search.setStartDate(firstDay.toString());
        search.setEndDate(lastDay.toString());
        return search;
    }

    /**
     * @Description: 查询老师约课日历
     * @Author: cy
     * @Date: 2022/4/13
     */
    public Set<String> queryCourseSchedule(MyCourseSearch search) {
        return baseMapper.queryCourseSchedule(monthToDate(search));
    }

    /**
     * @Description: 根据学生id查询老师
     * @Author: cy
     * @Date: 2022/4/13
     */
    public IPage<MyCourseVo> queryStudentPracticeCourse(IPage<MyCourseVo> page, MyCourseSearch search) {
        return page.setRecords(baseMapper.queryStudentPracticeCourse(page, monthToDate(search)));
    }

    @Override
    public boolean checkTeacherCourseSchedule(Long teacherId, Long courseId) {
        CourseSchedule courseSchedule = this.getById(courseId);
        return teacherId.equals(courseSchedule.getTeacherId());
    }

    /**
     * @Description: 查询学生约课日历
     * @Author: cy
     * @Date: 2022/4/14
     */
    public Set<String> queryCourseScheduleStudent(MyCourseSearch search) {
        return baseMapper.queryCourseScheduleStudent(monthToDate(search));
    }

    @Override
    public boolean checkStudentCourseSchedule(Long studentId, Long courseId) {
        CourseScheduleStudentPayment courseScheduleStudentPayment = courseScheduleStudentPaymentService
                .getByCourseId(courseId);
        return courseScheduleStudentPayment != null && studentId.equals(courseScheduleStudentPayment.getUserId());

    }

    /**
     * @Description: 老师端-课表-日历-用户
     * @Author: cy
     * @Date: 2022/4/18
     */
    @Override
    public Map<String, Object> queryCourseUser(MyCourseSearch search) {
        List<CourseStudent> studentList = baseMapper.queryCourseUser(search);

        Map<String, String> sysConfig = new HashMap<>();
        //提前XX分钟创建/进入陪练课房间时间
        sysConfig.put("practiceStartTime", sysConfigService.findConfigValue(SysConfigConstant.PRE_CREATE_PRACTICE_ROOM_MINUTE));
        //陪练课结束后，XX分钟关闭房间
        sysConfig.put("practiceEndTime", sysConfigService.findConfigValue(SysConfigConstant.DESTROY_EXPIRED_PRACTICE_ROOM_MINUTE));
        //提前XX分钟创建/进入直播房间的时间
        sysConfig.put("liveStartTime", sysConfigService.findConfigValue(SysConfigConstant.PRE_CREATE_LIVE_ROOM_MINUTE));
        //直播结束后，XX分钟关闭房间
        sysConfig.put("liveEndTime", sysConfigService.findConfigValue(SysConfigConstant.DESTROY_EXPIRED_LIVE_ROOM_MINUTE));

        Map map = new HashMap();
        map.put("sysConfig", sysConfig);
        map.put("studentList", studentList);
        return map;
    }

    /**
     * @Description: 学生端-首页-陪练课老师列表
     * @Author: cy
     * @Date: 2022/4/19
     */
    @Override
    public IPage<PracticeTeacherVo> teacherList(IPage<PracticeTeacherVo> page, PracticeTeacherSearch search) {
        return page.setRecords(baseMapper.teacherList(page, search));
    }

    /**
     * @Description: 学生端-课表-日历-用户
     * @Author: cy
     * @Date: 2022/4/20
     */
    public Map<String, Object> queryCourseTeacher(MyCourseSearch search) {
        List<CourseStudent> teacherList = baseMapper.queryCourseTeacher(search);
        Map<String, String> sysConfig = new HashMap<>();
        //提前XX分钟创建/进入陪练课房间时间
        sysConfig.put("practiceStartTime", sysConfigService.findConfigValue(SysConfigConstant.PRE_CREATE_PRACTICE_ROOM_MINUTE));
        //陪练课结束后，XX分钟关闭房间
        sysConfig.put("practiceEndTime", sysConfigService.findConfigValue(SysConfigConstant.DESTROY_EXPIRED_PRACTICE_ROOM_MINUTE));
        //提前XX分钟创建/进入直播房间的时间
        sysConfig.put("liveStartTime", sysConfigService.findConfigValue(SysConfigConstant.PRE_CREATE_LIVE_ROOM_MINUTE));
        //直播结束后，XX分钟关闭房间
        sysConfig.put("liveEndTime", sysConfigService.findConfigValue(SysConfigConstant.DESTROY_EXPIRED_LIVE_ROOM_MINUTE));

        Map map = new HashMap();
        map.put("sysConfig", sysConfig);
        map.put("studentList", teacherList);
        return map;
    }


    /**
     * 学生-查询直播课
     *
     * @param param 传入参数
     *              <p> - studentId 学生id
     *              <p> - courseState 课程状态 NOT_START未开始 ING进行中 COMPLETE已完成 CANCEL已取消
     *              <p> - classDate 查询时间-年月
     *              <p> - subjectId 声部id
     */
    @Override
    public PageInfo<CourseStudent> queryStudentLiveCourse(Map<String, Object> param) {
        String classDate = WrapperUtil.toStr(param, "classDate");
        String[] classDateSp = classDate.split("-");
        LocalDate firstDay;
        try {
            firstDay = LocalDate.of(Integer.parseInt(classDateSp[0]), Integer.parseInt(classDateSp[1]), 1);
        } catch (Exception e) {
            throw new BizException("查询时间格式不正确 [" + classDate + "]");
        }
        //本月的最后一天
        LocalDate lastDay = firstDay.with(TemporalAdjusters.lastDayOfMonth());
        param.put("type", CourseScheduleEnum.LIVE.getCode());
        param.put("startDate", firstDay.toString());
        param.put("endDate", lastDay.toString());
        param.put("orderState", OrderStatusEnum.PAID.getCode());
        Page<CourseStudent> pageInfo = PageUtil.getPageInfo(param);
        pageInfo.setAsc(" cs.start_time_");
        return PageUtil.pageInfo(baseMapper.queryStudentLiveCourse(pageInfo, param));
    }

    /**
     * @Description: 学生购买陪练课
     * @Author: cy
     * @Date: 2022/4/21
     */
    @Transactional(rollbackFor = Exception.class)
    public HttpResponseResult<OrderCreateRes> buyPracticeCourse(OrderReq.OrderReqInfo orderReqInfo) {
        log.info("buyPracticeCourse  param:{}", JSON.toJSONString(orderReqInfo));
        Long studentId = orderReqInfo.getUserId();

        //校验学生信息
        getSysUser(studentId);

        ObjectMapper objectMapper = new ObjectMapper();
        PracticeScheduleDto scheduleDto = objectMapper.convertValue(orderReqInfo.getBizContent(), PracticeScheduleDto.class);

        String orderNo = orderReqInfo.getOrderNo();
        scheduleDto.setType(CourseScheduleEnum.PRACTICE.getCode());
        scheduleDto.setStatus(CourseGroupEnum.NOT_SALE.getCode());
        scheduleDto.setMixStudentNum(1);
        scheduleDto.setStudentId(studentId);

        //写入course_group
        baseMapper.addCourseGroup(scheduleDto);
        Long groupId = scheduleDto.getGroupId();

        //根据老师id获取默认配置声部原价
        TeacherSubjectPrice teacherSubjectPrice = teacherFreeTimeDao.selectSubjectPrice(scheduleDto.getTeacherId(), scheduleDto.getSubjectId());
        BigDecimal subjectPrice = teacherSubjectPrice.getSubjectPrice();

        //每课实际价格
        Map<Integer, BigDecimal> courseAveragePrice = WrapperUtil.getAveragePrice(scheduleDto.getCourseNum(), scheduleDto.getCoursePrice());

        List<CourseScheduleDate> classTime = scheduleDto.getClassTime();
        for (int i = 0; i < classTime.size(); i++) {
            CourseScheduleDate date = classTime.get(i);
            CourseSchedule schedule = new CourseSchedule();
            schedule.setClassDate(date.getClassDate());
            schedule.setStartTime(date.getStartTime());
            schedule.setEndTime(date.getEndTime());
            schedule.setCourseGroupId(groupId);
            schedule.setType(CourseScheduleEnum.PRACTICE.getCode());
            schedule.classNum(i + 1);
            schedule.setTeacherId(scheduleDto.getTeacherId());
            schedule.setLock(1);
            schedule.setStatus(CourseScheduleEnum.NOT_START.getCode());
            schedule.setCreatedBy(scheduleDto.getStudentId());
            //写入course_schedule
            baseMapper.insert(schedule);

            Long scheduleId = schedule.getId();
            CourseScheduleStudentPayment payment = new CourseScheduleStudentPayment();
            payment.setUserId(scheduleDto.getStudentId());
            payment.setCourseId(scheduleId);
            payment.setCourseGroupId(groupId);
            payment.setCourseType(CourseScheduleEnum.PRACTICE.getCode());
            payment.setOrderNo(orderNo);
            payment.setOriginalPrice(subjectPrice);//原价
            payment.setExpectPrice(subjectPrice);//预计价格
            payment.setActualPrice(courseAveragePrice.get(i + 1));//实际价格
            //写入course_schedule_student_payment
            courseScheduleStudentPaymentService.save(payment);
        }

        OrderCreateRes orderCreateRes = new OrderCreateRes();
        orderCreateRes.setRes(true);
        orderCreateRes.setMerchId(scheduleDto.getTeacherId());
        orderCreateRes.setBizId(groupId);
        orderCreateRes.setOriginalPrice(scheduleDto.getCoursePrice());
        orderCreateRes.setExpectPrice(scheduleDto.getCoursePrice());
        orderCreateRes.setGoodNum(scheduleDto.getCourseNum());
        orderCreateRes.setGoodType(GoodTypeEnum.PRACTICE);
        HttpResponseResult<OrderCreateRes> httpResponseResult = new HttpResponseResult<>();
        httpResponseResult.setData(orderCreateRes);
        log.info("buyPracticeCourse  return {}", httpResponseResult);
        return httpResponseResult;
    }

    /**
     * 学生购买陪练课-成功-回调
     */
    @Transactional(rollbackFor = Exception.class)
    public void buyPracticeCourseSuccess(UserOrderDetailVo orderParam) {
        log.info("buyPracticeCourseSuccess  param:{}", JSON.toJSONString(orderParam));
        String orderNo = orderParam.getOrderNo();
        List<CourseScheduleStudentPaymentVo> paymentList = paymentDao.selectPaymentList(orderNo);
        if (paymentList.isEmpty()) {
            throw new BizException("订单不存在!");
        }

        //course_schedule中lock_改为0
        List<Long> scheduleIds = paymentList.stream().map(CourseScheduleStudentPayment::getCourseId).collect(Collectors.toList());
        baseMapper.updateLock(scheduleIds);

        //查询陪练课服务费
        String practiceServiceRateStr = sysConfigService.findConfigValue(SysConfigConstant.PRACTICE_SERVICE_FEE);
        BigDecimal practiceServiceRate = new BigDecimal(practiceServiceRateStr).divide(new BigDecimal("100"), 2, RoundingMode.HALF_UP);

        //写course_schedule_teacher_salary
        Date now = new Date();
        List<CourseScheduleTeacherSalary> teacherSalaryList = new ArrayList<>();
        for (CourseScheduleStudentPaymentVo payment : paymentList) {
            BigDecimal expectPrice = payment.getExpectPrice();//预计价格

            CourseScheduleTeacherSalary teacherSalary = new CourseScheduleTeacherSalary();
            teacherSalary.setTeacherId(payment.getTeacherId());
            teacherSalary.setStudentId(payment.getUserId());
            teacherSalary.setCourseScheduleId(payment.getCourseId());
            teacherSalary.setCourseGroupType(payment.getCourseType());
            teacherSalary.setCourseGroupId(payment.getCourseGroupId());
            teacherSalary.setClassNum(payment.getClassNum());
            //预计获取学生支付单课全部课酬
            teacherSalary.setExpectSalary(payment.getActualPrice());
            //实际获取扣除手续费后课酬
            teacherSalary.setActualSalary(expectPrice.subtract(expectPrice.multiply(practiceServiceRate)).setScale(2, RoundingMode.HALF_UP));//实际
            teacherSalary.setReduceSalary(BigDecimal.ZERO);
            teacherSalary.setStatus(TeacherSalaryEnum.NOT_START.getCode());
            teacherSalaryList.add(teacherSalary);
            teacherSalary.setCreateTime(now);
        }
        courseScheduleTeacherSalaryService.getDao().insertBatch(teacherSalaryList);
        log.info("buyPracticeCourseSuccess ok");
    }

    /**
     * 学生购买陪练课-失败-回调
     */
    @Transactional(rollbackFor = Exception.class)
    public void buyPracticeCourseFailed(UserOrderDetailVo orderParam) {
        String orderNo = orderParam.getOrderNo();
        List<CourseScheduleStudentPayment> paymentList = paymentDao.selectList(Wrappers.<CourseScheduleStudentPayment>lambdaQuery()
                .eq(CourseScheduleStudentPayment::getOrderNo, orderNo));
        if (paymentList.isEmpty()) {
            throw new BizException("订单不存在!");
        }

        List<Long> scheduleIds = paymentList.stream().map(CourseScheduleStudentPayment::getCourseId).collect(Collectors.toList());
        Long courseGroupId = paymentList.get(0).getCourseGroupId();

        //删除course_schedule_student_payment数据
        courseScheduleStudentPaymentService.remove(Wrappers.<CourseScheduleStudentPayment>lambdaQuery()
                .eq(CourseScheduleStudentPayment::getOrderNo, orderNo));
        //删除course_schedule数据
        this.removeByIds(scheduleIds);
        //删除course_group数据
        courseGroupService.removeById(courseGroupId);
        log.info("buyPracticeCourseFailed ok");
    }

    /**
     * @Description: 老师端-课表-日历-调课
     * @Author: cy
     * @Date: 2022/4/21
     */
    @Override
    public void courseAdjust(CourseAdjustVo adjustVo) {
        baseMapper.courseAdjust(adjustVo);
    }

    /**
     * @Description: 学生-首页-直播课&视频课
     * @Author: cy
     * @Date: 2022/4/22
     */
    @Override
    public StudentHomePage queryLiveAndVideo(Long studentId) {
        StudentHomePage homePage = new StudentHomePage();
        homePage.setLiveList(baseMapper.selectLive());
        homePage.setVideoList(baseMapper.selectVideo());
        homePage.setRecentCourses(baseMapper.selectRecentCourses(studentId));
        return homePage;
    }

    /**
     * @Description: 查询老师陪练课配置
     * @Author: cy
     * @Date: 2022/4/26
     */
    public List<TeacherSubjectPrice> teacherSubjectPrice(Long teacherId) {
        return teacherFreeTimeDao.selectPriceByTeacherId(teacherId);
    }
}