package com.qmth.themis.admin.api;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.qmth.themis.business.annotation.ApiJsonObject;
import com.qmth.themis.business.annotation.ApiJsonProperty;
import com.qmth.themis.business.bean.admin.*;
import com.qmth.themis.business.cache.ExamRecordCacheUtil;
import com.qmth.themis.business.cache.bean.ExamActivityCacheBean;
import com.qmth.themis.business.cache.bean.ExamCacheBean;
import com.qmth.themis.business.cache.bean.ExamStudentCacheBean;
import com.qmth.themis.business.constant.SystemConstant;
import com.qmth.themis.business.dto.AuthDto;
import com.qmth.themis.business.dto.MqDto;
import com.qmth.themis.business.dto.cache.TEStudentCacheDto;
import com.qmth.themis.business.dto.response.TBExamInvigilateUserDto;
import com.qmth.themis.business.entity.*;
import com.qmth.themis.business.enums.*;
import com.qmth.themis.business.excel.ExportUtils;
import com.qmth.themis.business.service.*;
import com.qmth.themis.business.util.JacksonUtil;
import com.qmth.themis.business.util.MqUtil;
import com.qmth.themis.business.util.OssUtil;
import com.qmth.themis.business.util.ServletUtil;
import com.qmth.themis.common.enums.ExceptionResultEnum;
import com.qmth.themis.common.exception.BusinessException;
import com.qmth.themis.common.util.Result;
import com.qmth.themis.common.util.ResultUtil;
import io.swagger.annotations.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import java.io.File;
import java.util.*;

/**
 * @Description: 监考信息 前端控制器
 * @Param:
 * @return:
 * @Author: wangliang
 * @Date: 2020/6/25
 */
@Api(tags = "监考信息Controller")
@RestController
@RequestMapping(SystemConstant.PREFIX_URL_ADMIN + "/invigilate")
@Validated
public class TIeInvigilateController {
    private final static Logger log = LoggerFactory.getLogger(TIeInvigilateController.class);

    @Resource
    MqDtoService mqDtoService;

    @Resource
    TEExamBreachLogService teExamBreachLogService;

    @Resource
    TOeExamRecordService tOeExamRecordService;

    @Resource
    TEExamStudentService teExamStudentService;

    @Resource
    TIeInvigilateWarnInfoService tIeInvigilateWarnInfoService;

    @Resource
    TEExamStudentLogService teExamStudentLogService;

    @Resource
    TEExamActivityService teExamActivityService;

    @Resource
    TEExamService teExamService;

    @Resource
    OssUtil ossUtil;

    @Resource
    MqUtil mqUtil;

    @Resource
    ThemisCacheService themisCacheService;

    @Resource
    TBExamInvigilateUserService tbExamInvigilateUserService;

    @Resource
    TIeExamInvigilateNoticeService tIeExamInvigilateNoticeService;

    @Resource
    TIeExamMediaLogService tIeExamMediaLogService;

    @Resource
    TEExamSummaryService teExamSummaryService;

    @ApiOperation(value = "实时监控台视频列表接口")
    @RequestMapping(value = "/list/video", method = RequestMethod.POST)
    @ApiResponses({@ApiResponse(code = 200, message = "监考监控信息", response = InvigilateListVideoBean.class)})
//    @RedisLimitAnnotation(key = "listVideo", period = 1, count = 1)
    public Result listVideo(@ApiParam(value = "考试批次id", required = false) @RequestParam(required = false) Long examId,
                            @ApiParam(value = "考试场次id", required = false) @RequestParam(required = false) Long examActivityId,
                            @ApiParam(value = "虚拟考场代码", required = false) @RequestParam(required = false) String roomCode,
                            @ApiParam(value = "试题下载状态", required = false) @RequestParam(required = false) Integer paperDownload,
                            @ApiParam(value = "考生状态", required = false) @RequestParam(required = false) String status,
                            @ApiParam(value = "姓名", required = false) @RequestParam(required = false) String name,
                            @ApiParam(value = "证件号", required = false) @RequestParam(required = false) String identity,
                            @ApiParam(value = "预警量min", required = false) @RequestParam(required = false) Integer minWarningCount,
                            @ApiParam(value = "预警量max", required = false) @RequestParam(required = false) Integer maxWarningCount,
                            @ApiParam(value = "客户端网络通信状态", required = false) @RequestParam(required = false) String clientWebsocketStatus,
                            @ApiParam(value = "监控设备来源", required = false) @RequestParam(required = false) String monitorVideoSource,
                            @ApiParam(value = "摄像头监控设备推流状态('START'or'STOP')", required = false) @RequestParam(required = false) String cameraMonitorStatus,
                            @ApiParam(value = "屏幕监控设备推流状态('START'or'STOP')", required = false) @RequestParam(required = false) String screenMonitorStatus,
                            @ApiParam(value = "手机端主机位监控设备推流状态('START'or'STOP')", required = false) @RequestParam(required = false) String mobileFirstMonitorStatus,
                            @ApiParam(value = "手机端辅机位监控设备推流状态('START'or'STOP')", required = false) @RequestParam(required = false) String mobileSecondMonitorStatus,
                            @ApiParam(value = "分页页码", required = true) @RequestParam @Min(SystemConstant.PAGE_NUMBER_MIN) int pageNumber,
                            @ApiParam(value = "分页数", required = true) @RequestParam @Min(SystemConstant.PAGE_SIZE_MIN) @Max(SystemConstant.PAGE_SIZE_MAX) int pageSize) {
        TBUser tbUser = (TBUser) ServletUtil.getRequestAccount();
        AuthDto authDto = themisCacheService.addAccountAuthCache(tbUser.getId());
        //如果有监考员角色，只能查看自己所监考的考场，巡考员和管理员则可以查看全部考场
        Long userId = null;
        if (authDto.getRoleCodes().toString().contains(RoleEnum.INVIGILATE.name())) {
            userId = tbUser.getId();
        }
        IPage<InvigilateListVideoBean> invigilateListVideoBeanIPage = tOeExamRecordService.invigilatePageListVideo(new Page<>(pageNumber, pageSize), examId, examActivityId, roomCode, paperDownload, status, name, identity, minWarningCount, maxWarningCount, clientWebsocketStatus, cameraMonitorStatus, screenMonitorStatus, mobileFirstMonitorStatus, mobileSecondMonitorStatus, userId, tbUser.getOrgId());
        if (Objects.nonNull(invigilateListVideoBeanIPage)) {
            List<InvigilateListVideoBean> invigilateListVideoBeanList = invigilateListVideoBeanIPage.getRecords();
            ExamCacheBean examCacheBean = null;
            if (Objects.nonNull(examId)) {
                examCacheBean = teExamService.getExamCacheBean(examId);
            }
            if (Objects.nonNull(invigilateListVideoBeanList) && invigilateListVideoBeanList.size() > 0) {
                ExamCacheBean finalExamCacheBean = examCacheBean;
                invigilateListVideoBeanList.forEach(s -> {
                    s.setBasePhotoPath(
                            ossUtil.getAliYunOssPublicDomain().getPublicUrl() + File.separator + s.getBasePhotoPath());
                    List<String> monitorVideoSourceList = null;
                    if (Objects.nonNull(s.getMonitorVideoSource()) && !Objects.equals(s.getMonitorVideoSource().trim().replaceAll(" ", ""), "")) {
                        monitorVideoSourceList = Arrays.asList(s.getMonitorVideoSource().trim().toUpperCase().replaceAll(" ", "").split(","));
                    }
                    if (Objects.nonNull(monitorVideoSourceList) && monitorVideoSourceList.size() > 0) {
                        monitorVideoSourceList.forEach(l -> {
                            MonitorVideoSourceEnum source = MonitorVideoSourceEnum.valueOf(l);
                            switch (source.name()) {
                                case "CLIENT_SCREEN":
                                    s.setScreenMonitorStatusSource(ExamRecordCacheUtil.getMonitorStatus(s.getExamRecordId(), source));
                                    break;
                                case "CLIENT_CAMERA":
                                    s.setCameraMonitorStatusSource(ExamRecordCacheUtil.getMonitorStatus(s.getExamRecordId(), source));
                                    break;
                                case "MOBILE_FIRST":
                                    s.setMobileFirstMonitorStatusSource(ExamRecordCacheUtil.getMonitorStatus(s.getExamRecordId(), source));
                                    break;
                                default:
                                    s.setMobileSecondMonitorStatusSource(ExamRecordCacheUtil.getMonitorStatus(s.getExamRecordId(), source));
                                    break;
                            }
                        });
                    }
                    SysConfig sysConfig = themisCacheService.addSysConfigCache(SystemConstant.MONITOR_CONFIG_PREFIX);
                    Optional.ofNullable(sysConfig).orElseThrow(() -> new BusinessException("未配置监控前缀"));

                    if (Objects.nonNull(monitorVideoSource)) {
                        s.setMonitorLiveUrl(SystemConstant.setStreamId(sysConfig.getConfigValue(), s.getExamRecordId(), MonitorVideoSourceEnum.valueOf(monitorVideoSource)));
                    } else if (Objects.nonNull(monitorVideoSourceList) && monitorVideoSourceList.contains(MonitorVideoSourceEnum.CLIENT_CAMERA.name())) {
                        s.setMonitorLiveUrl(SystemConstant.setStreamId(sysConfig.getConfigValue(), s.getExamRecordId(), MonitorVideoSourceEnum.CLIENT_CAMERA));
                    }
                    WebsocketStatusEnum websocketStatusEnum = ExamRecordCacheUtil.getClientWebsocketStatus(s.getExamRecordId());
                    if (Objects.nonNull(websocketStatusEnum)) {
                        s.setClientWebsocketStatus(websocketStatusEnum);
                    }
                    Integer paperDownLoad = ExamRecordCacheUtil.getPaperDownload(s.getExamRecordId());
                    if (Objects.nonNull(paperDownLoad)) {
                        s.setPaperDownload(paperDownLoad);
                    }
                    //剩余时间计算
                    if (Objects.nonNull(finalExamCacheBean)) {
                        s.setRemainTime(this.getRemainTime(finalExamCacheBean.getMode(), s.getExamRecordId()));
                    }
                });
            }
        }
        return ResultUtil.ok(invigilateListVideoBeanIPage);
    }

    @ApiOperation(value = "实时监控台视频随机列表接口")
    @RequestMapping(value = "/list/video/random", method = RequestMethod.POST)
    @ApiResponses({@ApiResponse(code = 200, message = "监考监控信息", response = InvigilateListVideoBean.class)})
//    @RedisLimitAnnotation(key = "listVideoRandom", period = 1, count = 1)
    public Result listVideoRandom(@ApiParam(value = "考试批次id", required = false) @RequestParam(required = false) Long examId, @ApiParam(value = "随机数", required = true) @RequestParam Integer randomNum) {
        if (Objects.isNull(randomNum) || Objects.equals(randomNum, "")) {
            throw new BusinessException("随机数不能为空");
        }
        TBUser tbUser = (TBUser) ServletUtil.getRequestAccount();
        AuthDto authDto = themisCacheService.addAccountAuthCache(tbUser.getId());
        //如果有监考员角色，只能查看自己所监考的考场，巡考员和管理员则可以查看全部考场
        Long userId = null;
        if (authDto.getRoleCodes().toString().contains(RoleEnum.INVIGILATE.name())) {
            userId = tbUser.getId();
        }
        List<InvigilateListVideoBean> invigilateListVideoBeanList = tOeExamRecordService.invigilatePageListVideoRandom(examId, userId, randomNum, tbUser.getOrgId());
        if (Objects.nonNull(invigilateListVideoBeanList) && invigilateListVideoBeanList.size() > 0) {
            invigilateListVideoBeanList.forEach(s -> {
                ExamCacheBean examCacheBean = teExamService.getExamCacheBean(s.getExamId());
                List<String> monitorVideoSourceList = null;
                if (Objects.nonNull(s.getMonitorVideoSource()) && !Objects.equals(s.getMonitorVideoSource().trim().replaceAll(" ", ""), "")) {
                    monitorVideoSourceList = Arrays.asList(s.getMonitorVideoSource().trim().toUpperCase().replaceAll(" ", "").split(","));
                }
                MonitorVideoSourceEnum mainMonitorVideoSource = SystemConstant.getMainMonitor(monitorVideoSourceList);
                SysConfig sysConfig = themisCacheService.addSysConfigCache(SystemConstant.MONITOR_CONFIG_PREFIX);
                Optional.ofNullable(sysConfig).orElseThrow(() -> new BusinessException("未配置监控前缀"));

                if (Objects.nonNull(mainMonitorVideoSource)) {
                    s.setMonitorLiveUrl(SystemConstant.setStreamId(sysConfig.getConfigValue(), s.getExamRecordId(), mainMonitorVideoSource));
                }
                WebsocketStatusEnum websocketStatusEnum = ExamRecordCacheUtil.getClientWebsocketStatus(s.getExamRecordId());
                if (Objects.nonNull(websocketStatusEnum)) {
                    s.setClientWebsocketStatus(websocketStatusEnum);
                }
                Integer paperDownLoad = ExamRecordCacheUtil.getPaperDownload(s.getExamRecordId());
                if (Objects.nonNull(paperDownLoad)) {
                    s.setPaperDownload(paperDownLoad);
                }
                //剩余时间计算
                if (Objects.nonNull(examCacheBean)) {
                    s.setRemainTime(this.getRemainTime(examCacheBean.getMode(), s.getExamRecordId()));
                }
            });
        }
        invigilateListVideoBeanList.sort(Comparator.comparing(InvigilateListVideoBean::getClientWebsocketStatus, Comparator.naturalOrder()).thenComparing(InvigilateListVideoBean::getMonitorLiveUrl, Comparator.nullsLast(Comparator.naturalOrder())));
        return ResultUtil.ok(invigilateListVideoBeanList);
    }

    @ApiOperation(value = "监考明细管理列表接口")
    @RequestMapping(value = "/history/list", method = RequestMethod.POST)
    @ApiResponses({@ApiResponse(code = 200, message = "监考明细信息", response = InvigilateListHistoryBean.class)})
    public Result historyList(@ApiParam(value = "考试批次id", required = false) @RequestParam(required = false) Long examId,
                              @ApiParam(value = "考试场次id", required = false) @RequestParam(required = false) Long examActivityId,
                              @ApiParam(value = "虚拟考场代码", required = false) @RequestParam(required = false) String roomCode,
                              @ApiParam(value = "科目代码", required = false) @RequestParam(required = false) String courseCode,
                              @ApiParam(value = "考生状态", required = false) @RequestParam(required = false) String status,
                              @ApiParam(value = "违纪状态", required = false) @RequestParam(required = false) Integer breachStatus,
                              @ApiParam(value = "交卷方式", required = false) @RequestParam(required = false) String finishType,
                              @ApiParam(value = "姓名", required = false) @RequestParam(required = false) String name,
                              @ApiParam(value = "证件号", required = false) @RequestParam(required = false) String identity,
                              @ApiParam(value = "陌生人脸min", required = false) @RequestParam(required = false) Integer minMultipleFaceCount,
                              @ApiParam(value = "陌生人脸max", required = false) @RequestParam(required = false) Integer maxMultipleFaceCount,
                              @ApiParam(value = "异常处理min", required = false) @RequestParam(required = false) Integer minExceptionCount,
                              @ApiParam(value = "异常处理max", required = false) @RequestParam(required = false) Integer maxExceptionCount,
                              @ApiParam(value = "预警量min", required = false) @RequestParam(required = false) Integer minWarningCount,
                              @ApiParam(value = "预警量max", required = false) @RequestParam(required = false) Integer maxWarningCount,
                              @ApiParam(value = "分页页码", required = true) @RequestParam @Min(SystemConstant.PAGE_NUMBER_MIN) int pageNumber,
                              @ApiParam(value = "分页数", required = true) @RequestParam @Min(SystemConstant.PAGE_SIZE_MIN) @Max(SystemConstant.PAGE_SIZE_MAX) int pageSize) {
        TBUser tbUser = (TBUser) ServletUtil.getRequestAccount();
        AuthDto authDto = themisCacheService.addAccountAuthCache(tbUser.getId());
        //如果有监考员角色，只能查看自己所监考的考场，巡考员和管理员则可以查看全部考场
        Long userId = null;
        if (authDto.getRoleCodes().toString().contains(RoleEnum.INVIGILATE.name())) {
            userId = tbUser.getId();
        }
        return ResultUtil.ok(tOeExamRecordService.invigilatePageListHistory(new Page<>(pageNumber, pageSize), examId, examActivityId, roomCode, courseCode, status, breachStatus, finishType, name, identity, minMultipleFaceCount, maxMultipleFaceCount, minExceptionCount, maxExceptionCount, minWarningCount, maxWarningCount, userId, tbUser.getOrgId()));
    }

    @ApiOperation(value = "监考明细管理列表导出接口")
    @RequestMapping(value = "/history/list/export", method = RequestMethod.POST)
    @ApiResponses({@ApiResponse(code = 200, message = "监考明细信息", response = InvigilateListHistoryBean.class)})
    public void historyListExport(@ApiParam(value = "考试批次id", required = false) @RequestParam(required = false) Long examId,
                                  @ApiParam(value = "考试场次id", required = false) @RequestParam(required = false) Long examActivityId,
                                  @ApiParam(value = "虚拟考场代码", required = false) @RequestParam(required = false) String roomCode,
                                  @ApiParam(value = "科目代码", required = false) @RequestParam(required = false) String courseCode,
                                  @ApiParam(value = "考生状态", required = false) @RequestParam(required = false) String status,
                                  @ApiParam(value = "违纪状态", required = false) @RequestParam(required = false) Integer breachStatus,
                                  @ApiParam(value = "交卷方式", required = false) @RequestParam(required = false) String finishType,
                                  @ApiParam(value = "姓名", required = false) @RequestParam(required = false) String name,
                                  @ApiParam(value = "证件号", required = false) @RequestParam(required = false) String identity,
                                  @ApiParam(value = "陌生人脸min", required = false) @RequestParam(required = false) Integer minMultipleFaceCount,
                                  @ApiParam(value = "陌生人脸max", required = false) @RequestParam(required = false) Integer maxMultipleFaceCount,
                                  @ApiParam(value = "异常处理min", required = false) @RequestParam(required = false) Integer minExceptionCount,
                                  @ApiParam(value = "异常处理max", required = false) @RequestParam(required = false) Integer maxExceptionCount,
                                  @ApiParam(value = "预警量min", required = false) @RequestParam(required = false) Integer minWarningCount,
                                  @ApiParam(value = "预警量max", required = false) @RequestParam(required = false) Integer maxWarningCount) throws Exception {
        TBUser tbUser = (TBUser) ServletUtil.getRequestAccount();
        AuthDto authDto = themisCacheService.addAccountAuthCache(tbUser.getId());
        //如果有监考员角色，只能查看自己所监考的考场，巡考员和管理员则可以查看全部考场
        Long userId = null;
        if (authDto.getRoleCodes().toString().contains(RoleEnum.INVIGILATE.name())) {
            userId = tbUser.getId();
        }
        List<InvigilateListHistoryBean> invigilateListHistoryBeanList = tOeExamRecordService.invigilatePageListHistoryExport(examId, examActivityId, roomCode, courseCode, status, breachStatus, finishType, name, identity, minMultipleFaceCount, maxMultipleFaceCount, minExceptionCount, maxExceptionCount, minWarningCount, maxWarningCount, userId, tbUser.getOrgId());
        ExportUtils.exportEXCEL("监考明细", InvigilateListHistoryBean.class, invigilateListHistoryBeanList, ServletUtil.getResponse());
    }

    @ApiOperation(value = "实时监控台列表明细接口")
    @RequestMapping(value = "/list/detail", method = RequestMethod.POST)
    @ApiResponses({@ApiResponse(code = 200, message = "监考监控信息", response = InvigilateListDetailBean.class)})
    public Result listDetail(@ApiParam(value = "考试记录id") @RequestParam Long examRecordId) {
        if (Objects.isNull(examRecordId) || Objects.equals(examRecordId, "")) {
            throw new BusinessException(ExceptionResultEnum.RECORD_ID_IS_NULL);
        }
        //获取考试记录缓存
        Long examId = ExamRecordCacheUtil.getExamId(examRecordId), examStudentId = null, examActivityId = null;
        TOeExamRecord tOeExamRecord = null;
        ExamRecordStatusEnum status = null;
        Integer breachStatus = null, warningMultipleFaceCount = null, exceptionCount = null, warningCount = null;
        if (Objects.isNull(examId)) {
            tOeExamRecord = tOeExamRecordService.getById(examRecordId);
            examId = tOeExamRecord.getExamId();
            examStudentId = tOeExamRecord.getExamStudentId();
            examActivityId = tOeExamRecord.getExamActivityId();
            status = tOeExamRecord.getStatus();
            breachStatus = Objects.isNull(tOeExamRecord.getBreachStatus()) ? 1 : tOeExamRecord.getBreachStatus();
            warningCount = Objects.nonNull(tOeExamRecord.getWarningCount()) ? tOeExamRecord.getWarningCount() : 0;
            warningMultipleFaceCount = Objects.nonNull(tOeExamRecord.getWarningMultipleFaceCount()) ? tOeExamRecord.getWarningMultipleFaceCount() : 0;
            exceptionCount = Objects.nonNull(tOeExamRecord.getExceptionCount()) ? tOeExamRecord.getExceptionCount() : 0;
        } else {
            examStudentId = ExamRecordCacheUtil.getExamStudentId(examRecordId);
            examActivityId = ExamRecordCacheUtil.getExamActivityId(examRecordId);
            status = ExamRecordCacheUtil.getStatus(examRecordId);
            breachStatus = Objects.isNull(ExamRecordCacheUtil.getBreachStatus(examRecordId)) ? 1 : ExamRecordCacheUtil.getBreachStatus(examRecordId);
            warningCount = Objects.isNull(ExamRecordCacheUtil.getWarningCount(examRecordId)) ? 0 : ExamRecordCacheUtil.getWarningCount(examRecordId);
            warningMultipleFaceCount = Objects.isNull(ExamRecordCacheUtil.getWarningMultipleFaceCount(examRecordId)) ? 0 : ExamRecordCacheUtil.getWarningMultipleFaceCount(examRecordId);
            exceptionCount = Objects.isNull(ExamRecordCacheUtil.getExceptionCount(examRecordId)) ? 0 : ExamRecordCacheUtil.getExceptionCount(examRecordId);
        }
        ExamStudentCacheBean examStudentCacheBean = teExamStudentService.getExamStudentCacheBean(examStudentId);
        TEStudentCacheDto teStudent = themisCacheService.addStudentAccountCache(examStudentCacheBean.getStudentId());
        String basePhotoPath = ossUtil.getAliYunOssPublicDomain().getPublicUrl() + File.separator + teStudent.getBasePhotoPath();
        String identity = examStudentCacheBean.getIdentity();
        String examStudentName = examStudentCacheBean.getName();
        String courseNameCode = examStudentCacheBean.getCourseName() + "(" + examStudentCacheBean.getCourseCode() + ")";
        String roomCode = examStudentCacheBean.getRoomCode();
        String roomName = examStudentCacheBean.getRoomName();
        ExamActivityCacheBean examActivityCacheBean = teExamActivityService.getExamActivityCacheBean(examActivityId);
        ExamCacheBean examCacheBean = teExamService.getExamCacheBean(examId);

        IPage<TBExamInvigilateUserDto> tbExamInvigilateUserDtoIPage = tbExamInvigilateUserService.examInvigilateUserQuery(new Page<>(SystemConstant.PAGE_NUMBER_MIN, SystemConstant.SELECT_PAGE_SIZE_MAX), examId, roomCode, null, teStudent.getOrgId());
        String invigilateUsers = null;
        if (Objects.nonNull(tbExamInvigilateUserDtoIPage) && !CollectionUtils.isEmpty(tbExamInvigilateUserDtoIPage.getRecords())) {
            invigilateUsers = tbExamInvigilateUserDtoIPage.getRecords().get(0).getName();
        }

        InvigilateListDetailBean invigilateListDetailBean = new InvigilateListDetailBean(examCacheBean.getName(),
                examActivityCacheBean.getCode(), examId, examActivityId, examStudentId, examRecordId, identity,
                examStudentName, courseNameCode, status, roomCode, roomName, breachStatus, basePhotoPath,
                examActivityCacheBean.getStartTime(), examActivityCacheBean.getFinishTime(), invigilateUsers, examStudentCacheBean.getClassNo());

        //考生轨迹
        QueryWrapper<TEExamStudentLog> teExamStudentLogQueryWrapper = new QueryWrapper<>();
        teExamStudentLogQueryWrapper.lambda().eq(TEExamStudentLog::getExamStudentId, examStudentId)
                .eq(TEExamStudentLog::getExamRecordId, examRecordId)
//                .ne(TEExamStudentLog::getType, SystemOperationEnum.BREACH_HANDLE.name())
//                .ne(TEExamStudentLog::getType, SystemOperationEnum.BREACH_REVOKE.name())
                .orderByAsc(TEExamStudentLog::getCreateTime);
        //                .and(w -> w.ne(TEExamStudentLog::getType, SystemOperationEnum.BREACH_HANDLE.name()).or().ne(TEExamStudentLog::getType, SystemOperationEnum.BREACH_REVOKE.name()));
        List<TEExamStudentLog> teExamStudentLogList = teExamStudentLogService.list(teExamStudentLogQueryWrapper);

        //加入文本和语音消息轨迹
        List<TIeExamInvigilateNotice> tIeExamInvigilateNoticeList = tIeExamInvigilateNoticeService.queryNoticeByExamRecordId(examRecordId);
        if (!CollectionUtils.isEmpty(tIeExamInvigilateNoticeList)) {
            for (TIeExamInvigilateNotice t : tIeExamInvigilateNoticeList) {
                teExamStudentLogList.add(new TEExamStudentLog(teStudent.getId(), examStudentId, t.getExamRecordId(), t.getType(), t.getContent(), t.getFormUserId(), t.getFormUserName(), t.getSendTime()));
            }
        }

        //加入多媒体消息轨迹
        List<TIeExamMediaLog> tIeExamMediaLogList = tIeExamMediaLogService.queryMediaByExamRecordId(examRecordId);
        if (!CollectionUtils.isEmpty(tIeExamMediaLogList)) {
            for (TIeExamMediaLog t : tIeExamMediaLogList) {
                teExamStudentLogList.add(new TEExamStudentLog(teStudent.getId(), examStudentId, t.getExamRecordId(), t.getType(), t.getContent(), t.getCreateTime()));
            }
        }
        Collections.sort(teExamStudentLogList);
        invigilateListDetailBean.setExamStudentLogList(teExamStudentLogList);
        //预警、异常、人脸
        invigilateListDetailBean.setWarningCount(warningCount);
        invigilateListDetailBean.setMultipleFaceCount(warningMultipleFaceCount);
        invigilateListDetailBean.setExceptionCount(exceptionCount);

        //2020/11/24新增管理员不清除预警状态
        TBUser tbUser = (TBUser) ServletUtil.getRequestAccount();
        AuthDto authDto = themisCacheService.addAccountAuthCache(tbUser.getId());
        if (!authDto.getRoleCodes().toString().contains(RoleEnum.ADMIN.name())) {
            //清除预警未阅状态
            UpdateWrapper<TIeInvigilateWarnInfo> tIeExamInvigilateNoticeUpdateWrapper = new UpdateWrapper<>();
            tIeExamInvigilateNoticeUpdateWrapper.lambda().set(TIeInvigilateWarnInfo::getApproveStatus, 1)
                    .eq(TIeInvigilateWarnInfo::getExamRecordId, examRecordId);
            tIeInvigilateWarnInfoService.update(tIeExamInvigilateNoticeUpdateWrapper);
            tOeExamRecordService.updateWarningUnreadByDb(examRecordId, 0);
            tOeExamRecordService.updateWarningUnreadCache(examRecordId, 0, false);

            teExamSummaryService.updateExamSummaryWarningUnread(examId, examActivityId, examStudentCacheBean.getRoomCode());
            themisCacheService.updateExamSummaryCache(examId);
            themisCacheService.updateExamSummaryCache(examId, examActivityId);
            themisCacheService.updateExamSummaryCache(examId, examActivityId, examStudentCacheBean.getRoomCode());
        }
        invigilateListDetailBean.setMonitorVideoSource(examCacheBean.getMonitorVideoSource());
        invigilateListDetailBean.setMonitorRecord(examCacheBean.getMonitorRecord());
        invigilateListDetailBean.setExamStudentCallEnable(examCacheBean.getExamStudentCallEnable());
        return ResultUtil.ok(invigilateListDetailBean);
    }

    @ApiOperation(value = "强制/手动交卷接口")
    @RequestMapping(value = "/finish", method = RequestMethod.POST)
    @ApiResponses({@ApiResponse(code = 200, message = "{\"success\":true}", response = Result.class)})
    public Result finish(@ApiJsonObject(name = "invigilateFinish", value = {
            @ApiJsonProperty(key = "examRecordId", type = "long", example = "1", description = "考试记录id"),
            @ApiJsonProperty(key = "type", description = "收卷类型，手动/强制")
    }) @ApiParam(value = "考试记录信息", required = true) @RequestBody Map<String, Object> mapParameter) {
        if (Objects.isNull(mapParameter.get(SystemConstant.EXAM_RECORD_ID)) || Objects.equals(mapParameter.get(SystemConstant.EXAM_RECORD_ID), "")) {
            throw new BusinessException(ExceptionResultEnum.RECORD_ID_IS_NULL);
        }
        List<String> recordIdList = (List<String>) mapParameter.get(SystemConstant.EXAM_RECORD_ID);
        if (Objects.isNull(mapParameter.get(SystemConstant.TYPE)) || Objects.equals(mapParameter.get(SystemConstant.TYPE), "")) {
            throw new BusinessException(ExceptionResultEnum.FINISH_TYPE_IS_NULL);
        }
        if (recordIdList.size() > 0) {
            FinishTypeEnum type = FinishTypeEnum.valueOf(String.valueOf(mapParameter.get(SystemConstant.TYPE)));
            TBUser tbUser = (TBUser) ServletUtil.getRequestAccount();
            //发送mq给客户端强制收卷start
            MqTagEnum mqTagEnum = null;
            if (Objects.equals(type, FinishTypeEnum.INTERRUPT)) {
                mqTagEnum = MqTagEnum.OE_MONITOR_FINISH;
                recordIdList.forEach(s -> {
                    Integer breachStatus = Objects.nonNull(ExamRecordCacheUtil.getBreachStatus(Long.parseLong(s))) ? ExamRecordCacheUtil.getBreachStatus(Long.parseLong(s)) : 1;
                    if (Objects.nonNull(breachStatus) && breachStatus.intValue() == 1) {
                        throw new BusinessException("只有标记为违纪的考生才能强制交卷");
                    }
                });
            } else if (Objects.equals(type, FinishTypeEnum.BREACH)) {
                mqTagEnum = MqTagEnum.OE_WARNING_FINISH;
            }
//            else {
//                mqTagEnum = MqTagEnum.OE_HARD_FINISH;
//            }
            if (Objects.nonNull(mqTagEnum)) {
                MqDto mqDto = new MqDto(mqUtil.getTopic(), mqTagEnum.name(), JacksonUtil.parseJson(recordIdList), mqTagEnum, String.valueOf(tbUser.getId()), mapParameter, tbUser.getName());
                mqDtoService.assembleSendAsyncOrderMsg(mqDto);
            }
            //发送mq给客户端强制收卷end
        }
        return ResultUtil.ok(true);
    }

    @ApiOperation(value = "在线巡考列表接口")
    @RequestMapping(value = "/patrol/list", method = RequestMethod.POST)
    @ApiResponses({@ApiResponse(code = 200, message = "监考监控信息", response = InvigilateListPatrolBean.class)})
    public Result patrolList(@ApiParam(value = "考试批次id", required = false) @RequestParam(required = false) Long examId,
                             @ApiParam(value = "考试场次id", required = false) @RequestParam(required = false) Long examActivityId,
                             @ApiParam(value = "虚拟考场代码", required = false) @RequestParam(required = false) String roomCode,
                             @ApiParam(value = "考生状态", required = false) @RequestParam(required = false) String status,
                             @ApiParam(value = "姓名", required = false) @RequestParam(required = false) String name,
                             @ApiParam(value = "证件号", required = false) @RequestParam(required = false) String identity,
                             @ApiParam(value = "陌生人脸min", required = false) @RequestParam(required = false) Integer minMultipleFaceCount,
                             @ApiParam(value = "陌生人脸max", required = false) @RequestParam(required = false) Integer maxMultipleFaceCount,
                             @ApiParam(value = "异常处理min", required = false) @RequestParam(required = false) Integer minExceptionCount,
                             @ApiParam(value = "异常处理max", required = false) @RequestParam(required = false) Integer maxExceptionCount,
                             @ApiParam(value = "预警量min", required = false) @RequestParam(required = false) Integer minWarningCount,
                             @ApiParam(value = "预警量max", required = false) @RequestParam(required = false) Integer maxWarningCount,
                             @ApiParam(value = "客户端网络通信状态", required = false) @RequestParam(required = false) String clientWebsocketStatus,
                             @ApiParam(value = "分页页码", required = true) @RequestParam @Min(SystemConstant.PAGE_NUMBER_MIN) int pageNumber,
                             @ApiParam(value = "分页数", required = true) @RequestParam @Min(SystemConstant.PAGE_SIZE_MIN) @Max(SystemConstant.PAGE_SIZE_MAX) int pageSize) {
        TBUser tbUser = (TBUser) ServletUtil.getRequestAccount();
        AuthDto authDto = themisCacheService.addAccountAuthCache(tbUser.getId());
        //如果有监考员角色，只能查看自己所监考的考场，巡考员和管理员则可以查看全部考场
        Long userId = null;
        if (authDto.getRoleCodes().toString().contains(RoleEnum.INVIGILATE.name())) {
            userId = tbUser.getId();
        }
        IPage<InvigilateListPatrolBean> invigilateListPatrolBeanIPage = tOeExamRecordService.invigilatePagePatrolList(new Page<>(pageNumber, pageSize), examId, examActivityId, roomCode, status, name, identity, minMultipleFaceCount, maxMultipleFaceCount, minExceptionCount, maxExceptionCount, minWarningCount, maxWarningCount, clientWebsocketStatus, userId, tbUser.getOrgId());
        if (Objects.nonNull(invigilateListPatrolBeanIPage)) {
            List<InvigilateListPatrolBean> invigilateListPatrolBeanList = invigilateListPatrolBeanIPage.getRecords();
            if (Objects.nonNull(invigilateListPatrolBeanList) && invigilateListPatrolBeanList.size() > 0) {
                ExamCacheBean examCacheBean = null;
                if (Objects.nonNull(examId)) {
                    examCacheBean = teExamService.getExamCacheBean(examId);
                }
                ExamCacheBean finalExamCacheBean = examCacheBean;
                invigilateListPatrolBeanList.forEach(s -> {
                    WebsocketStatusEnum websocketStatusEnum = ExamRecordCacheUtil.getClientWebsocketStatus(s.getExamRecordId());
                    if (Objects.nonNull(websocketStatusEnum)) {
                        s.setClientWebsocketStatus(websocketStatusEnum);
                    }
                    //剩余时间计算
                    if (Objects.nonNull(finalExamCacheBean)) {
                        s.setRemainTime(this.getRemainTime(finalExamCacheBean.getMode(), s.getExamRecordId()));
                    }
                });
            }
        }
        return ResultUtil.ok(invigilateListPatrolBeanIPage);
    }

    @ApiOperation(value = "进度查询列表接口")
    @RequestMapping(value = "/progress/list", method = RequestMethod.POST)
    @ApiResponses({@ApiResponse(code = 200, message = "监考进度信息", response = InvigilateListProgressBean.class)})
    public Result progressList(@ApiParam(value = "考试批次id", required = false) @RequestParam(required = false) Long examId,
                               @ApiParam(value = "考试场次id", required = false) @RequestParam(required = false) Long examActivityId,
                               @ApiParam(value = "虚拟考场代码", required = false) @RequestParam(required = false) String roomCode,
                               @ApiParam(value = "科目代码", required = false) @RequestParam(required = false) String courseCode,
                               @ApiParam(value = "姓名", required = false) @RequestParam(required = false) String name,
                               @ApiParam(value = "证件号", required = false) @RequestParam(required = false) String identity,
                               @ApiParam(value = "状态", required = false) @RequestParam(required = false) Integer status,
                               @ApiParam(value = "违纪状态，0：违纪，1：正常", required = false) @RequestParam(required = false) Integer breachStatus,
                               @ApiParam(value = "分页页码", required = true) @RequestParam @Min(SystemConstant.PAGE_NUMBER_MIN) int pageNumber,
                               @ApiParam(value = "分页数", required = true) @RequestParam @Min(SystemConstant.PAGE_SIZE_MIN) @Max(SystemConstant.PAGE_SIZE_MAX) int pageSize) {
        TBUser tbUser = (TBUser) ServletUtil.getRequestAccount();
        AuthDto authDto = themisCacheService.addAccountAuthCache(tbUser.getId());
        //如果有监考员角色，只能查看自己所监考的考场，巡考员和管理员则可以查看全部考场
        Long userId = null;
        if (authDto.getRoleCodes().toString().contains(RoleEnum.INVIGILATE.name())) {
            userId = tbUser.getId();
        }
        return ResultUtil.ok(tOeExamRecordService.invigilatePageProgressList(new Page<>(pageNumber, pageSize), examId, examActivityId, roomCode, courseCode, name, identity, status, userId, tbUser.getOrgId(), breachStatus));
    }

    @ApiOperation(value = "进度查询列表导出接口")
    @RequestMapping(value = "/progress/list/export", method = RequestMethod.POST)
    @ApiResponses({@ApiResponse(code = 200, message = "监考进度信息", response = InvigilateListProgressBean.class)})
    public void progressListExport(@ApiParam(value = "考试批次id", required = false) @RequestParam(required = false) Long examId,
                                   @ApiParam(value = "考试场次id", required = false) @RequestParam(required = false) Long examActivityId,
                                   @ApiParam(value = "虚拟考场代码", required = false) @RequestParam(required = false) String roomCode,
                                   @ApiParam(value = "科目代码", required = false) @RequestParam(required = false) String courseCode,
                                   @ApiParam(value = "姓名", required = false) @RequestParam(required = false) String name,
                                   @ApiParam(value = "证件号", required = false) @RequestParam(required = false) String identity,
                                   @ApiParam(value = "状态", required = false) @RequestParam(required = false) Integer status,
                                   @ApiParam(value = "违纪状态，0：违纪，1：正常", required = false) @RequestParam(required = false) Integer breachStatus) throws Exception {
        TBUser tbUser = (TBUser) ServletUtil.getRequestAccount();
        AuthDto authDto = themisCacheService.addAccountAuthCache(tbUser.getId());
        //如果有监考员角色，只能查看自己所监考的考场，巡考员和管理员则可以查看全部考场
        Long userId = null;
        if (authDto.getRoleCodes().toString().contains(RoleEnum.INVIGILATE.name())) {
            userId = tbUser.getId();
        }
        List<InvigilateListProgressExcelBean> invigilateListProgressExcelBeanList = tOeExamRecordService.invigilatePageProgressListExport(examId, examActivityId, roomCode, courseCode, name, identity, status, userId, tbUser.getOrgId(), breachStatus);
        if (!CollectionUtils.isEmpty(invigilateListProgressExcelBeanList)) {
            for (InvigilateListProgressExcelBean i : invigilateListProgressExcelBeanList) {
                i.setCourseName(i.getCourseName() + "(" + i.getCourseCode() + ")");
                i.setRoomName(i.getRoomName() + "(" + i.getRoomCode() + ")");
            }
        }
        ExportUtils.exportEXCEL("进度信息", InvigilateListProgressExcelBean.class, invigilateListProgressExcelBeanList, ServletUtil.getResponse());
    }

    @ApiOperation(value = "违纪处理接口")
    @RequestMapping(value = "/breach", method = RequestMethod.POST)
    @ApiResponses({@ApiResponse(code = 200, message = "{\"success\":true}", response = Result.class)})
    public Result breach(@ApiJsonObject(name = "invigilateBreach", value = {
            @ApiJsonProperty(key = "examRecordId", type = "long", example = "1", description = "考试记录id", required = true),
            @ApiJsonProperty(key = "type", description = "违规类型", required = true),
            @ApiJsonProperty(key = "description", description = "描述", required = true),
            @ApiJsonProperty(key = "status", type = "int", example = "1", description = "新建/撤销", required = true),
            @ApiJsonProperty(key = "breachLogId", type = "long", example = "1", description = "违纪日志id", required = true)
    }) @ApiParam(value = "考试记录信息", required = true) @RequestBody Map<String, Object> mapParameter) {
        if (Objects.isNull(mapParameter.get(SystemConstant.EXAM_RECORD_ID)) || Objects.equals(mapParameter.get(SystemConstant.EXAM_RECORD_ID), "")) {
            throw new BusinessException(ExceptionResultEnum.RECORD_ID_IS_NULL);
        }
        if (Objects.isNull(mapParameter.get(SystemConstant.TYPE)) || Objects.equals(mapParameter.get(SystemConstant.TYPE), "")) {
            throw new BusinessException(ExceptionResultEnum.BREACH_TYPE_IS_NULL);
        }
        if (Objects.isNull(mapParameter.get("description")) || Objects.equals(mapParameter.get("description"), "")) {
            throw new BusinessException(ExceptionResultEnum.BREACH_DESC_IS_NULL);
        }
        if (Objects.isNull(mapParameter.get(SystemConstant.STATUS)) || Objects.equals(mapParameter.get(SystemConstant.STATUS), "")) {
            throw new BusinessException(ExceptionResultEnum.BREACH_STATUS_IS_NULL);
        }
        teExamBreachLogService.breachLogic(mapParameter);
        teExamBreachLogService.updateBreachStatus(mapParameter);
        return ResultUtil.ok(true);
    }

    @ApiOperation(value = "监考消息通知接口")
    @RequestMapping(value = "/notice", method = RequestMethod.POST)
    @ApiResponses({@ApiResponse(code = 200, message = "{\"success\":true}", response = Result.class)})
    public Result notice(@ApiJsonObject(name = "invigilateNotice", value = {
            @ApiJsonProperty(key = "examRecordId", type = "long", example = "1", description = "考试记录id", required = true),
            @ApiJsonProperty(key = "type", description = "消息类型，text/audio", required = true),
            @ApiJsonProperty(key = "content", description = "消息内容", required = true)
    }) @ApiParam(value = "考试记录信息", required = true) @RequestBody Map<String, Object> mapParameter) {
        if (Objects.isNull(mapParameter.get(SystemConstant.EXAM_RECORD_ID)) || Objects.equals(mapParameter.get(SystemConstant.EXAM_RECORD_ID), "")) {
            throw new BusinessException(ExceptionResultEnum.RECORD_ID_IS_NULL);
        }
        Long recordId = Long.parseLong(String.valueOf(mapParameter.get(SystemConstant.EXAM_RECORD_ID)));
        if (Objects.isNull(mapParameter.get(SystemConstant.TYPE)) || Objects.equals(mapParameter.get(SystemConstant.TYPE), "")) {
            throw new BusinessException(ExceptionResultEnum.MESSAGE_TYPE_IS_NULL);
        }
        TBUser tbUser = (TBUser) ServletUtil.getRequestAccount();
        //发送mq给客户端监考消息start
        mapParameter.put(SystemConstant.FORM_USER_ID, tbUser.getId());
        MqDto mqDto = new MqDto(mqUtil.getTopic(), MqTagEnum.OE_IM_CLUSTERING.name(), recordId, MqTagEnum.OE_IM_CLUSTERING, String.valueOf(recordId), mapParameter, tbUser.getName());
        mqDtoService.assembleSendAsyncOrderMsg(mqDto);
        //发送mq给客户端监考消息end

//        //发送mq给客户端监考强制活体验证start
//        mqDto = new MqDto(MqTopicEnum.THEMIS_TOPIC.getCode(), MqTagEnum.OE_LIVENESS_VERIFY.name(), recordId, MqTagEnum.OE_LIVENESS_VERIFY, String.valueOf(recordId), mapParameter, tbUser.getName());
//        mqDtoService.assembleSendOneOrderMsg(mqDto);
//        //发送mq给客户端监考强制活体验证end
        return ResultUtil.ok(true);
    }

    @ApiOperation(value = "结束监考接口")
    @RequestMapping(value = "/exam/finish", method = RequestMethod.POST)
    @ApiResponses({@ApiResponse(code = 200, message = "{\"success\":true}", response = Result.class)})
    public Result examFinish(@ApiParam(value = "考试id", required = true) @RequestParam Long examId,
                             @ApiParam(value = "监考状态", required = true) @RequestParam InvigilateMonitorStatusEnum status) {
        Optional.ofNullable(examId).orElseThrow(() -> new BusinessException(ExceptionResultEnum.EXAM_ID_IS_NULL));
        Optional.ofNullable(status).orElseThrow(() -> new BusinessException("监考状态不能为空"));

        ExamCacheBean examCacheBean = teExamService.getExamCacheBean(examId);
        if (status == InvigilateMonitorStatusEnum.FINISHED && System.currentTimeMillis() < examCacheBean.getEndTime()) {
            throw new BusinessException("未到考试批次结束时间");
        }
        teExamService.updateInvigilateMonitorStatus(status, examId);
        teExamService.deleteExamCacheBean(examId);
        return ResultUtil.ok(true);
    }

    /**
     * 获取剩余时间
     *
     * @param mode
     * @param recordId
     * @return
     */
    private String getRemainTime(ExamModeEnum mode, Long recordId) {
        String remainTime = null;
        Integer forceFinish = ExamRecordCacheUtil.getForceFinish(recordId);
        Integer maxDurationSeconds = Objects.isNull(ExamRecordCacheUtil.getMaxDurationSeconds(recordId)) ? 0 : ExamRecordCacheUtil.getMaxDurationSeconds(recordId);
        Integer durationSeconds = Objects.isNull(ExamRecordCacheUtil.getDurationSeconds(recordId)) ? 0 : ExamRecordCacheUtil.getDurationSeconds(recordId);
        Long endTime = Objects.isNull(ExamRecordCacheUtil.getEndTime(recordId)) ? 0 : ExamRecordCacheUtil.getEndTime(recordId);
        if (Objects.nonNull(mode) && Objects.equals(mode, ExamModeEnum.ANYTIME)) {
            Long diffTime = (maxDurationSeconds.longValue() - durationSeconds.longValue()) * 1000;
            remainTime = diffTime.intValue() == 0 ? "00:00:00" : SystemConstant.getRemainTime(diffTime);
        } else if (Objects.nonNull(mode) && Objects.equals(mode, ExamModeEnum.TOGETHER) && Objects.nonNull(forceFinish) && forceFinish.intValue() == 1) {
            Long diffTime = endTime - System.currentTimeMillis();
            remainTime = diffTime.intValue() == 0 ? "00:00:00" : SystemConstant.getRemainTime(diffTime);
        } else {
            Long diffTime = (maxDurationSeconds.longValue() - durationSeconds.longValue()) * 1000;
            remainTime = diffTime.intValue() == 0 ? "00:00:00" : SystemConstant.getRemainTime(diffTime);
        }
        return remainTime;
    }
}