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

import com.alibaba.fastjson.JSON;
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.yonge.cooleshow.auth.api.client.SysUserFeignService;
import com.yonge.cooleshow.auth.api.entity.SysUser;
import com.yonge.cooleshow.biz.dal.constant.CourseConstant;
import com.yonge.cooleshow.biz.dal.dao.CourseGroupDao;
import com.yonge.cooleshow.biz.dal.dto.CheckCourseTimeDto;
import com.yonge.cooleshow.biz.dal.dto.LiveCourseGroupDto;
import com.yonge.cooleshow.biz.dal.dto.LiveCourseGroupDto.CoursePlanDto;
import com.yonge.cooleshow.biz.dal.dto.LiveSaleOutDto;
import com.yonge.cooleshow.biz.dal.dto.UserAccountRecordDto;
import com.yonge.cooleshow.biz.dal.dto.req.OrderReq;
import com.yonge.cooleshow.biz.dal.dto.search.*;
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.WrapperUtil;
import com.yonge.cooleshow.biz.dal.vo.*;
import com.yonge.cooleshow.biz.dal.vo.res.OrderCreateRes;
import com.yonge.cooleshow.biz.dal.wrapper.coupon.CouponOrderWrapper;
import com.yonge.cooleshow.common.constant.SysConfigConstant;
import com.yonge.cooleshow.common.entity.HttpResponseResult;
import com.yonge.cooleshow.common.enums.PostStatusEnum;
import com.yonge.toolset.base.exception.BizException;
import com.yonge.toolset.base.page.PageInfo;
import com.yonge.toolset.base.string.MessageFormatter;
import com.yonge.toolset.mybatis.support.PageUtil;
import com.yonge.toolset.thirdparty.message.MessageSenderPluginContext;
import com.yonge.toolset.utils.date.DateUtil;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RMap;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
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.ZoneId;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
 * 课程组表(CourseGroup)表服务实现类
 *
 * @author hgw
 * @since 2022-03-18 15:29:10
 */
@Service("courseGroupService")
public class CourseGroupServiceImpl extends ServiceImpl<CourseGroupDao, CourseGroup> implements CourseGroupService {

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

    @Autowired
    private SysUserFeignService sysUserFeignService;
    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private CourseScheduleService courseScheduleService;
    @Autowired
    private CoursePlanService coursePlanService;
    @Autowired
    private SysConfigService sysConfigService;
    @Autowired
    private SubjectService subjectService;
    @Autowired
    private CourseScheduleStudentPaymentService courseScheduleStudentPaymentService;
    @Autowired
    private TeacherFreeTimeService teacherFreeTimeService;
    @Autowired
    private CourseScheduleTeacherSalaryService courseScheduleTeacherSalaryService;
    @Autowired
    private ImGroupService imGroupService;
    @Autowired
    private UserOrderService userOrderService;
    @Autowired
    private SysMessageService sysMessageService;
    @Autowired
    private ImGroupMemberService imGroupMemberService;
    @Autowired
    private UserOrderRefundService userOrderRefundService;
    @Autowired
    private UserAccountService userAccountService;

    @Autowired
    private TeacherService teacherService;

    @Autowired
    private CouponInfoService couponInfoService;

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

    /**
     * 查询课程组详情-直播课详情
     *
     * @param groupId 课程组id
     */
    @Override
    public LiveCourseInfoVo queryLiveCourseInfo(Long groupId) {
        return queryLiveCourseInfo(groupId, getSysUser());
    }

    /**
     * 查询课程组详情-直播课详情
     *
     * @param groupId 课程组id
     */

    private LiveCourseInfoVo queryLiveCourseInfo(Long groupId, SysUser sysUser) {
        CourseGroup group = Optional.ofNullable(groupId).map(this::getById)
                .orElseThrow(() -> new BizException("课程组信息不存在"));

        LiveCourseInfoVo result = new LiveCourseInfoVo();
        result.setCourseGroupId(group.getId());
        result.setCourseGroupName(group.getName());
        result.setCourseStartTime(group.getCourseStartTime());
        result.setSingleCourseMinutes(group.getSingleCourseMinutes());
        result.setStatus(group.getStatus());
        result.setStudentCount(group.getPreStudentNum());
        result.setBackgroundPic(group.getBackgroundPic());
        result.setTeacherId(group.getTeacherId());
        result.setCoursePrice(group.getCoursePrice());
        result.setCourseNum(group.getCourseNum());
        result.setCourseIntroduce(group.getCourseIntroduce());
        result.setSalesStartDate(group.getSalesStartDate());
        result.setSalesEndDate(group.getSalesEndDate());
        result.setMixStudentNum(group.getMixStudentNum());
        result.setImGroupId(group.getImGroupId());
        result.setAuditVersion(group.getAuditVersion());
        SysUser teacherUser = getSysUser(group.getTeacherId());
        if (teacherUser == null) {
            throw new BizException("用户不存在");
        }
        if (sysUser.getId().equals(teacherUser.getId())) {
            result.setMyself(true);
        } else {
            result.setMyself(false);
        }
        Teacher teacher = teacherService.getById(teacherUser.getId());

        if (teacher != null) {
            result.setDegreeFlag(teacher.getDegreeFlag());
            result.setTeacherFlag(teacher.getTeacherFlag());

        }
        result.setTeacherName(teacherUser.getRealName());
        result.setUserName(teacherUser.getUsername());
        result.setAvatar(teacherUser.getAvatar());
        Optional.ofNullable(group.getSubjectId()).map(subjectService::get)
                .ifPresent(subject -> result.setSubjectName(subject.getName()));
        //课程组计划
        result.setPlanList(coursePlanService.queryCoursePlanByGroupId(groupId));
        //课程组已经购买成功的学员信息
        Map<String, Object> param = new HashMap<>();
        param.put("groupId", groupId);
        param.put("orderState", OrderStatusEnum.PAID.getCode());
        result.setStudentList(courseScheduleStudentPaymentService.queryStudentInfoByGroupId(param));
        //查询是否购买过该课程组
        Long id = sysUser.getId();

        //这里修改为，订单完成后才算购买过，待支付和支付中订单不算
        //因为页面再点击购买按钮时，会请求/userOrder/getPendingOrder接口，如果有支付中的订单，用户选择继续支付或取消订单
        OrderSearch query = new OrderSearch();
        query.setUserId(id);
        query.setGoodType(GoodTypeEnum.LIVE.getCode());
        query.setBizId(groupId);
        query.setStatus(OrderStatusEnum.PAID.getCode());
        List<UserOrderVo> userOrderVos = userOrderService.selectAllList(query);
        result.setExistBuy(0);
        if (CollectionUtils.isNotEmpty(userOrderVos)) {
            result.setExistBuy(1);
        }
        return result;
    }

    /**
     * 分页查询课程组列表
     *
     * @param param 传入参数
     *              <p> - teacherId    老师id
     *              <p> - groupStatus  课程组状态  ING(进行中)  NOT_SALE(未开售,未上架) APPLY(报名中,销售中) COMPLETE(已完成)
     *              <p> - row 条数
     *              <p> - page 页数
     *              <p> - search 模糊搜索关键字
     */
    public PageInfo<CourseGroupVo> queryPageLiveCourseGroup(Map<String, Object> param) {
        //查询该月的所有直播课程
        param.put("type", CourseScheduleEnum.LIVE.getCode());
        Page<CourseGroupVo> pageInfo = PageUtil.getPageInfo(param);
        pageInfo.setAsc("b.created_time_");
        String os = WrapperUtil.toStr(param, "os");
        if (StringUtils.isNotBlank(os) && os.equals("student")) {
            Long studentId = getSysUser().getId();
            if (Objects.isNull(studentId)) {
                throw new BizException("未查询到用户信息请重新登录");
            }
            param.put("studentId", studentId);
            IPage<CourseGroupVo> page = baseMapper.queryStudentCourseGroup(pageInfo, param);
            return PageUtil.pageInfo(page);
        }
        IPage<CourseGroupVo> page = baseMapper.queryTeacherCourseGroup(pageInfo, param);
        return PageUtil.pageInfo(page);
    }

    /**
     * 新增课程组
     *
     * @param dto 创建直播课课程组参数
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void addLiveCourse(LiveCourseGroupDto dto) {
        //批量检查老师课时在数据库是否重复
        batchCheckTeacherCourseTime(dto.getTeacherId(), dto.getCoursePlanList(), CoursePlanDto::getStartTime, CoursePlanDto::getEndTime);
        //获取锁定课时缓存，没有问题就刷新缓存
        RMap<Long, List<CourseTimeEntity>> cacheTime = getExpireLiveLockTimeCache(dto.getTeacherId());
        List<CourseTimeEntity> timeEntities = new ArrayList<>();
        dto.getCoursePlanList().forEach(o -> {
            CourseTimeEntity time = new CourseTimeEntity();
            time.setStartTime(o.getStartTime());
            time.setEndTime(o.getEndTime());
            timeEntities.add(time);
        });
        cacheTime.fastPut(dto.getTeacherId(), timeEntities);
        Date now = new Date();
        String live = CourseScheduleEnum.LIVE.getCode();
        //写入课程组表
        CourseGroup group = new CourseGroup();
        group.setType(live);
        group.setTeacherId(dto.getTeacherId());
        group.setName(dto.getName());
        group.setSubjectId(dto.getSubjectId());
        group.setSingleCourseMinutes(dto.getSingleCourseMinutes());
        group.setCourseNum(dto.getCourseNum());
        group.setCourseIntroduce(dto.getCourseIntroduce());
        group.setCoursePrice(dto.getCoursePrice());
        group.setStatus(CourseGroupEnum.NOT_SALE.getCode());
        group.setCourseStartTime(dto.getStartTime());
        group.setSalesStartDate(dto.getSalesStartDate());
        group.setSalesEndDate(dto.getSalesEndDate());
        group.setBackgroundPic(dto.getBackgroundPic());
        group.setMixStudentNum(dto.getMixStudentNum());
        group.setCreatedBy(dto.getTeacherId());
        group.setCreatedTime(now);
        this.save(group);

        //写入课程表及课程计划表
        List<CourseSchedule> courseList = new ArrayList<>();
        List<CoursePlan> planList = new ArrayList<>();
        dto.getCoursePlanList().forEach(o -> {
            CourseSchedule course = new CourseSchedule();
            course.setCourseGroupId(group.getId());
            course.setType(live);
            course.setClassNum(o.getClassNum());
            course.setTeacherId(dto.getTeacherId());
            course.setClassDate(o.getStartTime());
            course.setStartTime(o.getStartTime());
            course.setEndTime(o.getEndTime());
            course.setCreatedBy(dto.getTeacherId());
            course.setCreatedTime(now);
            course.setLock(0);
            course.setStatus(CourseScheduleEnum.NOT_START.getCode());
            courseList.add(course);

            CoursePlan plan = new CoursePlan();
            plan.setCourseGroupId(group.getId());
            plan.setClassNum(o.getClassNum());
            plan.setPlan(o.getPlan());
            plan.setCreatedTime(now);
            planList.add(plan);
        });
        courseScheduleService.getDao().insertBatch(courseList);
        coursePlanService.getDao().insertBatch(planList);

        //课程组完成删除缓存
        cacheTime.delete();
    }


    @Override
    public void updateLiveCourse(LiveCourseGroupDto dto) {
        CourseGroup courseGroup = this.getById(dto.getId());
        if (!courseGroup.getStatus().equals(CourseGroupEnum.NOT_SALE.getCode()) && !courseGroup.getStatus().equals(CourseGroupEnum.OUT_SALE.getCode())) {
            throw new BizException("只有未开售和下架状态的课程能修改");
        }
        //写入课程组表
        CourseGroup group = new CourseGroup();
        group.setId(dto.getId());
        group.setName(dto.getName());
        group.setCourseIntroduce(dto.getCourseIntroduce());
        if (courseGroup.getStatus().equals(CourseGroupEnum.OUT_SALE.getCode())) {
            group.setStatus(CourseGroupEnum.APPLY.getCode());
        }
        group.setSalesStartDate(dto.getSalesStartDate());
        group.setSalesEndDate(dto.getSalesEndDate());
        group.setBackgroundPic(dto.getBackgroundPic());
        this.saveOrUpdate(group);

        //修改课程计划表
        List<CoursePlan> planList = new ArrayList<>();
        dto.getCoursePlanList().forEach(o -> {
            CoursePlan plan = new CoursePlan();
            plan.setCourseGroupId(group.getId());
            plan.setClassNum(o.getClassNum());
            plan.setPlan(o.getPlan());
            plan.setId(o.getPlanId());
            planList.add(plan);
        });
        coursePlanService.saveOrUpdateBatch(planList);
    }

    /**
     * 取消课程组-下架课程组
     *
     * @param groupId 课程组
     */
    public void cancelCourseGroup(Long groupId) {
        //获取课程组
        CourseGroup group = this.getOne(Wrappers.<CourseGroup>lambdaQuery().
                eq(CourseGroup::getId, groupId));
        Predicate<CourseGroupEnum> courseStateFunc = e -> group.getStatus().equalsIgnoreCase(e.getCode());
        if (courseStateFunc.test(CourseGroupEnum.ING)) {
            throw new BizException("课程组进行中，无法下架/取消课程组！");
        } else if (courseStateFunc.test(CourseGroupEnum.APPLY)) {
            //查询订单有人已经在支付中则无法下架
            List<CourseScheduleStudentPayment> list = courseScheduleStudentPaymentService.list(Wrappers.<CourseScheduleStudentPayment>lambdaQuery()
                    .eq(CourseScheduleStudentPayment::getCourseGroupId, groupId));
            if (CollectionUtils.isNotEmpty(list)) {
                throw new BizException("该课程组已有学生付款或支付中无法下架或取消！");
            }
        } else if (courseStateFunc.test(CourseGroupEnum.COMPLETE)) {
            throw new BizException("课程组已完结！");
        } else if (courseStateFunc.test(CourseGroupEnum.DISSOLVE) || courseStateFunc.test(CourseGroupEnum.CANCEL)) {
            throw new BizException("课程组已下架！");
        }
        //只剩下未开售状态和上架没人购买，可以直接修改
        group.setStatus(CourseGroupEnum.CANCEL.getCode());
        this.updateById(group);
        //修改课程表
        courseScheduleService.lambdaUpdate()
                .eq(CourseSchedule::getCourseGroupId, groupId)
                .set(CourseSchedule::getStatus, CourseScheduleEnum.CANCEL.getCode())
                .update();
    }

    /**
     * 创建课时写到缓存锁定时间
     */
    @Override
    public List<CourseTimeEntity> lockCourseToCache(CheckCourseTimeDto dto) {
        // true：陪练课PRACTICE     false：LIVE直播课
        boolean courseTypeFlag = CourseScheduleEnum.existCourseType(dto.getCourseType(), "课程类型不正确！").equals(CourseScheduleEnum.PRACTICE);
        //先自校验传入时间是否交集
        List<CourseTimeEntity> timeList = dto.getTimeList();
        if (CollectionUtils.isEmpty(timeList)) {
            throw new BizException("课程时间不能为空");
        }
        if (timeList.size() > 1) {
            for (int i = 0; i < timeList.size(); i++) {
                if (i == timeList.size() - 1) {
                    break;
                }
                CourseTimeEntity o = timeList.get(i);
                List<CourseTimeEntity> newList = timeList.subList(i + 1, timeList.size());
                boolean checkParamTime = courseScheduleService.checkCourseTime(newList, CourseTimeEntity::getStartTime, CourseTimeEntity::getEndTime, o.getStartTime(), o.getEndTime());
                if (checkParamTime) {
                    throw new BizException(DateUtil.dateToString(o.getStartTime(), "yyyy年MM月dd号 HH点mm分") + "的课程时间重复!");
                }
            }
        }
        //批量检查老师课时在数据库是否重复
        batchCheckTeacherCourseTime(dto.getTeacherId(), timeList, CourseTimeEntity::getStartTime, CourseTimeEntity::getEndTime);

        //需要自动补全课时
        if (dto.getLoop() == 1) {
            //自动排课，获取排课后所有的课程时间
            timeList = autoPlanningLiveCourseTime(dto.getTeacherId(), dto.getCourseNum(), timeList, courseTypeFlag);
            timeList.sort(Comparator.comparing(CourseTimeEntity::getStartTime));
        }
        //获取老师锁课缓存
        RMap<Long, List<CourseTimeEntity>> map = getExpireLiveLockTimeCache(dto.getTeacherId());
        //陪练课/琴房课无需锁定时间
        boolean coursePianoFlag = CourseScheduleEnum.existCourseType(dto.getCourseType(), "课程类型不正确！").equals(CourseScheduleEnum.PIANO_ROOM_CLASS);
        if (!courseTypeFlag && !coursePianoFlag) {
            //直播课添加课时数据
            map.fastPut(dto.getTeacherId(), timeList);
        }
        return timeList;
    }

    /**
     * 批量检查老师课时在数据库是否重复
     *
     * @param teacherId 老师id
     * @param timeList  时间集合
     */
    public <T> void batchCheckTeacherCourseTime(Long teacherId, List<T> timeList, Function<T, Date> startTimeFun, Function<T, Date> endTimeFun) {
        //再校验数据库中课程时间和传入时间是否有交集
        timeList.forEach(o -> {
            boolean checkDataTime = courseScheduleService.checkTeacherCourseTime(teacherId, startTimeFun.apply(o), endTimeFun.apply(o));
            if (checkDataTime) {
                throw new BizException("预计安排在" + DateUtil.dateToString(startTimeFun.apply(o), "yyyy年MM月dd号 HH点mm分") + "的课程时间存在冲突!");
            }
        });
    }

    /**
     * 批量检查学生课时在数据库是否重复
     *
     * @param studentId 老师id
     * @param timeList  时间集合
     */
    public <T> void batchCheckStudentCourseTime(Long studentId, List<T> timeList, Function<T, Date> startTimeFun, Function<T, Date> endTimeFun) {
        //再校验数据库中课程时间和传入时间是否有交集
        timeList.forEach(o -> {
            boolean checkDataTime = courseScheduleService.checkStudentCourseTime(studentId, startTimeFun.apply(o), endTimeFun.apply(o));
            if (checkDataTime) {
                SysUser sysUser = sysUserFeignService.queryUserById(studentId);
                throw new BizException("学生:" + sysUser.getUsername() + ",预计安排在" + DateUtil.dateToString(startTimeFun.apply(o), "yyyy年MM月dd号 HH点mm分") + "的课程时间存在冲突!");
            }
        });
    }

    /**
     * 自动排课
     * <p>自动排课规则及场景：总5节课，填入2节，需要自动补3节
     * <p>1.把前面2节课的时间循环+1周直到填满5节课为止
     * <p>2.如果自动排课时的时间和未来课程时间有冲突则继续往后面延续一周
     *
     * @param teacherId      老师id
     * @param totalCourseNum 总课程数量
     * @param paramTimeList  当前课程的时间段
     * @param courseTypeFlag true：陪练课PRACTICE     false：LIVE直播课
     * @return 自动排课后的全部课时
     */
    private List<CourseTimeEntity> autoPlanningLiveCourseTime(Long teacherId, int totalCourseNum, List<CourseTimeEntity> paramTimeList, boolean courseTypeFlag) {
        //是否跳过节假日
        boolean skipHoliday = false;
        Long studentId = null;
        if (courseTypeFlag) {
            //查询老师陪练课设置
            TeacherFreeTime teacherTime = teacherFreeTimeService.getOne(Wrappers.<TeacherFreeTime>lambdaQuery()
                    .eq(TeacherFreeTime::getDefaultFlag, true)
                    .eq(TeacherFreeTime::getTeacherId, teacherId)
            );
            Optional.ofNullable(teacherTime).orElseThrow(() -> new BizException("未查询到老师陪练课设置！"));
            skipHoliday = teacherTime.getSkipHolidayFlag();
            studentId = getSysUser().getId();
        }

        //获取当前课程
        int nowCourseNum = paramTimeList.size();
        //获取总课程数量 - 获取当前选择的课程数量 = 要自动排课的课程数量
        int diffCourse = totalCourseNum - nowCourseNum;
        //获取课程时间，并按开始时间排序
        List<CourseTimeEntity> resultCourseTime = paramTimeList.stream()
                .sorted(Comparator.comparing(CourseTimeEntity::getStartTime))
                .collect(Collectors.toList());
        //获取最大排课周
        String maxWeekStr = sysConfigService.findConfigValue(SysConfigConstant.AUTO_PLANNING_COURSE_MAX_WEEK);
        int maxWeek = 26;//自动生成课时最大的生成周数  默认 26周
        if (StringUtils.isBlank(maxWeekStr)) {
            maxWeek = Integer.parseInt(maxWeekStr);
        }
        //从第index课程开始生成时间
        int index = 0;
        //节假日缓存数据，避免多次查询
        Map<Integer, List<String>> holidayMap = new HashMap<>();
        //开始自动补全课时
        for (int i = 0; i < diffCourse; i++) {
            //需要增加的周数
            AtomicInteger week = new AtomicInteger(1);
            //是否要一直生成课程 true：一直增加周数并生成到不冲突的时间为止
            AtomicBoolean flag = new AtomicBoolean(true);
            //生成课程
            while (flag.get()) {
                if (index == nowCourseNum) {
                    index = 0;
                    //进入新的循环周数+1
                    week.getAndIncrement();
                }
                CourseTimeEntity timeDto = resultCourseTime.get(index);
                if (week.get() > maxWeek) {
                    throw new BizException("系统自动排课时发现当前时间往后" + maxWeek + "周的课程时间已排满，请手动排课！");
                }
                Date autoStartDate = DateUtil.addWeeks(timeDto.getStartTime(), week.get());
                Date autoEndDate = DateUtil.addWeeks(timeDto.getEndTime(), week.get());

                //传入 ->true ：周数+1   ->false：停止生成时间
                Consumer<Boolean> con = (check) -> {
                    //check = true ,有交集延续1周后继续生成时间
                    if (check) {
                        week.getAndIncrement();
                        flag.set(true);
                    } else {
                        flag.set(false);
                    }
                };

                //是否跳过节假日
                if (skipHoliday) {
                    //将自动生成的开始日期转换为字符串
                    String dateStr = DateUtil.dateToString(autoStartDate);
                    //取年份
                    int calendarYear = LocalDate.parse(dateStr).getYear();
                    //获取节假日信息
                    List<String> holiday = Optional.of(calendarYear)
                            .map(holidayMap::get)
                            .filter(CollectionUtils::isNotEmpty)
                            .orElseGet(() -> {
                                List<String> list = courseScheduleService.getHoliday(calendarYear);
                                holidayMap.put(calendarYear, list);
                                return list;
                            });

                    //如果生成的日期是节假日 则跳过当前日期
                    con.accept(holiday.contains(dateStr));
                    if (flag.get()) {
                        continue;
                    }
                }

                boolean checkTime;
                //若:传入时间是1号10点和8号10点，然后1号10点自动生成的课时是8号10点那么就和传入的8号10点冲突了，这种情况需要继续往后延续1周
                checkTime = courseScheduleService.checkCourseTime(resultCourseTime, CourseTimeEntity::getStartTime, CourseTimeEntity::getEndTime, autoStartDate, autoEndDate);
                con.accept(checkTime);
                //如果和传入时间冲突则跳过
                if (flag.get()) {
                    continue;
                }

                //校验当前生成时间是否和未来的课程时间是否交集
                checkTime = courseScheduleService.checkTeacherCourseTime(teacherId, autoStartDate, autoEndDate);
                con.accept(checkTime);
                //如果和未来时间冲突则跳过
                if (flag.get()) {
                    continue;
                }

                //校验当前生成时间是否和未来的课程时间是否交集-学生
                if (courseTypeFlag) {
                    checkTime = courseScheduleService.checkStudentCourseTime(studentId, autoStartDate, autoEndDate);
                    con.accept(checkTime);
                    //如果和未来时间冲突则跳过
                    if (flag.get()) {
                        continue;
                    }
                }

                //获取老师锁课缓存
                RMap<Long, List<CourseTimeEntity>> map = getExpireLiveLockTimeCache(teacherId);
                if (map.isExists()) {
                    List<CourseTimeEntity> courseTimeCache = map.get(teacherId);
                    //校验缓存中的时间和当前自动生成的时间有没有重复
                    checkTime = courseScheduleService.checkCourseTime(courseTimeCache, CourseTimeEntity::getStartTime, CourseTimeEntity::getEndTime, autoStartDate, autoEndDate);
                    con.accept(checkTime);
                    //如果和未来时间冲突则跳过
                    if (flag.get()) {
                        continue;
                    }
                }

                //将自动生成时间收入集合
                CourseTimeEntity autoTimeDto = new CourseTimeEntity();
                autoTimeDto.setStartTime(autoStartDate);
                autoTimeDto.setEndTime(autoEndDate);
                resultCourseTime.add(autoTimeDto);
            }
            index++;
        }
        return resultCourseTime;
    }

    /**
     * 创建直播课程组-解除锁定课程时间-删除写到缓存当作锁定的课时
     *
     * @param teacherId 老师id
     */
    @Override
    public void unlockCourseToCache(Long teacherId) {
        Optional.ofNullable(teacherId)
                .ifPresent(id -> getLiveLockTimeCache(id).delete());
    }

    /**
     * 获取老师锁定课时数据的缓存-设置过期时间
     *
     * @param teacherId 老师id
     * @return 缓存
     */
    public RMap<Long, List<CourseTimeEntity>> getExpireLiveLockTimeCache(Long teacherId) {
        String lockMinuteStr = sysConfigService.findConfigValue(SysConfigConstant.CREATE_LIVE_TIME_LOCK_MINUTE);
        long lockMinute = StringUtils.isBlank(lockMinuteStr) ? 15L : Long.parseLong(lockMinuteStr);
        RMap<Long, List<CourseTimeEntity>> cache = getLiveLockTimeCache(teacherId);
        cache.expire(lockMinute, TimeUnit.MINUTES);
        return cache;
    }

    /**
     * 获取老师锁定的直播课时数据的缓存
     *
     * @param teacherId 老师id
     * @return 缓存 key teacherId value List<CourseTimeEntity>
     */
    public RMap<Long, List<CourseTimeEntity>> getLiveLockTimeCache(Long teacherId) {
        String key = CourseConstant.LOCK_COURSE_TIME_INFO.replace(CourseConstant.TEACHER_ID, teacherId.toString());
        return redissonClient.getMap(key);
    }

    @Override
    public IPage<LiveCourseGroupVo> selectAdminLivePage(IPage<LiveCourseGroupVo> page, LiveCourseGroupSearch query) {

        IPage<LiveCourseGroupVo> liveCourseGroupVoIPage = baseMapper.selectAdminLivePage(page, query);
        List<LiveCourseGroupVo> records = liveCourseGroupVoIPage.getRecords();

        if (CollectionUtils.isEmpty(records)) {
            return liveCourseGroupVoIPage;
        }
        List<Long> courseGroupIdList = records.stream()
                .map(LiveCourseGroupVo::getCourseGroupId)
                .collect(Collectors.toList());

        // 获取课程组声部
        List<LiveCourseGroupVo> subjectList = baseMapper.selectCourseGroupSubject(courseGroupIdList);
        Map<Long, List<LiveCourseGroupVo>> collect = subjectList.stream()
                .collect(Collectors.groupingBy(LiveCourseGroupVo::getCourseGroupId));
        for (LiveCourseGroupVo record : records) {
            List<LiveCourseGroupVo> liveCourseGroupVos = collect.get(record.getCourseGroupId());
            if (CollectionUtils.isEmpty(liveCourseGroupVos)) {
                continue;
            }
            record.setSubjectName(liveCourseGroupVos.get(0).getSubjectName());
        }

        return liveCourseGroupVoIPage;
    }

    @Override
    public IPage<LiveCourseGroupStudentVo> selectAdminLiveStudentPage(IPage<LiveCourseGroupStudentVo> page,
                                                                      LiveCourseGroupStudentSearch query) {
        return baseMapper.selectAdminLiveStudentPage(page, query);
    }

    @Override
    public List<LiveCourseGroupPlanVo> selectAdminLivePlan(Long courseGroupId) {
        return baseMapper.selectAdminLivePlan(courseGroupId);
    }

    @Override
    public LiveCourseGroupVo detail(Long courseGroupId) {
        return baseMapper.selectAdminLiveCourseGroup(courseGroupId);

    }

    @Override
    public IPage<LiveCourseGroupStudentCourseVo> selectAdminLiveStudentCoursePage(IPage<LiveCourseGroupStudentCourseVo> page, LiveCourseGroupStudentCourseSearch query) {
        IPage<LiveCourseGroupStudentCourseVo> studentCourseVoIPage = baseMapper.selectAdminLiveStudentCoursePage(
                page, query);
        List<LiveCourseGroupStudentCourseVo> records = studentCourseVoIPage.getRecords();
        // 设置学生上课状态
        for (LiveCourseGroupStudentCourseVo record : records) {
            if (CourseScheduleEnum.NOT_START.getCode().equals(record.getCourseStatue().getCode())) {
                record.setStatus(StudentCourseEnum.NOTSTART);
            } else {
                if (record.getStudentAttendanceId() != null) {
                    record.setStatus(StudentCourseEnum.ATTENDCLASS);
                } else {
                    record.setStatus(StudentCourseEnum.TRUANT);
                }
            }
        }
        return studentCourseVoIPage;
    }

    /**
     * 学生购买直播课程组
     *
     * @param orderReqInfo 订单信息
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public HttpResponseResult<OrderCreateRes> buyLiveCourse(OrderReq.OrderReqInfo orderReqInfo) {
        log.info("buyLiveCourse  param:{}", JSON.toJSONString(orderReqInfo));
        Map<String, Object> param = WrapperUtil.toMap(orderReqInfo.getBizContent());
        Long groupId = WrapperUtil.toLong(param, "groupId", "课程组id不能为空！");
        Long studentId = orderReqInfo.getUserId();
        //校验学生信息
        getSysUser(studentId);
        //课程组信息
        CourseGroup courseGroup = this.getOne(Wrappers.<CourseGroup>lambdaQuery()
                .eq(CourseGroup::getId, groupId)
                .eq(CourseGroup::getType, CourseScheduleEnum.LIVE.getCode())
        );
        if (Objects.isNull(courseGroup)) {
            throw new BizException("课程组不存在！");
        }
        //只要不是销售中的课程组都提示无法购买
        if (!courseGroup.getStatus().equals(CourseGroupEnum.APPLY.getCode())) {
            throw new BizException("课程已结束销售！");
        }
        //课程信息
        List<CourseSchedule> courseList = courseScheduleService.list(Wrappers.<CourseSchedule>lambdaQuery()
                .eq(CourseSchedule::getCourseGroupId, groupId));
        if (CollectionUtils.isEmpty(courseList)) {
            throw new BizException("课程组课程不存在！");
        }
        //校验购买的课程组每节课时间是否和自己的课时冲突
        batchCheckStudentCourseTime(studentId, courseList, CourseSchedule::getStartTime, CourseSchedule::getEndTime);
        //写入学生购买课程记录
        buyLiveCourseAfter(orderReqInfo.getOrderNo(), studentId, courseList, courseGroup,orderReqInfo.getActualPrice());

        OrderCreateRes orderCreateRes = new OrderCreateRes();
        orderCreateRes.setRes(true);
        orderCreateRes.setMerchId(courseGroup.getTeacherId());
        orderCreateRes.setBizId(courseGroup.getId());
        orderCreateRes.setOriginalPrice(courseGroup.getCoursePrice());
        orderCreateRes.setExpectPrice(courseGroup.getCoursePrice());
        orderCreateRes.setGoodNum(courseGroup.getCourseNum());
        orderCreateRes.setGoodType(GoodTypeEnum.LIVE);

        HttpResponseResult<OrderCreateRes> httpResponseResult = new HttpResponseResult<>();
        httpResponseResult.setData(orderCreateRes);
        log.info("buyLiveCourse  return {}", httpResponseResult);
        return httpResponseResult;
    }

    /**
     * 待订单创建完毕后继续后续操作
     *  @param orderNo     订单号
     * @param studentId   学员id
     * @param courseList  课程列表
     * @param courseGroup 直播课程组信息
     * @param actualPrice
     */
    private void buyLiveCourseAfter(String orderNo, Long studentId, List<CourseSchedule> courseList, CourseGroup courseGroup, BigDecimal actualPrice) {
        log.info("buyLiveCourse buyLiveCourseAfter -> order {} studentId {} courseList {} courseGroup {}", orderNo, studentId, JSON.toJSONString(courseList), JSON.toJSONString(courseGroup));
        //获取每节课购买的价格
        Map<Integer, BigDecimal> courseAveragePrice = WrapperUtil.getAveragePrice(courseGroup.getCourseNum(), courseGroup.getCoursePrice());
        Map<Integer, BigDecimal> courseAverageActualPrice = WrapperUtil.getAveragePrice(courseGroup.getCourseNum(),actualPrice);
        //写course_schedule_student_payment表 作为记录锁定时间用，防止重复购买，如果支付失败则删除该数据
        List<CourseScheduleStudentPayment> studentPaymentList = new ArrayList<>();
        Date now = new Date();
        AtomicInteger i = new AtomicInteger(1);
        courseList.forEach(course -> {
            CourseScheduleStudentPayment studentPayment = new CourseScheduleStudentPayment();
            studentPayment.setUserId(studentId);
            studentPayment.setCourseGroupId(courseGroup.getId());
            studentPayment.setCourseId(course.getId());
            studentPayment.setOrderNo(orderNo);
            BigDecimal amount = courseAveragePrice.get(i.get());
            BigDecimal actualAmount = courseAverageActualPrice.get(i.getAndIncrement());
            studentPayment.setOriginalPrice(amount);
            studentPayment.setExpectPrice(amount);
            studentPayment.setActualPrice(actualAmount);
            studentPayment.setCreatedTime(now);
            studentPayment.setUpdatedTime(now);
            studentPayment.setCourseType(courseGroup.getType());
            studentPaymentList.add(studentPayment);
        });
        courseScheduleStudentPaymentService.getDao().insertBatch(studentPaymentList);
    }

    /**
     * 学生购买直播课程-成功-回调
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void buyLiveCourseSuccess(UserOrderDetailVo orderParam) {
        log.info("buyLiveCourseSuccess param :{}", JSON.toJSONString(orderParam));
        String orderNo = orderParam.getOrderNo();
        //更新课程组的购买人数+1
        List<CourseScheduleStudentPayment> studentPaymentList = courseScheduleStudentPaymentService.list(Wrappers.<CourseScheduleStudentPayment>lambdaQuery()
                .eq(CourseScheduleStudentPayment::getOrderNo, orderNo)
        );
        CourseScheduleStudentPayment studentPayment;
        if (CollectionUtils.isEmpty(studentPaymentList)) {
            log.info("buyLiveCourseSuccess >>> 订单不存在！orderParam： {}", JSON.toJSONString(orderParam));
            throw new BizException("订单不存在");
        } else {
            studentPayment = studentPaymentList.get(0);
        }
        //课程组信息
        CourseGroup courseGroup = this.getOne(Wrappers.<CourseGroup>lambdaQuery()
                .eq(CourseGroup::getId, studentPayment.getCourseGroupId()));
        if (Objects.isNull(courseGroup)) {
            log.info("buyLiveCourseSuccess >>> 课程组不存在！orderParam： {}", JSON.toJSONString(orderParam));
            throw new BizException("课程组不存在");
        }
        //更新课程组的购买人数+1
        this.baseMapper.opsPreStudentNum(studentPayment.getCourseGroupId(), 1);

        // 课程组 人数 + 1
        courseGroup.setPreStudentNum(courseGroup.getPreStudentNum() + 1);
        //查询课程组课程列表
        List<CourseSchedule> courseList = courseScheduleService.list(Wrappers.<CourseSchedule>lambdaQuery()
                .eq(CourseSchedule::getCourseGroupId, studentPayment.getCourseGroupId()));
        //课程总金额
        BigDecimal totalExpectPrice = WrapperUtil.sumList(studentPaymentList, CourseScheduleStudentPayment::getExpectPrice);
        //查询直播课服务费
        String liveServiceRateStr = sysConfigService.findConfigValue(SysConfigConstant.LIVE_SERVICE_RATE);
        BigDecimal liveServiceRate = new BigDecimal(liveServiceRateStr).divide(new BigDecimal("100"), 2, RoundingMode.HALF_UP);
        //总课酬  1 - (1 * 手续费率)
        BigDecimal totalRatePrice = totalExpectPrice.subtract(totalExpectPrice.multiply(liveServiceRate)).setScale(2, RoundingMode.HALF_UP);
        //获取每节课的实际课酬 key 课堂数  value 课酬
        Map<Integer, BigDecimal> singerActualCourseSalary = WrapperUtil.getAveragePrice(courseGroup.getCourseNum(), totalRatePrice);
        //获取每节课的预计原课酬 key 课堂数  value 课酬
        Map<Integer, BigDecimal> singerExpectCourseSalary = WrapperUtil.getAveragePrice(courseGroup.getCourseNum(), totalExpectPrice);
        //写入课酬表计算-根据课程组总金额计算分配到每节课的金额
        Date now = new Date();
        List<CourseScheduleTeacherSalary> teacherSalaryList = new ArrayList<>();
        courseList.forEach(course -> {
            CourseScheduleTeacherSalary teacherSalary = new CourseScheduleTeacherSalary();
            teacherSalary.setTeacherId(course.getTeacherId());
            teacherSalary.setStudentId(studentPayment.getUserId());
            teacherSalary.setCourseScheduleId(course.getId());
            teacherSalary.setCourseGroupType(course.getType());
            teacherSalary.setCourseGroupId(course.getCourseGroupId());
            teacherSalary.setClassNum(course.getClassNum());
            teacherSalary.setCreateTime(now);
            teacherSalary.setStatus(TeacherSalaryEnum.NOT_START.getCode());
            //预计课酬
            BigDecimal ex = singerExpectCourseSalary.get(course.getClassNum());
            //实际课酬
            BigDecimal ac = singerActualCourseSalary.get(course.getClassNum());
            teacherSalary.setExpectSalary(ex);
            teacherSalary.setActualSalary(ac);
            //预计课酬-实际课酬 = 扣除费用
            teacherSalary.setReduceSalary(ex.subtract(ac));
            //备注是为直播课手续费
            teacherSalary.setReduceSalaryRemark(SysConfigConstant.LIVE_SERVICE_RATE);
            if (teacherSalary.getExpectSalary().compareTo(BigDecimal.ZERO) > 0) {
                teacherSalaryList.add(teacherSalary);
            }
        });
        if(CollectionUtils.isNotEmpty(teacherSalaryList)){
            courseScheduleTeacherSalaryService.getDao().insertBatch(teacherSalaryList);
            //写老师账户
            addTeacherAccount(orderParam, teacherSalaryList, totalExpectPrice);
        }
        log.info("buyLiveCourseSuccess ok");
        //课程购买成功后进行消息推送
        buyLiveSendMessage(studentPayment, courseGroup);
    }

    //写入老师账户
    private void addTeacherAccount(UserOrderDetailVo orderParam, List<CourseScheduleTeacherSalary> teacherSalaryList, BigDecimal totalExpectPrice) {
        //获取账期时间
        Date accountPeriodTime = userOrderService.getAccountPeriodTime(orderParam);

        //老师待入账
        List<UserAccountRecordDto> userAccountRecordDtos = new ArrayList<>();
        for (CourseScheduleTeacherSalary salary : teacherSalaryList) {
            UserAccountRecordDto dto = new UserAccountRecordDto(orderParam.getMerchId(), PostStatusEnum.WAIT, salary.getActualSalary(),
                    InOrOutEnum.IN, AccountBizTypeEnum.LIVE, salary.getCourseScheduleId(), orderParam.getGoodName(), orderParam.getOrderNo(), orderParam.getUserId(), accountPeriodTime);
            userAccountRecordDtos.add(dto);
        }
        if (CollectionUtils.isNotEmpty(userAccountRecordDtos)) {
            userAccountService.accountRecord(userAccountRecordDtos);
        }
    }

    /**
     * 课程购买成功后进行消息推送
     *
     * @param studentPayment 学生购买信息
     * @param courseGroup    课程组信息
     */
    private void buyLiveSendMessage(CourseScheduleStudentPayment studentPayment, CourseGroup courseGroup) {
        //查询老师信息
        SysUser teacherInfo = getSysUser(courseGroup.getTeacherId());
        //查询学生信息
        SysUser studentInfo = getSysUser(studentPayment.getUserId());
        try {

            String payType = "";
            if (studentPayment.getActualPrice().compareTo(BigDecimal.ZERO) >0) {
                payType = "购买";
            } else {
                payType = "领取";
            }

            //极光-消息推送-学生端-通知学生购买成功-跳转到APP
            MessageTypeEnum liveBuy = MessageTypeEnum.LIVE_BUY;
            //查询推送跳转的url
            String liveBuyUrl = sysMessageService.selectConfigUrl(liveBuy.getCode());
            Map<Long, String> studentReceivers = new HashMap<>();
            studentReceivers.put(studentInfo.getId(), studentInfo.getPhone());
            sysMessageService.batchSendMessage(MessageSenderPluginContext.MessageSender.JIGUANG, liveBuy,
                    studentReceivers, null, 0, liveBuyUrl, ClientEnum.STUDENT.getCode(),payType,
                    teacherInfo.getUsername(), courseGroup.getName());
            log.info("buyLiveCourseSuccess buyLiveSendMessage LIVE_BUY ok");

            //短信-消息推送-学生端-通知学生购买成功-跳转到APP
            MessageTypeEnum smsLiveBuy = MessageTypeEnum.SMS_LIVE_BUY;
            //查询推送跳转的url
            String smsLiveBuyUrl = sysMessageService.selectConfigUrl(smsLiveBuy.getCode());
            Map<Long, String> smsStudentReceivers = new HashMap<>();
            smsStudentReceivers.put(studentInfo.getId(), studentInfo.getPhone());
            sysMessageService.batchSendMessage(MessageSenderPluginContext.MessageSender.AWSMS, smsLiveBuy,
                    smsStudentReceivers, null, 0, null, null,payType,
                    teacherInfo.getUsername(), courseGroup.getName(), smsLiveBuyUrl);
            log.info("buyLiveCourseSuccess buyLiveSendMessage SMS_BUY_LIVE ok");

            if (studentPayment.getActualPrice().compareTo(BigDecimal.ZERO) >0) {
                //极光-消息推送-老师端-通知老师有学生购买课程-跳转到APP
                MessageTypeEnum studentBuyLive = MessageTypeEnum.STUDENT_BUY_LIVE;
                //查询推送跳转的url
                String studentBuyLiveUrl = sysMessageService.selectConfigUrl(studentBuyLive.getCode(), courseGroup.getId());
                Map<Long, String> teacherReceivers = new HashMap<>();
                teacherReceivers.put(teacherInfo.getId(), teacherInfo.getPhone());
                sysMessageService.batchSendMessage(MessageSenderPluginContext.MessageSender.JIGUANG, studentBuyLive,
                                                   teacherReceivers, null, 0, studentBuyLiveUrl, ClientEnum.TEACHER.getCode(),
                                                   studentInfo.getUsername(), courseGroup.getName());
                log.info("buyLiveCourseSuccess buyLiveSendMessage STUDENT_BUY_LIVE ok");

                //短信-消息推送-老师端-通知老师有学生购买课程-无需跳转到APP
                Map<Long, String> smsTeacherReceivers = new HashMap<>();
                smsTeacherReceivers.put(teacherInfo.getId(), teacherInfo.getPhone());
                sysMessageService.batchSendMessage(MessageSenderPluginContext.MessageSender.AWSMS, MessageTypeEnum.SMS_BUY_LIVE,
                                                   smsTeacherReceivers, null, 0, null, null, studentInfo.getUsername(), courseGroup.getName());
                log.info("buyLiveCourseSuccess buyLiveSendMessage SMS_BUY_LIVE ok");

            }
            //判断是否到达最低成课人数
            if (Objects.equals(courseGroup.getPreStudentNum(), courseGroup.getMixStudentNum())) {
                //极光-消息推送-老师端-通知老师课程最小开课人数已达标
                MessageTypeEnum liveCompletionSuccess = MessageTypeEnum.LIVE_COMPLETION_SUCCESS;
                Map<Long, String> liveCompletionSuccessReceivers = new HashMap<>();
                liveCompletionSuccessReceivers.put(teacherInfo.getId(), teacherInfo.getPhone());
                sysMessageService.batchSendMessage(MessageSenderPluginContext.MessageSender.JIGUANG, liveCompletionSuccess,
                        liveCompletionSuccessReceivers, null, 0, null, ClientEnum.TEACHER.getCode(),
                        courseGroup.getName());
                log.info("buyLiveCourseSuccess buyLiveSendMessage LIVE_COMPLETION_SUCCESS ok");

                //短信-消息推送-老师端-通知老师课程最小开课人数已达标
                Map<Long, String> smsLiveCompletionSuccessReceivers = new HashMap<>();
                smsLiveCompletionSuccessReceivers.put(teacherInfo.getId(), teacherInfo.getPhone());
                sysMessageService.batchSendMessage(MessageSenderPluginContext.MessageSender.AWSMS, MessageTypeEnum.SMS_LIVE_COMPLETION_SUCCESS,
                        smsLiveCompletionSuccessReceivers, null, 0, null, null,
                        courseGroup.getName());
                log.info("buyLiveCourseSuccess buyLiveSendMessage SMS_LIVE_COMPLETION_SUCCESS ok");
            }
        } catch (Exception ex) {
            log.error("buyLiveCourseSuccess buyLiveSendMessage error", ex.getCause());
        }
    }

    @Override
    public void buyLiveCourseCancel(UserOrderDetailVo orderParam) {
        courseScheduleStudentPaymentService.remove(Wrappers.<CourseScheduleStudentPayment>lambdaQuery()
                .eq(CourseScheduleStudentPayment::getOrderNo, orderParam.getOrderNo()));
    }

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

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

    /**
     * <p>定时将符合开售日期的未开售的直播课课程组修改为报名中状态-每隔10分钟执行一次
     * <p>定时将符合结束售卖日期的在售卖中的直播课课程组修改为取消或者成课状态状态-每隔10分钟执行一次
     */
    public void opsCourseGroup() {
        openCourseGroup();
        closeCourseGroup();
    }

    @Override
    public void refundAfter(UserOrderDetailVo userOrderDetailVo) {

    }

    /**
     * 学生购买直播课程-成功-回调
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void refundSuccess(UserOrderDetailVo orderParam) {
        log.info("refundSuccess");
        String orderNo = orderParam.getOrderNo();
        List<CourseScheduleStudentPayment> studentPaymentList = courseScheduleStudentPaymentService.list(Wrappers.<CourseScheduleStudentPayment>lambdaQuery()
                .eq(CourseScheduleStudentPayment::getOrderNo, orderNo)
        );
        CourseScheduleStudentPayment studentPayment;
        if (CollectionUtils.isEmpty(studentPaymentList)) {
            log.info("buyLiveCourseSuccess >>> 订单不存在！orderParam： {}", JSON.toJSONString(orderParam));
            throw new BizException("订单不存在");
        } else {
            studentPayment = studentPaymentList.get(0);
        }

        // 取消老师课酬
        cancelTeacherSalary(studentPayment.getUserId(), studentPayment.getCourseGroupId());

        // 删除学生购买记录
        buyLiveCourseCancel(orderParam);
    }

    /**
     * 取消老师课酬
     *
     * @param userId        学生id
     * @param courseGroupId 课程组id
     */
    private void cancelTeacherSalary(Long userId, Long courseGroupId) {
        List<CourseScheduleTeacherSalary> list = courseScheduleTeacherSalaryService.lambdaQuery()
                .eq(CourseScheduleTeacherSalary::getStudentId, userId)
                .eq(CourseScheduleTeacherSalary::getCourseGroupId, courseGroupId)
                .list();
        list = list.stream().filter(csts ->
                        TeacherSalaryEnum.NOT_START.getCode().equals(csts.getStatus())
                ).peek(csts -> csts.setStatus(CourseScheduleEnum.CANCEL.getCode()))
                .collect(Collectors.toList());
        if (CollectionUtils.isEmpty(list)) {
            return;
        }

        courseScheduleTeacherSalaryService.saveOrUpdateBatch(list);

    }

    @Override
    public void refundCancel(UserOrderDetailVo userOrderDetailVo) {

    }

    @Override
    public IPage<CourseGroupPracticeVo> selectPracticeGroup(IPage<CourseGroupPracticeVo> page, CourseGroupSearch search) {
        return baseMapper.selectPracticeGroup(page, search);
    }

    @Override
    public IPage<CourseGroupPracticeDetailVo> selectPracticeGroupDetail(IPage<CourseGroupPracticeDetailVo> page, CourseGroupDetailSearch search) {
        return baseMapper.selectPracticeGroupDetail(page, search);
    }

    @Override
    public IPage<CourseGroupLiveVo> selectLiveGroup(IPage<CourseGroupLiveVo> page, CourseGroupSearch search) {
        return baseMapper.selectLiveGroup(page, search);
    }

    @Override
    public IPage<CourseSchedulePaymentVo> selectLiveGroupStudent(IPage<CourseSchedulePaymentVo> page, CourseGroupSearch search) {
        return baseMapper.selectLiveGroupStudent(page, search);
    }

    @Override
    public IPage<CourseSchedulePlanVo> selectLiveGroupPlan(IPage<CourseSchedulePlanVo> page, CourseGroupSearch search) {
        return baseMapper.selectLiveGroupPlan(page, search);
    }

    @Override
    public IPage<CourseGroupVideoVo> selectVideoGroup(IPage<CourseGroupVideoVo> page, CourseGroupSearch search) {
        search.setStatus(AuthStatusEnum.PASS.getCode());
        return baseMapper.selectVideoGroup(page, search);
    }

    @Override
    public IPage<CourseGroupStudentVo> selectVideoGroupStudent(IPage<CourseGroupStudentVo> page, CourseGroupSearch search) {
        return baseMapper.selectVideoGroupStudent(page, search);
    }

    @Override
    public IPage<VideoLessonGroupDetail> selectVideoGroupPlan(IPage<VideoLessonGroupDetail> page, CourseGroupSearch search) {
        return baseMapper.selectVideoGroupPlan(page, search);
    }

    @Override
    public CourseGroupInfoVo selectLiveGroupInfo(Long groupId) {
        return baseMapper.selectGroupInfoVo(groupId);
    }

    /**
     * 定时将符合开售日期的未开售的直播课课程组修改为报名中状态
     */
    private void openCourseGroup() {
        LocalDate today = LocalDate.now();
        LocalDate yesterday = today.plusDays(-1L);
        //查询今天未开售的课程组
        List<CourseGroup> courseGroupList = this.list(Wrappers.<CourseGroup>lambdaQuery()
                .eq(CourseGroup::getType, CourseScheduleEnum.LIVE.getCode())
                .ge(CourseGroup::getSalesStartDate, yesterday)
                .le(CourseGroup::getSalesStartDate, today)
                .eq(CourseGroup::getStatus, CourseGroupEnum.NOT_SALE.getCode()));
        if (CollectionUtils.isEmpty(courseGroupList)) {
            return;
        }
        //修改为报名中状态
        courseGroupList.forEach(courseGroup -> {
            courseGroup.setStatus(CourseGroupEnum.APPLY.getCode());
            try {
                this.updateById(courseGroup);
            } catch (Exception e) {
                log.error("openCourseGroup error >>> courseGroup: {} ", JSON.toJSONString(courseGroup));
                log.error("openCourseGroup error >>> ", e.getCause());
            }
        });
    }

    /**
     * 定时将符合结束售卖日期的在售卖中的直播课课程组修改为取消或者成课状态状态
     */
    private void closeCourseGroup() {
        //查询昨天要结束售卖的课程组
        List<CourseGroup> courseGroupList = this.list(Wrappers.<CourseGroup>lambdaQuery()
                .eq(CourseGroup::getType, CourseScheduleEnum.LIVE.getCode())
                .eq(CourseGroup::getSalesEndDate, LocalDate.now().plusDays(-1))
                .in(CourseGroup::getStatus, CourseGroupEnum.APPLY.getCode(),CourseGroupEnum.OUT_SALE.getCode()));
        if (CollectionUtils.isEmpty(courseGroupList)) {
            return;
        }
        courseGroupList.forEach(courseGroup -> {
            try {
                List<CourseScheduleStudentPayment> list = courseScheduleStudentPaymentService.list(Wrappers.<CourseScheduleStudentPayment>lambdaQuery()
                                           .eq(CourseScheduleStudentPayment::getCourseGroupId, courseGroup.getId()));
                Set<Long> userIds = list.stream().map(CourseScheduleStudentPayment::getUserId).collect(Collectors.toSet());
                if (courseGroup.getPreStudentNum() >= courseGroup.getMixStudentNum() &&courseGroup.getStatus().equals(CourseGroupEnum.APPLY.getCode())) {
                    //人数达标则修改课程组为进行中状态
                    courseGroup.setStatus(CourseGroupEnum.ING.getCode());
                    //创建群聊 并添加人员到群中
                    String imGroupId = imGroupService.autoCreate(courseGroup.getId(), courseGroup.getType());

                    imGroupMemberService.initGroupMembers(imGroupId, userIds, ImGroupMemberRoleType.STUDENT);
                    //添加老师进群
                    imGroupMemberService.initGroupMembers(imGroupId, Collections.singleton(courseGroup.getTeacherId()), ImGroupMemberRoleType.TEACHER);
                    courseGroup.setImGroupId(imGroupId);
                } else {
                    //人数未达标则修改课程组为取消状态
                    courseGroup.setStatus(CourseGroupEnum.CANCEL.getCode());
                    //更新课程组下课程状态为取消
                    courseScheduleService.update(Wrappers.<CourseSchedule>lambdaUpdate()
                            .eq(CourseSchedule::getCourseGroupId, courseGroup.getId())
                            .set(CourseSchedule::getStatus, CourseScheduleEnum.CANCEL.getCode())
                    );
                    // 老师直播课成课失败发送消息
                    this.sendMessage(courseGroup);

                    // 学生直播课成课失败发送消息
                    this.sendStudentMessage(userIds,courseGroup);

                    //退款
                    this.refund(courseGroup);
                }
                courseGroup.setUpdatedTime(new Date());
                this.updateById(courseGroup);
            } catch (Exception e) {
                log.error("closeCourseGroup error >>> courseGroup: {} ", JSON.toJSONString(courseGroup));
                log.error("closeCourseGroup error >>> ", e.getCause());
            }
        });
    }

    private void sendStudentMessage(Set<Long> userIds, CourseGroup courseGroup) {
        for (Long userId : userIds) {

                SysUser user = sysUserFeignService.queryUserById(userId);
                // 发短信
                try {
                    Map<Long, String> receivers = new HashMap<>();
                    receivers.put(user.getId(), user.getPhone());
                    sysMessageService.batchSendMessage(MessageSenderPluginContext.MessageSender.AWSMS, MessageTypeEnum.SMS_STUDENT_LIVE_COMPLETION_FAIL,
                                                       receivers, null, 0, null, null, courseGroup.getName());
                } catch (Exception e) {
                    log.warn("学生直播课成课失败短信发送失败，{}", e.getMessage());
                }

                // 发推送
                try {
                    Map<Long, String> receivers = new HashMap<>();
                    receivers.put(user.getId(), user.getPhone());
                    sysMessageService.batchSendMessage(MessageSenderPluginContext.MessageSender.JIGUANG, MessageTypeEnum.STUDENT_LIVE_COMPLETION_FAIL,
                                                       receivers, null, 0, null, ClientEnum.STUDENT.getCode(), courseGroup.getName());
                } catch (Exception e) {
                    log.warn("学生直播课成课失败推送发送失败，{}", e.getMessage());
                }
        }
    }

    /**
     * 直播课成课失败退款
     *
     * @param courseGroup
     */
    private void refund(CourseGroup courseGroup) {
        // 退款
        List<String> orderNoList = courseScheduleStudentPaymentService.getOrderNoByGroupId(courseGroup.getId());
        for (String orderNo : orderNoList) {
            try {
                userOrderRefundService.orderRefund(orderNo, "直播课成课失败退款");

                //退还优惠券
                couponInfoService.updateUserOrderCouponInfo(CouponOrderWrapper.builder().orderNo(orderNo).reset(true).build());
            } catch (Exception e) {
                log.warn("直播课成课失败退款 退款失败，退款订单号 {}", orderNo);
                log.error("直播课成课失败退款 退款失败", e.getCause());
            }
        }
    }

    /**
     * 直播课成课失败发送消息
     *
     * @param courseGroup 课程组
     */
    private void sendMessage(CourseGroup courseGroup) {
        // 直播课下架后，不推送直播课成课失败消息
        if (courseGroup.getStatus().equals(CourseGroupEnum.OUT_SALE.getCode())) {
            return;
        }
        // 发短信
        try {
            SysUser user = sysUserFeignService.queryUserById(courseGroup.getTeacherId());
            Map<Long, String> receivers = new HashMap<>();
            receivers.put(user.getId(), user.getPhone());
            sysMessageService.batchSendMessage(MessageSenderPluginContext.MessageSender.AWSMS, MessageTypeEnum.SMS_LIVE_COMPLETION_FAIL,
                    receivers, null, 0, null, null, courseGroup.getName());
        } catch (Exception e) {
            log.warn("直播课成课失败短信发送失败，{}", e.getMessage());
        }

        // 发推送
        try {
            SysUser user = sysUserFeignService.queryUserById(courseGroup.getTeacherId());
            Map<Long, String> receivers = new HashMap<>();
            receivers.put(user.getId(), user.getPhone());
            sysMessageService.batchSendMessage(MessageSenderPluginContext.MessageSender.JIGUANG, MessageTypeEnum.LIVE_COMPLETION_FAIL,
                    receivers, null, 0, null, ClientEnum.TEACHER.getCode(), courseGroup.getName());
        } catch (Exception e) {
            log.warn("直播课成课失败推送发送失败，{}", e.getMessage());
        }
    }

    @Override
    public IPage<CourseGroupPianoVo> selectPianoGroup(IPage<CourseGroupPianoVo> page, CourseGroupSearch search) {
        return baseMapper.selectPianoGroup(page, search);
    }

    @Override
    public IPage<CourseGroupPianoDetailVo> selectPianoGroupDetail(IPage<CourseGroupPianoDetailVo> page, CourseGroupDetailSearch search) {
        if (search.getStudentSign() != null) {
            List<StudentSignVo> list = studentSign(null, CourseScheduleEnum.PIANO_ROOM_CLASS.getCode());
            List<Long> collect = new ArrayList<>();
            if (search.getStudentSign() == 0) {//异常学生
                collect = list.stream().filter(s -> (s.getStudentOutSign() + s.getStudentInSign()) != 2).map(StudentSignVo::getCourseId).collect(Collectors.toList());
            }
            if (search.getStudentSign() == 1) {//正常学生
                collect = list.stream().filter(s -> (s.getStudentOutSign() + s.getStudentInSign()) == 2).map(StudentSignVo::getCourseId).collect(Collectors.toList());
            }
            collect.add(Long.valueOf(-1));
            search.setCourseIds(collect);
        }

        IPage<CourseGroupPianoDetailVo> pianoGroupDetail = baseMapper.selectPianoGroupDetail(page, search);
        List<CourseGroupPianoDetailVo> records = pianoGroupDetail.getRecords();

        for (CourseGroupPianoDetailVo record : records) {
            List<StudentSignVo> studentSigns = studentSign(record.getCourseId(), null);
            for (StudentSignVo studentSign : studentSigns) {
                if ((studentSign.getStudentInSign() + studentSign.getStudentOutSign()) == 2) {//签到正常
                    record.setStudentSign(1);
                } else {//签到异常
                    record.setStudentSign(0);
                }
            }
        }
        return pianoGroupDetail;
    }

    @Override
    public List<StudentSignVo> studentSign(Long courseId, String type) {
        return baseMapper.studentSign(courseId, type);
    }

    @Override
    public IPage<CourseGroupPianoVo> selectPianoGroupStudent(IPage<CourseGroupPianoVo> page, CourseGroupSearch search) {
        return baseMapper.selectPianoGroupStudent(page, search);
    }

    @Override
    public IPage<CourseGroupPianoDetailVo> selectPianoGroupStudentDetail(IPage<CourseGroupPianoDetailVo> page, CourseGroupDetailSearch search) {
        return baseMapper.selectPianoGroupStudentDetail(page, search);
    }

    @Override
    public IPage<CourseGroupPianoVo> selectPianoGroupTeacher(IPage<CourseGroupPianoVo> page, CourseGroupSearch search) {
        return baseMapper.selectPianoGroupTeacher(page, search);
    }

    @Override
    public IPage<CourseGroupPianoDetailVo> selectPianoGroupTeacherDetail(IPage<CourseGroupPianoDetailVo> page, CourseGroupDetailSearch search) {
        if (search.getStudentSign() != null) {
            List<StudentSignVo> list = studentSign(null, CourseScheduleEnum.PIANO_ROOM_CLASS.getCode());
            List<Long> collect = new ArrayList<>();
            if (search.getStudentSign() == 0) {//异常学生
                collect = list.stream().filter(s -> (s.getStudentOutSign() + s.getStudentInSign()) != 2).map(StudentSignVo::getCourseId).collect(Collectors.toList());
            }
            if (search.getStudentSign() == 1) {//正常学生
                collect = list.stream().filter(s -> (s.getStudentOutSign() + s.getStudentInSign()) == 2).map(StudentSignVo::getCourseId).collect(Collectors.toList());
            }
            collect.add((long) -1);
            search.setCourseIds(collect);
        }

        IPage<CourseGroupPianoDetailVo> detailPage = baseMapper.selectPianoGroupTeacherDetail(page, search);
        List<CourseGroupPianoDetailVo> records = detailPage.getRecords();

        for (CourseGroupPianoDetailVo record : records) {
            List<StudentSignVo> studentSigns = studentSign(record.getCourseId(), null);
            for (StudentSignVo studentSign : studentSigns) {
                if ((studentSign.getStudentInSign() + studentSign.getStudentOutSign()) == 2) {//签到正常
                    record.setStudentSign(1);
                } else {//签到异常
                    record.setStudentSign(0);
                }
            }
        }
        return detailPage;
    }

    @Override
    public ShareProfitVo shareLiveCourseProfit(SysUser sysUser, Long liveGroupId) {
        LiveCourseInfoVo liveCourseInfoVo = queryLiveCourseInfo(liveGroupId, sysUser);
        SysUser teacher = this.getSysUser(liveCourseInfoVo.getTeacherId());

        LiveCourseGroupShareVo liveCourseGroupShareVo = new LiveCourseGroupShareVo();
        BeanUtils.copyProperties(liveCourseInfoVo, liveCourseGroupShareVo);
        liveCourseGroupShareVo.setShareTeacherAvatar(teacher.getAvatar());
        liveCourseGroupShareVo.setShareTeacherName(teacher.getUsername());

        String teacherLiveGroupShareProfitUrl = sysConfigService.findConfigValue(SysConfigConstant.TEACHER_LIVE_GROUP_SHARE_PROFIT_URL);

        ShareProfitVo result = new ShareProfitVo();
        result.setTeacherId(sysUser.getId());
        result.setType("LIVE");
        result.setLiveCourseGroup(liveCourseGroupShareVo);
        result.setUrl(MessageFormatter.arrayFormat(teacherLiveGroupShareProfitUrl, liveGroupId, sysUser.getId()));
        result.setName(sysUser.getUsername());
        result.setAvatar(sysUser.getAvatar());
        return result;
    }

    @Override
    @Transactional
    public Boolean liveSaleOut(LiveSaleOutDto dto) {


        CourseGroup liveCourseGroupVo = this.getById(dto.getCourseGroupId());
        if (liveCourseGroupVo == null || !CourseScheduleEnum.LIVE.getCode().equals(liveCourseGroupVo.getType())) {
            throw new BizException("没找到课程组信息");
        }

        // 条件检查
        if(dto.getStatus() == 0 && StringUtils.isEmpty(dto.getReason())) {
            throw  new BizException("请填写下架原因");
        } else if (dto.getStatus() == 0  && !CourseGroupEnum.APPLY.getCode().equals(liveCourseGroupVo.getStatus())) {
            throw new BizException("只能下架销售中的课程组");
        } else if (dto.getStatus() == 1  && !CourseGroupEnum.OUT_SALE.getCode().equals(liveCourseGroupVo.getStatus())) {
            throw new BizException("只能上架被下架的课程组");
        } else if ( LocalDate.now().compareTo(liveCourseGroupVo.getSalesEndDate().toInstant().atZone(ZoneId.systemDefault()).toLocalDate()) > 0) {
            throw new BizException("课程组售卖时间已结束，不能操作");
        }

        if (dto.getStatus() == 0) {
            liveCourseGroupVo.setStatus(CourseGroupEnum.OUT_SALE.getCode());

            // 发送下架消息
            sendOutSaleMessage(dto, liveCourseGroupVo);

        } else {
            liveCourseGroupVo.setStatus(CourseGroupEnum.APPLY.getCode());
        }
        liveCourseGroupVo.setReason(dto.getReason());

        return this.updateById(liveCourseGroupVo);
    }

    @Override
    public List<Subject> liveSubject() {

        List<Subject> subjectList = baseMapper.selectLiveSubject();
        List<Long> subjectParentIdList = subjectList.stream().map(Subject::getParentSubjectId).collect(Collectors.toList());
        List<Subject> parentSubjectList = subjectService.findBySubjectByIdList(subjectParentIdList);
        for (Subject subject : parentSubjectList) {
            List<Subject> children = subjectList.stream()
                           .filter(subject1 -> subject1.getParentSubjectId().equals(subject.getId()))
                           .collect(Collectors.toList());
            subject.setSubjects(children);
        }
        return parentSubjectList;
    }


    private void sendOutSaleMessage(LiveSaleOutDto dto, CourseGroup liveCourseGroupVo) {
        // 发送课程下架通知
        try {
            SysUser user = sysUserFeignService.queryUserById(liveCourseGroupVo.getTeacherId());
            Map<Long, String> receivers = new HashMap<>();
            receivers.put(user.getId(), user.getPhone());
            sysMessageService.batchSendMessage(MessageSenderPluginContext.MessageSender.JIGUANG, MessageTypeEnum.LIVE_COURSE_OUT_SALE_REASON,
                                               receivers, null, 0, null, ClientEnum.TEACHER.getCode(),
                                               liveCourseGroupVo.getName(), dto.getReason());
        } catch (Exception e) {
            log.warn("直播课下架推送发送失败，{}", e.getMessage());
        }
    }
}

