package com.qmth.distributed.print.business.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
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.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.qmth.boot.api.exception.ApiException;
import com.qmth.distributed.print.business.bean.dto.*;
import com.qmth.distributed.print.business.bean.dto.initMarkData.ExamDetailCourseInitMarkDto;
import com.qmth.distributed.print.business.bean.dto.initMarkData.InitExamDetailDto;
import com.qmth.distributed.print.business.bean.examRule.CodeNameEnableValue;
import com.qmth.distributed.print.business.bean.examRule.FieldsDto;
import com.qmth.distributed.print.business.bean.result.ExaminationDetailResult;
import com.qmth.distributed.print.business.bean.result.ExaminationResult;
import com.qmth.distributed.print.business.bean.result.SummarizedDataResult;
import com.qmth.distributed.print.business.entity.*;
import com.qmth.distributed.print.business.enums.*;
import com.qmth.distributed.print.business.mapper.ExamDetailMapper;
import com.qmth.distributed.print.business.service.*;
import com.qmth.distributed.print.business.templete.execute.AsyncExaminationImportTemplateService;
import com.qmth.teachcloud.common.bean.dto.DataPermissionRule;
import com.qmth.teachcloud.common.bean.vo.PaperInfoVo;
import com.qmth.teachcloud.common.contant.SystemConstant;
import com.qmth.teachcloud.common.entity.*;
import com.qmth.teachcloud.common.enums.*;
import com.qmth.teachcloud.common.service.*;
import com.qmth.teachcloud.common.util.*;
import com.qmth.teachcloud.mark.service.ScanPaperService;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ss.usermodel.FillPatternType;
import org.apache.poi.ss.usermodel.Font;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.IndexedColors;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.context.annotation.Lazy;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.jdbc.BadSqlGrammarException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * <p>
 * 考务数据 服务实现类
 * </p>
 *
 * @author xf
 * @since 2021-03-23
 */
@Service
public class ExamDetailServiceImpl extends ServiceImpl<ExamDetailMapper, ExamDetail> implements ExamDetailService {
    private final static Logger log = LoggerFactory.getLogger(ExamDetailServiceImpl.class);

    @Resource
    private ExamDetailCourseService examDetailCourseService;


    @Resource
    private BasicExamRuleService basicExamRuleService;

    @Resource
    private ExamDetailMapper examDetailMapper;

    @Resource
    private CommonCacheService commonCacheService;

    @Resource
    private ExamStudentService examStudentService;

    @Resource
    private ExamPrintPlanService examPrintPlanService;

    @Resource
    private BasicAttachmentService basicAttachmentService;

    @Resource
    private SysOrgService sysOrgService;

    @Resource
    private SysUserService sysUserService;

    @Resource
    TBTaskService tbTaskService;

    @Resource
    RedisCounterUtil redisCounterUtil;

    @Resource
    private ClientPrintDataService clientPrintDataService;

    @Resource
    ClientStatusService clientStatusService;

    @Resource
    TeachcloudCommonService teachcloudCommonService;

    @Resource
    ExamTaskService examTaskService;

    @Resource
    ExamTaskDetailService examTaskDetailService;

    @Resource
    RedisUtil redisUtil;

    @Resource
    BasicClazzService basicClazzService;

    @Resource
    TeachClazzService teachClazzService;

    @Resource
    BasicRoleDataPermissionService basicRoleDataPermissionService;

    @Resource
    BasicPrintConfigService basicPrintConfigService;
    @Resource
    private ScanPaperService scanPaperService;

    @Resource
    @Lazy
    private PrintCommonService printCommonService;

    @Resource
    private AsyncExaminationImportTemplateService asyncExaminationImportTemplateService;

    @Transactional(rollbackFor = Exception.class)
    @Override
    public double calculateTotalSubjects(Long printPlanId) {
        List<ExamDetail> examDetailList = this.list(new QueryWrapper<ExamDetail>().lambda().eq(ExamDetail::getPrintPlanId, printPlanId));
        List<Long> examDetailIds = examDetailList.stream().map(m -> m.getId()).collect(Collectors.toList());

        if (examDetailIds.isEmpty()) {
            return 0;
        }
        List<ExamDetailCourse> examDetailCourseList = examDetailCourseService.list(new QueryWrapper<ExamDetailCourse>().lambda().in(ExamDetailCourse::getExamDetailId, examDetailIds));
        return examDetailCourseList.isEmpty() ? 0 : examDetailCourseList.stream().map(m -> m.getCourseCode()).distinct().count();
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public double calculateTotalPackages(Long printPlanId) {
        List<ExamDetail> examDetailList = this.list(new QueryWrapper<ExamDetail>().lambda().eq(ExamDetail::getPrintPlanId, printPlanId));
        return examDetailList.size();
    }

    @Override
    public IPage<PrintTaskDto> listPrintTask(Long semesterId, Long examId, Long printPlanId, String status, String courseCode, String paperNumber, String examPlace, String examRoom, Long examStartTime, Long examEndTime, Long printStartTime, Long printEndTime, Long printHouseId, Integer pageNumber, Integer pageSize) {
        Long schoolId = Long.valueOf(ServletUtil.getRequestHeaderSchoolId().toString());
        SysUser requestUser = (SysUser) ServletUtil.getRequestUser();
        DataPermissionRule dpr = basicRoleDataPermissionService.findDataPermission(schoolId, requestUser.getId(), ServletUtil.getRequest().getServletPath());
        Page<PrintTaskDto> page = new Page<>(pageNumber, pageSize);
        return this.baseMapper.listPrintTask(page, schoolId, semesterId, examId, printPlanId, status, courseCode, paperNumber, examPlace, examRoom, examStartTime, examEndTime, printStartTime, printEndTime, printHouseId, dpr);
    }

    @Override
    public PrintTaskTotalDto taskTotalData(Long semesterId, Long examId, Long printPlanId, String status, String courseCode, String paperNumber, String examPlace, String examRoom, Long examStartTime, Long examEndTime, Long printStartTime, Long printEndTime, Long printHouseId) {
        Long schoolId = Long.valueOf(ServletUtil.getRequestHeaderSchoolId().toString());
        SysUser requestUser = (SysUser) ServletUtil.getRequestUser();
        DataPermissionRule dpr = basicRoleDataPermissionService.findDataPermission(schoolId, requestUser.getId(), ServletUtil.getRequest().getServletPath());
        PrintTaskTotalDto printTaskTotalDto = this.baseMapper.taskTotalData(schoolId, semesterId, examId, printPlanId, status, courseCode, paperNumber, examPlace, examRoom, examStartTime, examEndTime, printStartTime, printEndTime, printHouseId, dpr);

        if (printTaskTotalDto != null) {
            // 试卷总计
            Set<String> paperNumberTypeSet = new HashSet<>();
            List<ExamDetailCourse> examDetailCourseList = this.baseMapper.selectPaperCount(schoolId, semesterId, examId, printPlanId, status, courseCode, paperNumber, examPlace, examRoom, examStartTime, examEndTime, printStartTime, printEndTime, printHouseId, dpr);
            for (ExamDetailCourse examDetailCourse : examDetailCourseList) {
                for (String s : examDetailCourse.getPaperType().split(",")) {
                    paperNumberTypeSet.add(examDetailCourse.getPaperNumber().concat(s));
                }
            }
            printTaskTotalDto.setPaperCount(paperNumberTypeSet.size());
        }
        return printTaskTotalDto;
    }

    @Override
    public List<JSONObject> taskViewPDF(Long examDetailId) {
        ExamDetail examDetail = this.getById(examDetailId);
        List<JSONObject> list = new ArrayList<>();
        if (examDetail.getAttachmentId() != null) {
            JSONObject jsonObject = new JSONObject();
            jsonObject.put(SystemConstant.TYPE, PdfTypeEnum.PAPER);
            BasicAttachment attachment = basicAttachmentService.getById(examDetail.getAttachmentId());
            jsonObject.put("url", attachment == null ? null : teachcloudCommonService.filePreview(attachment.getPath()));
            list.add(jsonObject);
        }
        if (examDetail.getCardAttachmentId() != null) {
            JSONObject jsonObject = new JSONObject();
            jsonObject.put(SystemConstant.TYPE, PdfTypeEnum.CARD_A3);
            BasicAttachment attachment = basicAttachmentService.getById(examDetail.getCardAttachmentId());
            jsonObject.put("url", attachment == null ? null : teachcloudCommonService.filePreview(attachment.getPath()));
            list.add(jsonObject);
        }
        if (Objects.nonNull(examDetail.getAttachmentPath())) {
            JSONObject js = JSONObject.parseObject(examDetail.getAttachmentPath());
            JSONArray jsonArray = js.getJSONArray(SystemConstant.PATH);
            for (int i = 0; i < jsonArray.size(); i++) {
                JSONObject object = jsonArray.getJSONObject(i);
                JSONObject jsonObject = new JSONObject();
                jsonObject.put(SystemConstant.TYPE, Objects.nonNull(object.get("printType")) ? PdfTypeEnum.valueOf((String) object.get("printType")) : null);
                String uploadType = null;
                if (object.get(SystemConstant.UPLOAD_TYPE) instanceof JSONArray) {
                    JSONArray uploadTypeJsonArray = object.getJSONArray(SystemConstant.UPLOAD_TYPE);
                    uploadTypeFor:
                    for (int y = 0; y < uploadTypeJsonArray.size(); y++) {
                        uploadType = uploadTypeJsonArray.getString(y);
                        if (uploadType.equalsIgnoreCase("PDF")) {
                            break uploadTypeFor;
                        }
                    }
                } else {
                    uploadType = (String) object.get(SystemConstant.UPLOAD_TYPE);
                }
                jsonObject.put("url", Objects.nonNull(object.get(SystemConstant.PDF_PATH)) ? teachcloudCommonService.filePreviewByPathAndType((String) object.get(SystemConstant.PDF_PATH), uploadType, (String) object.get("type"), false) : null);
                list.add(jsonObject);
            }
        }
        return list;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public List<FieldsDto> findExaminationFields(Long schoolId) {
        BasicExamRule basicExamRule = basicExamRuleService.getBySchoolId(schoolId);
        if (basicExamRule == null) {
            throw ExceptionResultEnum.ERROR.exception("通用规则未设置");
        }
        String requiredFields = basicExamRule.getRequiredFields();
        String extendFields = basicExamRule.getExtendFields();
        // 基础字段
        List<FieldsDto> requiredFieldsList = JSONObject.parseArray(requiredFields, FieldsDto.class);
        requiredFieldsList = requiredFieldsList.stream().filter(m -> m.getEnable()).collect(Collectors.toList());
        for (FieldsDto fieldsDto : requiredFieldsList) {
            fieldsDto.setLevel("primary");
        }

        // 扩展字段
        List<FieldsDto> extendFieldsList = JSONObject.parseArray(extendFields, FieldsDto.class);
        List<String> extendCodeList = extendFieldsList.stream().map(FieldsDto::getCode).collect(Collectors.toList());
        List<FieldsDto> finalRequiredFieldsList = requiredFieldsList;
        if (extendCodeList.stream().anyMatch(e -> finalRequiredFieldsList.stream().map(FieldsDto::getCode).collect(Collectors.toList()).contains(e))) {
            throw ExceptionResultEnum.ERROR.exception("扩展字段中存在和基础字段相同的字段变量名");
        }
        // 有效的扩展字段
        List<FieldsDto> validExtendList = extendFieldsList.stream().filter(FieldsDto::getEnable).collect(Collectors.toList());
        for (FieldsDto fieldsDto : validExtendList) {
            fieldsDto.setLevel("secondary");
        }

        // 有效的考务字段
        List<FieldsDto> validExaminationFieldList = new ArrayList<>();
        validExaminationFieldList.addAll(requiredFieldsList);
        validExaminationFieldList.addAll(validExtendList);

        return validExaminationFieldList;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void downLoadExaminationTemplate(Long schoolId, HttpServletResponse response) throws IOException {
        final String DEFALUT_EXT = ".xlsx";
        final String DEFALUT_CONTENT_TYPE = "application/vnd.ms-excel";
        List<FieldsDto> fieldsNameList = this.findExaminationFields(schoolId);

        log.debug("导出Excel开始...");
        XSSFWorkbook wb = new XSSFWorkbook();
        String fileName = "考务数据模板";
        response.setHeader("Content-Disposition", "inline;filename=" + URLEncoder.encode(fileName, SystemConstant.CHARSET_NAME) + DEFALUT_EXT);
        response.setContentType(DEFALUT_CONTENT_TYPE);
        XSSFSheet sheet = wb.createSheet(commonCacheService.schoolCache(schoolId).getName() + "考务数据模板");

        Font defaultFont = wb.createFont();
        defaultFont.setFontHeightInPoints((short) 12);
        defaultFont.setFontName("宋体");


        int cellCount = fieldsNameList.size();
        // 说明
        XSSFCellStyle describeStyle = wb.createCellStyle();
        describeStyle.setAlignment(HorizontalAlignment.LEFT);
        Font describeFont = wb.createFont();
        describeFont.setFontHeightInPoints((short) 12);
        describeFont.setFontName("宋体");
        describeFont.setColor(IndexedColors.RED.getIndex());
        describeStyle.setFont(describeFont);
        String describe = SystemConstant.EXAMINATION_DESCRIBE;
        describe = describe.trim();
        XSSFRow rowDescribe = sheet.createRow(0);
        rowDescribe.setHeightInPoints(195); //行高设置成195px
        for (int i = 0; i < cellCount; i++) {
            XSSFCell cell = rowDescribe.createCell(i);
            if (i == 0) {
                cell.setCellValue(describe);
                cell.setCellStyle(describeStyle);
            }
        }
        CellRangeAddress region = new CellRangeAddress(0, 0, 0, cellCount - 1);
        sheet.addMergedRegion(region);

        // 表头行
        XSSFCellStyle headerStyle = wb.createCellStyle();
        headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
        headerStyle.setAlignment(HorizontalAlignment.CENTER);
        headerStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
        headerStyle.setFont(defaultFont);
        XSSFRow rowHead = sheet.createRow(1);
        for (int i = 0; i < fieldsNameList.size(); i++) {
            XSSFCell cell = rowHead.createCell(i);
            cell.setCellValue(fieldsNameList.get(i).getName());
            cell.setCellStyle(headerStyle);
        }

        // 样例行
        XSSFCellStyle exampleStyle = wb.createCellStyle();
        exampleStyle.setAlignment(HorizontalAlignment.LEFT);
        exampleStyle.setFont(defaultFont);
        XSSFRow rowExample = sheet.createRow(2);
        for (int i = 0; i < fieldsNameList.size(); i++) {
            XSSFCell cell = rowExample.createCell(i);
            FieldsDto fieldsDto = fieldsNameList.get(i);
            if ("examDate".equals(fieldsDto.getCode())) {
                cell.setCellValue("2023-07-09");
            } else if ("examTime".equals(fieldsDto.getCode())) {
                cell.setCellValue("18:30-20:30");
            } else {
                cell.setCellValue("测试" + fieldsNameList.get(i).getName());
            }
            cell.setCellStyle(exampleStyle);
        }

        for (int i = 0; i < cellCount; i++) {
            sheet.autoSizeColumn(i);
            sheet.setColumnWidth(i, sheet.getColumnWidth(i) * 17 / 10);
        }
        ServletOutputStream outputStream = response.getOutputStream();
        wb.write(outputStream);
        outputStream.flush();
        outputStream.close();
        log.debug("导出Excel结束...");
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public IPage<ExaminationResult> findExaminationBriefPage(Long schoolId, Long semesterId, Long examId, List<Long> printPlanIdList, String courseCode, String paperNumber, String examPlace, String examRoom, String packageCode, Long startDate, Long endDate, int pageNumber, int pageSize, DataPermissionRule dpr) {
        IPage<ExaminationResult> page = examDetailMapper.findBriefPage(new Page<>(pageNumber, pageSize), schoolId, semesterId, examId, printPlanIdList, courseCode, paperNumber, examPlace, examRoom, packageCode, startDate, endDate, dpr);
        List<ExaminationResult> list = page.getRecords();
        for (ExaminationResult examinationResult : list) {
            // 处理时间
            Long startTime = examinationResult.getExamStartTime();
            Long endTime = examinationResult.getExamEndTime();
            Map<String, Object> dateMap = ConvertUtil.analyzeDateAndTime(startTime, endTime);
            String date = String.valueOf(dateMap.get("date"));
            String time = String.valueOf(dateMap.get("time"));
            examinationResult.setExamDate(date);
            examinationResult.setExamTime(time);

            Set<String> stringSet = new HashSet<>();
            for (String id : examinationResult.getExamDetailCourseIds().split(",")) {
                ExamDetailCourse examDetailCourse = examDetailCourseService.getById(id);
                if (StringUtils.isNotBlank(examDetailCourse.getPaperType())) {
                    ExamTask examTask = examTaskService.getByExamIdAndCourseCodeAndPaperNumber(examinationResult.getExamId(), examDetailCourse.getCourseCode(), examDetailCourse.getPaperNumber());
                    ExamTaskDetail examTaskDetail = examTaskDetailService.getByExamTaskId(examTask.getId());
                    String paperType = examDetailCourse.getPaperType();
                    List<PaperInfoVo> paperInfoVoList = ExamTaskUtil.parsePaperAttachmentPath(examTaskDetail.getPaperAttachmentIds(), paperType);
                    for (PaperInfoVo paperInfoVo : paperInfoVoList) {
                        String cardTypeString = paperInfoVo.getCardType();
                        String createMethod = paperInfoVo.getCreateMethod();
                        if (cardTypeString.equals(CardTypeEnum.GENERIC.name()) && StringUtils.isNotBlank(createMethod) && createMethod.equals(CardCreateMethodEnum.UPLOAD.name())) {
                            stringSet.add(SyncCardTypeEnum.GENERIC.getDesc());
                        } else {
                            stringSet.add(SyncCardTypeEnum.CUSTOM.getDesc());
                        }
                    }
                }
            }
            if (stringSet.size() > 0) {
                examinationResult.setCardType(String.join(",", stringSet));
            } else {
                examinationResult.setCardType("-");
            }
        }
        return page;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public SummarizedDataResult findSummarizedData(Long schoolId, Long semesterId, Long examId, List<Long> printPlanIdList, String courseCode, String paperNumber, String examPlace, String examRoom, String packageCode, Long startDate, Long endDate, DataPermissionRule dpr) {
        SummarizedDataResult summarizedDataResult = this.baseMapper.findSummarizedData(schoolId, semesterId, examId, printPlanIdList, courseCode, paperNumber, examPlace, examRoom, packageCode, startDate, endDate, dpr);
        if (summarizedDataResult == null) {
            summarizedDataResult = new SummarizedDataResult();
            summarizedDataResult.setSchoolId(schoolId);
            summarizedDataResult.setTotalSubjects(0);
            summarizedDataResult.setPackageCount(0);
        }

        return summarizedDataResult;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public IPage<ExaminationDetailResult> findExaminationDetail(Long schoolId, Long semesterId, Long examId, List<Long> printPlanIdList, String courseCode, String paperNumber, String examPlace, String examRoom, String studentParams, Long startDate, Long endDate, int pageNumber, int pageSize) {
        DataPermissionRule dpr = basicRoleDataPermissionService.findDataPermission();
        return examDetailMapper.findDetailPage(new Page<>(pageNumber, pageSize), schoolId, semesterId, examId, printPlanIdList, courseCode, paperNumber, examPlace, examRoom, SystemConstant.translateSpecificSign(studentParams), startDate, endDate, dpr);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public IPage<ExaminationDetailResult> findExaminationDetail(Long examDetailId, String param, int pageNumber, int pageSize) {
        return examDetailMapper.findDetailPageById(new Page<>(pageNumber, pageSize), examDetailId, param);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public List<String> findExamPlaceDatasource(String param, List<Long> printPlanIdList) {
        Long schoolId = Long.valueOf(ServletUtil.getRequestHeaderSchoolId().toString());
        SysUser sysUser = (SysUser) ServletUtil.getRequestUser();
        SysOrg sysOrg = sysOrgService.getById(sysUser.getOrgId());
        Set<Long> orgIds = teachcloudCommonService.listSubOrgIds(null);
        QueryWrapper<ExamDetail> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda().eq(ExamDetail::getSchoolId, schoolId);
        if (sysOrg != null && !OrgTypeEnum.PRINTING_HOUSE.equals(sysOrg.getType())) {
            queryWrapper.lambda().in(ExamDetail::getOrgId, orgIds);
        }
        if (StringUtils.isNotBlank(param)) {
            queryWrapper.lambda().like(ExamDetail::getExamPlace, SystemConstant.translateSpecificSign(param));
        }
        if (printPlanIdList.size() > 0) {
            queryWrapper.lambda().in(ExamDetail::getPrintPlanId, printPlanIdList);
        }
        List<ExamDetail> examDetail = this.list(queryWrapper);
        return examDetail.stream().filter(m -> StringUtils.isNotBlank(m.getExamPlace())).map(ExamDetail::getExamPlace).distinct().collect(Collectors.toList());
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public List<String> findExamRoomDatasource(String param, List<Long> printPlanIdList) {
        Long schoolId = Long.valueOf(ServletUtil.getRequestHeaderSchoolId().toString());
        SysUser sysUser = (SysUser) ServletUtil.getRequestUser();
        SysOrg sysOrg = sysOrgService.getById(sysUser.getOrgId());
        Set<Long> orgIds = teachcloudCommonService.listSubOrgIds(null);
        QueryWrapper<ExamDetail> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda().eq(ExamDetail::getSchoolId, schoolId);
        if (sysOrg != null && !OrgTypeEnum.PRINTING_HOUSE.equals(sysOrg.getType())) {
            queryWrapper.lambda().in(ExamDetail::getOrgId, orgIds);
        }
        if (StringUtils.isNotBlank(param)) {
            queryWrapper.lambda().like(ExamDetail::getExamRoom, SystemConstant.translateSpecificSign(param));
        }
        if (printPlanIdList.size() > 0) {
            queryWrapper.lambda().in(ExamDetail::getPrintPlanId, printPlanIdList);
        }
        List<ExamDetail> examDetail = this.list(queryWrapper);
        return examDetail.stream().filter(m -> StringUtils.isNotBlank(m.getExamRoom())).map(ExamDetail::getExamRoom).distinct().collect(Collectors.toList());
    }

    @Transactional
    @Override
    public boolean taskSubmit(ExamDetail examDetail) {
        // 检查前置操作是否完成(是否生成pdf)
        ExamDetail detail = this.getById(examDetail.getId());
        if (detail == null) {
            throw ExceptionResultEnum.ERROR.exception("考场数据异常");
        }

        // 印刷计划必须为就绪才可提交印刷
        ExamPrintPlan examPrintPlan = examPrintPlanService.getById(detail.getPrintPlanId());
        if (!PrintPlanStatusEnum.READY.equals(examPrintPlan.getStatus()) && !PrintPlanStatusEnum.PRINTING.equals(examPrintPlan.getStatus())) {
            throw ExceptionResultEnum.ERROR.exception("印刷计划就绪、印刷中状态才可提交印刷");
        }

        // 只有就绪状态并且附件生成才可提交
        if (!ExamDetailStatusEnum.READY.equals(detail.getStatus())) {
            throw ExceptionResultEnum.ERROR.exception("考场就绪状态才可提交印刷");
        } else if (detail.getAttachmentId() == null && detail.getCardAttachmentId() == null) {
            throw ExceptionResultEnum.ERROR.exception("考场文件未生成");
        }

        // 修改印刷计划状态为印刷中
        UpdateWrapper<ExamPrintPlan> examPrintPlanUpdateWrapper = new UpdateWrapper<>();
        examPrintPlanUpdateWrapper.lambda().set(ExamPrintPlan::getStatus, PrintPlanStatusEnum.PRINTING).eq(ExamPrintPlan::getId, detail.getPrintPlanId());
        examPrintPlanService.update(examPrintPlanUpdateWrapper);

        // 修改考场状态为待印刷
        UpdateWrapper<ExamDetail> updateWrapper = new UpdateWrapper<>();
        updateWrapper.lambda().set(ExamDetail::getStatus, ExamDetailStatusEnum.WAITING).eq(ExamDetail::getId, examDetail.getId());

        return this.update(updateWrapper);
    }

    @Transactional
    @Override
    public boolean taskFinish(ExamDetail examDetail) {
        // 检查前置操作是否完成(是否生成pdf)
        ExamDetail detail = this.getById(examDetail.getId());
        if (detail == null) {
            throw ExceptionResultEnum.ERROR.exception("考场数据异常");
        }

        // 只有待印刷状态并且附件生成才可提交
        if (!ExamDetailStatusEnum.WAITING.equals(detail.getStatus())) {
            throw ExceptionResultEnum.ERROR.exception("考场待印刷状态才可完成");
        }

        // 修改考场状态为已完成
        UpdateWrapper<ExamDetail> updateWrapper = new UpdateWrapper<>();
        updateWrapper.lambda().set(ExamDetail::getStatus, ExamDetailStatusEnum.FINISH)
                .set(ExamDetail::getPrintStartTime, System.currentTimeMillis())
                .set(ExamDetail::getPrintEndTime, System.currentTimeMillis())
                .eq(ExamDetail::getId, examDetail.getId());
        this.update(updateWrapper);

        // 所有考场打印完成，更新印刷计划状态
        examPrintPlanService.updateFinishStatus(examDetail.getPrintPlanId());

        // 更新曝光卷型
        examTaskDetailService.updateExposePaperType(examDetail.getExamId(), examDetailCourseService.listByExamDetailId(examDetail.getId()));
        return true;
    }

    @Transactional
    @Override
    public boolean taskCancel(Long id) {
        ExamDetail detail = this.getById(id);
        if (!detail.getStatus().equals(ExamDetailStatusEnum.WAITING)) {
            throw ExceptionResultEnum.ERROR.exception("印刷状态为[" + ExamDetailStatusEnum.WAITING.getName() + "]的任务可撤回提交");
        }
        UpdateWrapper<ExamDetail> updateWrapper = new UpdateWrapper<>();
        updateWrapper.lambda().set(ExamDetail::getStatus, ExamDetailStatusEnum.READY).eq(ExamDetail::getId, id);
        this.update(updateWrapper);

        //所有考场都撤回，印刷任务状态改为就绪
        examPrintPlanService.updateStatusById(detail.getPrintPlanId(), PrintPlanStatusEnum.READY);

        // 当关联课程所有考场都撤回，清除提当前计划下打样记录
        QueryWrapper<ExamDetailCourse> examDetailCourseQueryWrapper = new QueryWrapper<>();
        examDetailCourseQueryWrapper.lambda().eq(ExamDetailCourse::getExamDetailId, detail.getId());
        List<ExamDetailCourse> examDetailCourses = examDetailCourseService.list(examDetailCourseQueryWrapper);
        for (ExamDetailCourse examDetailCours : examDetailCourses) {
            // 清空审核表附件ID
            UpdateWrapper<ExamTask> examTaskUpdateWrapper = new UpdateWrapper<>();
            examTaskUpdateWrapper.lambda().set(ExamTask::getApproveFormAttachmentId, null)
                    .eq(ExamTask::getSchoolId, examDetailCours.getSchoolId())
                    .eq(ExamTask::getCourseCode, examDetailCours.getCourseCode())
                    .eq(ExamTask::getPaperNumber, examDetailCours.getPaperNumber());
            examTaskService.update(examTaskUpdateWrapper);

            List<ExamDetail> examDetailList = this.listByPrintPlanIdAndCourseCodeAndPaperNumber(detail.getSchoolId(), detail.getPrintPlanId(), examDetailCours.getCourseCode(), examDetailCours.getPaperNumber());
            long count = examDetailList.stream().filter(m -> !m.getStatus().name().equals(ExamDetailStatusEnum.NEW.name()) && !m.getStatus().name().equals(ExamDetailStatusEnum.READY.name())).count();
            if (count == 0) {
                UpdateWrapper<ClientStatus> clientStatusUpdateWrapper = new UpdateWrapper<>();
                clientStatusUpdateWrapper.lambda().eq(ClientStatus::getSchoolId, detail.getSchoolId())
                        .eq(ClientStatus::getPrintPlanId, detail.getPrintPlanId())
                        .eq(ClientStatus::getCourseCode, examDetailCours.getCourseCode())
                        .eq(ClientStatus::getPaperNumber, examDetailCours.getPaperNumber());
                clientStatusService.remove(clientStatusUpdateWrapper);
            }
        }
        return true;
    }

    @Transactional
    @Override
    public boolean taskBatchCancel(Map data) {
        String type = String.valueOf(data.get("type"));
        String ids = String.valueOf(data.get("ids"));
        for (String id : ids.split(",")) {
            ExamDetail examDetail = this.getById(id);
            if ("submit".equals(type)) {
                taskSubmit(examDetail);
            } else if ("cancel".equals(type)) {
                taskCancel(Long.valueOf(id));
            } else if ("finish".equals(type)) {
                taskFinish(examDetail);
            }
        }
        return true;
    }

//    @Override
//    public boolean resubmitTask(ExamDetail examDetail) {
//        ExamDetail detail = this.getById(examDetail.getId());
//        if (!detail.getStatus().name().equals(ExamDetailStatusEnum.PRINTING.name())
//                && !detail.getStatus().name().equals(ExamDetailStatusEnum.FINISH.name())) {
//            throw ExceptionResultEnum.ERROR.exception("印刷状态为“印刷中”、“已完成”的任务可重新提交印刷");
//        }
//        TbTaskDetailResult tbTaskDetailResult = examDetailCourseService.getByExamDetailId(detail.getId());
//        // 调用生成pdf方法
//        Map<String, Object> map = tbTaskService.saveTask(tbTaskDetailResult, TaskTypeEnum.CREATE_PDF, detail.getPrintPlanId(), (SysUser) ServletUtil.getRequestUser(), detail.getId());
////        asyncCreatePdfTempleteService.createPdf(map, null);
//        MqDto mqDto = new MqDto(MqTagEnum.PDF.getCode(), map, String.valueOf(map.get(SystemConstant.TB_TASK_ID)));
//        redisUtil.sendMessage(mqDto.getTopic(), mqDto);
//        return true;
//    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public List<Long> disposeExamDataByExaminationExcel(ExamPrintPlan examPrintPlan, List<ExaminationImportDto> dataList, SysUser sysUser) {
        try {
            List<Map<String, Object>> examDetailKeyList = dataList.stream().flatMap(e -> {
                Long schoolIdId = e.getSchoolId();
                Map<String, Object> map = new HashMap<>();
                map.put("schoolId", schoolIdId);
                map.put("examPlace", e.getExamPlace());
                map.put("examRoom", e.getExamRoom());
                map.put("examStartTime", e.getExamStartTime());
                map.put("examEndTime", e.getExamEndTime());
                return Stream.of(map);
            }).distinct().collect(Collectors.toList());

            List<Long> examDetailIdList = new ArrayList<>();
            for (Map<String, Object> map : examDetailKeyList) {
                Long schoolId = Long.valueOf(String.valueOf(map.get("schoolId")));
                String examPlace = String.valueOf(map.get("examPlace"));
                String examRoom = String.valueOf(map.get("examRoom"));
                String examStartTime = String.valueOf(map.get("examStartTime"));
                String examEndTime = String.valueOf(map.get("examEndTime"));

                QueryWrapper<ExamDetail> queryWrapper = new QueryWrapper<>();
                queryWrapper.lambda().eq(ExamDetail::getSchoolId, schoolId)
                        .eq(ExamDetail::getPrintPlanId, examPrintPlan.getId())
                        .eq(ExamDetail::getExamPlace, examPlace)
                        .eq(ExamDetail::getExamRoom, examRoom)
                        .eq(ExamDetail::getExamStartTime, SystemConstant.convertIdToLong(examStartTime))
                        .eq(ExamDetail::getExamEndTime, SystemConstant.convertIdToLong(examEndTime));
                ExamDetail examDetailOld = this.getOne(queryWrapper);
                if (examDetailOld != null) {
                    this.deleteExaminationDataByExamDetailIds(Arrays.asList(examDetailOld.getId()));
                }

                List<ExaminationImportDto> examDetailStudentList = dataList.stream().filter(e -> e.getExamPlace().equals(examPlace) &&
                                e.getExamRoom().equals(examRoom) && e.getExamStartTime().equals(examStartTime) && e.getExamEndTime().equals(examEndTime))
                        .collect(Collectors.toList());

                ExamDetail examDetail = new ExamDetail();
                examDetail.setId(SystemConstant.getDbUuid());
                examDetail.setPackageCode("1" + redisCounterUtil.getCounter(schoolId, 6, "packageCode"));
                examDetail.setSchoolId(schoolId);
                examDetail.setOrgId(sysUser.getOrgId());
                examDetail.setPrintPlanId(examPrintPlan.getId());
                examDetail.setPrintPlanName(examPrintPlan.getName());
                examDetail.setExamId(examPrintPlan.getExamId());
                examDetail.setExamPlace(examPlace);
                examDetail.setExamRoom(examRoom);
                examDetail.setStatus(ExamDetailStatusEnum.NEW);
                examDetail.setTotalSubjects(examDetailStudentList.size());
                examDetail.setExamStartTime(SystemConstant.convertIdToLong(examStartTime));
                examDetail.setExamEndTime(SystemConstant.convertIdToLong(examEndTime));
                examDetail.setExamDataSource(ExamDataSourceEnum.FILE_IMPORT);
                examDetail.setNormal(true);
                examDetail.setCreateId(sysUser.getId());
                examDetail.setBackupCount(examPrintPlan.getBackupCount());
                this.save(examDetail);
                examDetailIdList.add(examDetail.getId());

                //组装exam_detail_course数据
                disposeExamDetailCourseData(examPrintPlan.getExamId(), examDetail.getId(), examDetailStudentList, sysUser);
            }
            return examDetailIdList;
        } catch (Exception e) {
            if (e instanceof DataIntegrityViolationException || e instanceof BadSqlGrammarException) {
                String content = JdbcErrorUtil.parseErrorMsg(e.getMessage().toString());
                throw ExceptionResultEnum.SQL_ERROR.exception(content);
            } else if (e instanceof ApiException) {
                ResultUtil.error((ApiException) e, e.getMessage());
            } else {
                ResultUtil.error(e.getMessage());
            }
        }
        return null;
    }

    /**
     * 生成考场科目数据
     *
     * @param examId                考试ID
     * @param examDetailId          考场ID
     * @param examDetailStudentList 考场下学生集合（excel中读取）
     * @param sysUser               用户
     */
    private void disposeExamDetailCourseData(Long examId, Long examDetailId, List<ExaminationImportDto> examDetailStudentList, SysUser sysUser) {
        List<Map<String, Object>> examDetailKeyList = examDetailStudentList.stream().flatMap(e -> {
            Map<String, Object> map = new HashMap<>();
            map.put("courseCode", e.getCourseCode());
            map.put("courseName", e.getCourseName());
            map.put("paperNumber", e.getPaperNumber());
            map.put("coursePaperId", e.getCoursePaperId());
            return Stream.of(map);
        }).distinct().collect(Collectors.toList());

        Long schoolId = sysUser.getSchoolId();

        for (Map<String, Object> map : examDetailKeyList) {
            String courseCode = String.valueOf(map.get("courseCode"));
            String courseName = String.valueOf(map.get("courseName"));
            String paperNumber = String.valueOf(map.get("paperNumber"));
            String coursePaperId = String.valueOf(map.get("coursePaperId"));

            // 获取该科目下的考生人数
            List<ExaminationImportDto> examDetailCourseStudentList = examDetailStudentList.stream().filter(e -> e.getCourseCode().equals(courseCode) &&
                    e.getCourseName().equals(courseName) && e.getPaperNumber().equals(paperNumber)).collect(Collectors.toList());

            ExamDetailCourse examDetailCourse = new ExamDetailCourse();
            examDetailCourse.setId(SystemConstant.getDbUuid());
            examDetailCourse.setSchoolId(schoolId);
            examDetailCourse.setExamDetailId(examDetailId);
            examDetailCourse.setCourseCode(courseCode);
            examDetailCourse.setCourseName(courseName);
            examDetailCourse.setPaperNumber(paperNumber);
            examDetailCourse.setCoursePaperId(coursePaperId);
            examDetailCourse.setTotalSubjects(examDetailCourseStudentList.size());
            examDetailCourse.setCreateId(sysUser.getId());
            String clazzName = examDetailCourseStudentList.stream().filter(m -> StringUtils.isNotBlank(m.getClazzName())).map(ExaminationImportDto::getClazzName).collect(Collectors.joining(","));
            if (StringUtils.isNotBlank(clazzName)) {
                examDetailCourse.setClazzName(clazzName);
            }
            examDetailCourseService.save(examDetailCourse);

            //组装exam_student数据
            disposeExamStudentData(examId, examDetailCourse.getId(), examDetailCourseStudentList, sysUser);
        }
    }

    /**
     * @param examId                      考试ID
     * @param examDetailCourseId          考场科目
     * @param examDetailCourseStudentList 考场科目下学生集合（excel读取）
     * @param sysUser                     用户
     */
    private void disposeExamStudentData(Long examId, Long examDetailCourseId, List<ExaminationImportDto> examDetailCourseStudentList, SysUser sysUser) {
        List<ExamStudent> examStudentList = new ArrayList<>();
        Long schoolId = sysUser.getSchoolId();
        for (ExaminationImportDto examinationImportDto : examDetailCourseStudentList) {
            List<FieldsDto> fieldsDtoList = examinationImportDto.getSecondaryFieldList();
            List<CodeNameEnableValue> extendFieldsDtoList = this.getExtendFieldsByFields(fieldsDtoList);

            ExamStudent examStudent = new ExamStudent();
            examStudent.setId(SystemConstant.getDbUuid());
            examStudent.setSchoolId(schoolId);
            examStudent.setOrgId(sysUser.getOrgId());
            examStudent.setExamId(examId);
            examStudent.setExamDetailCourseId(examDetailCourseId);
            examStudent.setPaperNumber(examinationImportDto.getPaperNumber());
            examStudent.setCoursePaperId(examinationImportDto.getCoursePaperId());
            examStudent.setStudentCode(examinationImportDto.getStudentCode());
            examStudent.setStudentName(examinationImportDto.getStudentName());
            examStudent.setRequiredFields(JSON.toJSONString(examinationImportDto.getRequiredFieldList()));
            examStudent.setExtendFields(JSON.toJSONString(extendFieldsDtoList));
            examStudent.setSiteNumber(examinationImportDto.getSiteNumber());
            examStudent.setCollegeName(examinationImportDto.getCollegeName());
            examStudent.setMajorName(examinationImportDto.getMajorName());
            examStudent.setClazzName(examinationImportDto.getClazzName());
            examStudent.setTeachClazzName(examinationImportDto.getTeachClazzName());
            examStudent.setTeacherName(examinationImportDto.getTeacherName());
            examStudent.setTeacherId(examinationImportDto.getTeacherId());
            examStudent.setCreateId(sysUser.getId());
            examStudentList.add(examStudent);
        }
        examStudentService.saveBatch(examStudentList);
    }

    @Override
    public List<ExamDetail> listByPrintPlanIdAndCourseCodeAndPaperNumber(Long schoolId, Long printPlanId, String courseCode, String paperNumber) {
        List<ExamDetailCourse> examDetailCourses = examDetailCourseService.listByPrintPlanIdAndCourseCodeAndPaperNumber(schoolId, printPlanId, courseCode, paperNumber);
        if (examDetailCourses != null && examDetailCourses.size() > 0) {
            Set<Long> examDetailIds = examDetailCourses.stream().map(ExamDetailCourse::getExamDetailId).collect(Collectors.toSet());
            return this.listByIds(examDetailIds);
        }
        return null;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public List<ExaminationExportDto> findExaminationExportDtoDatasource(Map<String, Object> map) {
        Long semesterId = null;
        if (map.get("semesterId") != null) {
            semesterId = Long.valueOf(String.valueOf(map.get("semesterId")));
        }
        Long examId = null;
        if (map.get("examId") != null) {
            examId = Long.valueOf(String.valueOf(map.get("examId")));
        }
        List<Long> printPlanIdList = null;
        if (map.get("printPlanIdList") != null) {
            printPlanIdList = JSON.parseArray(map.get("printPlanIdList").toString(), Long.class);
        }
        String courseCode = null;
        if (map.get("courseCode") != null) {
            courseCode = String.valueOf(map.get("courseCode"));
        }
        String paperNumber = null;
        if (map.get("paperNumber") != null) {
            paperNumber = String.valueOf(map.get("paperNumber"));
        }
        String examPlace = null;
        if (map.get("examPlace") != null) {
            examPlace = String.valueOf(map.get("examPlace"));
        }
        String examRoom = null;
        if (map.get("examRoom") != null) {
            examRoom = String.valueOf(map.get("examRoom"));
        }
        String packageCode = null;
        if (map.get("packageCode") != null) {
            packageCode = String.valueOf(map.get("packageCode"));
        }
        Long startDate = null;
        if (map.get("startDate") != null) {
            startDate = Long.valueOf(String.valueOf(map.get("startDate")));
        }
        Long endDate = null;
        if (map.get("endDate") != null) {
            endDate = Long.valueOf(String.valueOf(map.get("endDate")));
        }
        DataPermissionRule dpr = new DataPermissionRule();
        if (Objects.nonNull(map.get("dpr"))) {
            dpr = (DataPermissionRule) map.get("dpr");
        }
        TBTask tbTask = (TBTask) map.get(SystemConstant.TASK);
        Long schoolId = tbTask.getSchoolId();

        List<ExaminationResult> examinationResultList = this.findExaminationBriefPage(schoolId, semesterId, examId,
                printPlanIdList, courseCode, paperNumber, examPlace, examRoom, packageCode, startDate, endDate,
                SystemConstant.PAGE_NUMBER, SystemConstant.PAGE_SIZE, dpr).getRecords();
        int index = 0;
        List<ExaminationExportDto> examinationExportDtoList = new ArrayList<>();
        for (ExaminationResult examinationResult : examinationResultList) {
            index++;
            ExaminationExportDto examinationExportDto = new ExaminationExportDto();
            examinationExportDto.setIndex((long) index);
            examinationExportDto.setExamName(examinationResult.getExamName());
            examinationExportDto.setSemesterName(examinationResult.getSemesterName());
            examinationExportDto.setPrintPlanName(examinationResult.getPrintPlanName());
            examinationExportDto.setExamDate(examinationResult.getExamDate());
            examinationExportDto.setExamTime(examinationResult.getExamTime());
            examinationExportDto.setExamPlace(examinationResult.getExamPlace());
            examinationExportDto.setExamRoom(examinationResult.getExamRoom());
            examinationExportDto.setPackageCode(examinationResult.getPackageCode());
            examinationExportDto.setCourseNameCode(examinationResult.getCourseNameCode());
            examinationExportDto.setPaperNumber(examinationResult.getPaperNumber());
            examinationExportDto.setTotalSubjects(examinationResult.getTotalSubjects());
            examinationExportDto.setCardType(examinationResult.getCardType());
            examinationExportDtoList.add(examinationExportDto);
        }
        return examinationExportDtoList;
    }

    @Override
    public List<Map> listStudentByExamDetailCourseId(Long schoolId, Long examDetailCourseId, String studentCode, String type) {
        return this.baseMapper.listStudentByExamDetailCourseId(schoolId, examDetailCourseId, studentCode, type);
    }

    @Override
    public List<ExamDetail> listByCourseCodeAndPaperNumber(Long schoolId, Long examId, String courseCode, String paperNumber) {
        return this.baseMapper.listByExamIdAndCourseCodeAndPaperNumber(schoolId, examId, courseCode, paperNumber);
    }

    @Override
    public boolean taskNormal(ExamDetail examDetail) {
        // 已扫描过，不能作废
        if (!examDetail.getNormal()) {
            List<ExamDetailCourse> examDetailCourseList = examDetailCourseService.listByExamDetailId(examDetail.getId());
            for (ExamDetailCourse examDetailCourse : examDetailCourseList) {
                boolean isScanPaper = scanPaperService.isScanByExamIdAndPaperNumber(examDetail.getExamId(), examDetailCourse.getPaperNumber());
                if (isScanPaper) {
                    throw ExceptionResultEnum.ERROR.exception("有课程已开始扫描，无法作废");
                }
            }
        }
        ExamDetail detail = this.getById(examDetail.getId());
        if (ExamDetailStatusEnum.CANCEL.equals(detail.getStatus()) && examDetail.getNormal()) {
            throw ExceptionResultEnum.ERROR.exception("印刷任务关联的命题任务已作废，无法恢复");
        }
        UpdateWrapper<ExamDetail> updateWrapper = new UpdateWrapper<>();
        updateWrapper.lambda().set(ExamDetail::getNormal, examDetail.getNormal()).eq(ExamDetail::getId, examDetail.getId());
        return this.update(updateWrapper);
    }

    @Override
    public String findClazzNamesByClazzIds(String clazzIds, String separator) {
        if (!SystemConstant.strNotNull(separator)) {
            separator = ",";
        }
        if (StringUtils.isBlank(clazzIds)) {
            return null;
        }
        String[] strArr = clazzIds.split(separator);
        Set<Long> clazzIdSet = Arrays.stream(strArr).map(SystemConstant::convertIdToLong).collect(Collectors.toSet());
        List<String> clazzNameList = this.findClazzNamesByClazzIdSet(clazzIdSet);
        return String.join(separator, clazzNameList);
    }

    @Override
    public String findClazzNamesByClazzIds(List<Long> clazzIdList, String separator) {
        if (!SystemConstant.strNotNull(separator)) {
            separator = ",";
        }
        Set<Long> clazzIdSet = new HashSet<>(clazzIdList);
        List<String> clazzNameList = this.findClazzNamesByClazzIdSet(clazzIdSet);
        return String.join(separator, clazzNameList);
    }

    /**
     * 根据id下载examDetail
     *
     * @param ids
     * @return
     */
    @Override
    public List<ExamDetailPdfDownloadDto> findPdfDownload(List<Long> ids) {
        return examDetailMapper.findPdfDownload(ids);
    }

    @Transactional
    @Override
    public void dataDelete(Long id) {
        ExamDetail examDetail = this.getById(id);
        if (examDetail == null) {
            throw ExceptionResultEnum.ERROR.exception("考场数据不存在");
        }
        if (ExamDetailStatusEnum.WAITING.equals(examDetail.getStatus())) {
            throw ExceptionResultEnum.ERROR.exception("考场数据已提交印刷，请先撤回后再删除");
        } else if (ExamDetailStatusEnum.PRINTING.equals(examDetail.getStatus())) {
            throw ExceptionResultEnum.ERROR.exception("考场数据已提交印刷，正在打印中，无法删除");
        } else if (ExamDetailStatusEnum.FINISH.equals(examDetail.getStatus())) {
            throw ExceptionResultEnum.ERROR.exception("考场数据已打印完成，无法删除");
        }
        //删除exam_detail
        this.removeById(id);
        // 删除exam_detail_course
        QueryWrapper<ExamDetailCourse> examDetailCourseQueryWrapper = new QueryWrapper<>();
        examDetailCourseQueryWrapper.lambda().eq(ExamDetailCourse::getExamDetailId, id);
        List<ExamDetailCourse> examDetailCourses = examDetailCourseService.list(examDetailCourseQueryWrapper);
        if (!examDetailCourses.isEmpty()) {
            for (ExamDetailCourse examDetailCourse : examDetailCourses) {
                examDetailCourseService.removeById(examDetailCourse.getId());

                // 删除 exam_student
                QueryWrapper<ExamStudent> examStudentQueryWrapper = new QueryWrapper<>();
                examStudentQueryWrapper.lambda().eq(ExamStudent::getExamDetailCourseId, examDetailCourse.getId());
                List<ExamStudent> examStudents = examStudentService.list(examStudentQueryWrapper);
                if (!examStudents.isEmpty()) {
                    List<Long> examStudentIds = examStudents.stream().map(ExamStudent::getId).collect(Collectors.toList());
                    examStudentService.removeByIds(examStudentIds);
                }
            }
        }

        // 修改exam_print_plan状态
        QueryWrapper<ExamDetail> examDetailQueryWrapper = new QueryWrapper<>();
        examDetailQueryWrapper.lambda().eq(ExamDetail::getPrintPlanId, examDetail.getPrintPlanId());
        List<ExamDetail> examDetailList = this.list(examDetailQueryWrapper);
        PrintPlanStatusEnum printPlanStatusEnum = PrintPlanStatusEnum.READY;
        if (examDetailList.isEmpty()) {
            printPlanStatusEnum = PrintPlanStatusEnum.NEW;
        } else {
            // 打印中
            long printingCount = examDetailList.stream().filter(m -> m.getStatus().equals(ExamDetailStatusEnum.PRINTING)).count();
            // 已完成
            long finishCount = examDetailList.stream().filter(m -> !m.getStatus().equals(ExamDetailStatusEnum.FINISH)).count();
            if (printingCount > 0) {
                printPlanStatusEnum = PrintPlanStatusEnum.PRINTING;
            } else if (finishCount == 0) {
                printPlanStatusEnum = PrintPlanStatusEnum.FINISH;
            }

        }
        // 由于考场数据变动，要同步更新最新的计划状态
        UpdateWrapper<ExamPrintPlan> updateWrapper = new UpdateWrapper<>();
        updateWrapper.lambda().set(ExamPrintPlan::getStatus, printPlanStatusEnum).eq(ExamPrintPlan::getId, examDetail.getPrintPlanId());
        examPrintPlanService.update(updateWrapper);
    }

    @Override
    public List<ExamStudent> listStudentByExamDetailId(String examDetailId) {
        QueryWrapper<ExamDetailCourse> examDetailCourseQueryWrapper = new QueryWrapper<>();
        examDetailCourseQueryWrapper.lambda().eq(ExamDetailCourse::getExamDetailId, examDetailId);
        List<ExamDetailCourse> examDetailCourses = examDetailCourseService.list(examDetailCourseQueryWrapper);

        if (!CollectionUtils.isEmpty(examDetailCourses)) {
            List<Long> examDetailCourseIds = examDetailCourses.stream().map(ExamDetailCourse::getId).collect(Collectors.toList());

            QueryWrapper<ExamStudent> examStudentQueryWrapper = new QueryWrapper<>();
            examStudentQueryWrapper.lambda().in(ExamStudent::getExamDetailCourseId, examDetailCourseIds);
            return examStudentService.list(examStudentQueryWrapper);
        }
        return null;
    }

    @Override
    public List<ExamDetailCourseDto> listByExamIdAndCourseCode(Long schoolId, Long examId, String courseCode) {
        return this.baseMapper.listByExamIdAndCourseCode(schoolId, examId, courseCode);
    }

    @Override
    public void deleteByExamIdAndPaperNumber(Long schoolId, Long examId, String paperNumber) {
        List<ExamDetailCourse> examDetailCourseList = examDetailCourseService.listExamDetailByExamIdAndPaperNumber(schoolId, examId, paperNumber);
        if (!CollectionUtils.isEmpty(examDetailCourseList)) {
            for (ExamDetailCourse examDetailCourse : examDetailCourseList) {
                // 删除考场
                this.removeById(examDetailCourse.getExamDetailId());
                // 删除考场科目
                examDetailCourseService.removeById(examDetailCourse.getId());
                // 删除考生
                examStudentService.removeByExamDetailCourseId(examDetailCourse.getId());
            }
        }
    }

    @Override
    public TBTask importData(String printPlanId, MultipartFile file) {
        Long schoolId = Long.valueOf(ServletUtil.getRequestHeaderSchoolId().toString());
        try {
            Map<String, Object> map = printCommonService.saveTask(SystemConstant.convertIdToLong(printPlanId), file, TaskTypeEnum.EXAMINATION_IMPORT);
            boolean lock = redisUtil.lock(SystemConstant.REDIS_LOCK_IMPORT_EXAM_DATA + schoolId, SystemConstant.REDIS_LOCK_IMPORT_EXAM_DATA_TIME_OUT);
            if (lock) {
                asyncExaminationImportTemplateService.importTask(map);
            }
            TBTask tbTask = Objects.nonNull(map.get(SystemConstant.TASK)) ? (TBTask) map.get(SystemConstant.TASK) : null;
            return tbTask;
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            redisUtil.releaseLock(SystemConstant.REDIS_LOCK_IMPORT_EXAM_DATA + schoolId);
        }
    }

    @Transactional
    @Override
    public void deleteExaminationDataByExamDetailIds(List<Long> examDetailIdList) {
        if (CollectionUtils.isEmpty(examDetailIdList)) {
            return;
        }
        // 删除exam_detail 表数据
        this.remove(new QueryWrapper<ExamDetail>().lambda().in(ExamDetail::getId, examDetailIdList));

        // 准备删除的考务-科目表id
        List<Long> examDetailCourseIds = examDetailCourseService.list(new QueryWrapper<ExamDetailCourse>().lambda().in(ExamDetailCourse::getExamDetailId, examDetailIdList))
                .stream().map(ExamDetailCourse::getId).collect(Collectors.toList());
        if (CollectionUtils.isEmpty(examDetailCourseIds)) {
            return;
        }
        // 删除exam_detail_course 表数据
        examDetailCourseService.remove(new QueryWrapper<ExamDetailCourse>().lambda().in(ExamDetailCourse::getId, examDetailCourseIds));

        // 准备删除的考务-考生表id
        List<Long> examStudentIds = examStudentService.list(new QueryWrapper<ExamStudent>().lambda().in(ExamStudent::getExamDetailCourseId, examDetailCourseIds))
                .stream().map(ExamStudent::getId).collect(Collectors.toList());
        if (CollectionUtils.isEmpty(examStudentIds)) {
            return;
        }
        // 删除exam_student 表数据
        examStudentService.remove(new QueryWrapper<ExamStudent>().lambda().in(ExamStudent::getId, examStudentIds));
    }

    @Transactional
    @Override
    public void resetExamDetail(Long entityId, CreatePdfTypeEnum type, ExamDetailStatusEnum status, boolean clearPaperType) {
        // 清掉客户端产生的数据，包含缓存状态、校验状态
        ExamDetail examDetail = this.getById(entityId);
        if (CreatePdfTypeEnum.ALL.equals(type) || CreatePdfTypeEnum.PAPER.equals(type) || CreatePdfTypeEnum.CARD_A3.equals(type)) {
            QueryWrapper<ClientStatus> clientStatusQueryWrapper = new QueryWrapper<>();
            clientStatusQueryWrapper.lambda().eq(ClientStatus::getSchoolId, examDetail.getSchoolId())
                    .eq(ClientStatus::getExamDetailId, entityId);
            List<ClientStatus> clientStatusList = clientStatusService.list(clientStatusQueryWrapper);
            if (!CollectionUtils.isEmpty(clientStatusList)) {
                for (ClientStatus clientStatus : clientStatusList) {
                    clientStatus.setPass(false);
                    clientStatus.setDownload(false);
                }
                clientStatusService.updateBatchById(clientStatusList);
            }

            // 清掉客户端产生的数据和考场文件，包含状态、校验状态，pdf文件路径，打印信息
            UpdateWrapper<ExamDetail> examDetailUpdateWrapper = new UpdateWrapper<>();
            LambdaUpdateWrapper<ExamDetail> lambdaUpdateWrapper = examDetailUpdateWrapper.lambda();
            lambdaUpdateWrapper.set(ExamDetail::getStatus, status)
                    .set(ExamDetail::getValidate, false).set(ExamDetail::getPrintProgress, null)
                    .set(ExamDetail::getPrintStartTime, null)
                    .set(ExamDetail::getPrintEndTime, null);
            if (CreatePdfTypeEnum.ALL.equals(type) || CreatePdfTypeEnum.PAPER.equals(type)) {
                lambdaUpdateWrapper.set(ExamDetail::getAttachmentId, null);
            }
            if (CreatePdfTypeEnum.ALL.equals(type) || CreatePdfTypeEnum.CARD_A3.equals(type)) {
                lambdaUpdateWrapper.set(ExamDetail::getCardAttachmentId, null);
            }
            if (CreatePdfTypeEnum.ALL.equals(type)) {
                lambdaUpdateWrapper.set(ExamDetail::getAttachmentPath, null);
            }
            lambdaUpdateWrapper.eq(ExamDetail::getId, entityId);
            this.update(examDetailUpdateWrapper);
        }

        if (clearPaperType) {
            // 清掉考场-课程表中关联的试卷类型
            List<ExamDetailCourse> examDetailCourseList = examDetailCourseService.listByExamDetailId(entityId);
            if (!CollectionUtils.isEmpty(examDetailCourseList)) {
                for (ExamDetailCourse examDetailCourse : examDetailCourseList) {
                    examDetailCourse.setPaperType(null);
                }
                examDetailCourseService.updateBatchById(examDetailCourseList);
            }
        }
    }

    @Override
    public List<ExamDetailCourseInitMarkDto> listPrintFinishExamDetailCourse(String printStatus, long startTime, String markStatus) {
        return this.baseMapper.listPrintFinishExamDetailCourse(printStatus, startTime, markStatus);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void deleteExaminationData(Long printPlanId, ExamDataSourceEnum source) {
        ExamPrintPlan examPrintPlan = examPrintPlanService.getById(printPlanId);
        if (examPrintPlan == null) {
            throw ExceptionResultEnum.ERROR.exception("未找到印刷计划信息");
        }
        PrintPlanStatusEnum status = examPrintPlan.getStatus();
        if (PrintPlanStatusEnum.NEW != status && PrintPlanStatusEnum.READY != status) {
            throw ExceptionResultEnum.ERROR.exception(status.getName() + "状态下的印刷计划不能删除考务数据");
        }
        // 准备删除的考务-场次表id
        LambdaQueryWrapper<ExamDetail> examDetailQueryWrapper = new QueryWrapper<ExamDetail>().lambda().eq(ExamDetail::getPrintPlanId, printPlanId);
        if (Objects.nonNull(source)) {
            examDetailQueryWrapper.eq(ExamDetail::getExamDataSource, source);
        }
        List<Long> examDetailIds = this.list(examDetailQueryWrapper).stream().map(ExamDetail::getId).collect(Collectors.toList());
        if (examDetailIds.size() == 0) {
            return;
        }
        // 删除exam_detail 表数据
        this.remove(new QueryWrapper<ExamDetail>().lambda().in(ExamDetail::getId, examDetailIds));

        // 准备删除的考务-科目表id
        List<Long> examDetailCourseIds = examDetailCourseService.list(new QueryWrapper<ExamDetailCourse>().lambda().in(ExamDetailCourse::getExamDetailId, examDetailIds))
                .stream().map(ExamDetailCourse::getId).collect(Collectors.toList());
        if (examDetailCourseIds.size() == 0) {
            return;
        }
        // 删除exam_detail_course 表数据
        examDetailCourseService.remove(new QueryWrapper<ExamDetailCourse>().lambda().in(ExamDetailCourse::getId, examDetailCourseIds));

        // 准备删除的考务-考生表id
        List<Long> examStudentIds = examStudentService.list(new QueryWrapper<ExamStudent>().lambda().in(ExamStudent::getExamDetailCourseId, examDetailCourseIds))
                .stream().map(ExamStudent::getId).collect(Collectors.toList());
        if (examStudentIds.size() == 0) {
            return;
        }
        // 删除exam_student 表数据
        examStudentService.remove(new QueryWrapper<ExamStudent>().lambda().in(ExamStudent::getId, examStudentIds));
    }

    private List<CodeNameEnableValue> getExtendFieldsByFields(List<FieldsDto> fieldsDtoList) {
        List<CodeNameEnableValue> extendFieldsDtoList = new ArrayList<>();
        for (FieldsDto fieldsDto : fieldsDtoList) {
            CodeNameEnableValue codeNameEnableValue = new CodeNameEnableValue();
            BeanUtils.copyProperties(fieldsDto, codeNameEnableValue);
            extendFieldsDtoList.add(codeNameEnableValue);
        }
        return extendFieldsDtoList;
    }

    /**
     * 根据clazzIdSet集合查询班级名称集合
     *
     * @param clazzIdSet 班级idset
     * @return 班级名称集合
     */
    private List<String> findClazzNamesByClazzIdSet(Set<Long> clazzIdSet) {
        Set<String> clazzNameSet = new HashSet<>();
        if (clazzIdSet.size() > 0) {
            Set<String> basicClazzSet = basicClazzService.list(new QueryWrapper<BasicClazz>().lambda().in(BasicClazz::getId, clazzIdSet)).stream().map(BasicClazz::getClazzName).collect(Collectors.toSet());
            Set<String> teachClazzSet = teachClazzService.list(new QueryWrapper<TeachClazz>().lambda().in(TeachClazz::getId, clazzIdSet)).stream().map(TeachClazz::getClazzName).collect(Collectors.toSet());
            clazzNameSet.addAll(basicClazzSet);
            clazzNameSet.addAll(teachClazzSet);
        }
        return new ArrayList<>(clazzNameSet);
    }

    @Override
    public ExamDetail getByExamDetailCourseId(Long examDetailCourseId) {
        ExamDetailCourse examDetailCourse = examDetailCourseService.getById(examDetailCourseId);
        return this.getById(examDetailCourse.getExamDetailId());
    }

    @Override
    public int countMakeupUsedByExamIdAndPaperNumber(Long examId, String paperNumber) {
        return this.baseMapper.countMakeupUsedByExamIdAndPaperNumber(examId, paperNumber);
    }

    @Override
    public void updateStatusById(Long id, ExamDetailStatusEnum examDetailStatusEnum) {
        UpdateWrapper<ExamDetail> updateWrapper = new UpdateWrapper<>();
        updateWrapper.lambda().set(ExamDetail::getStatus, examDetailStatusEnum)
                .eq(ExamDetail::getId, id);
        this.update(updateWrapper);
    }

    @Override
    public List<ExamDetailCourseInitMarkDto> listPrintFinishPackageCode(Long examId, String paperNumber, String paperType) {
        return this.baseMapper.listPrintFinishPackageCode(examId, paperNumber, paperType);
    }

    @Override
    public List<InitExamDetailDto> listPrintFinishExamDetailCourseIds(Long examId, String paperNumber, String paperType) {
        return this.baseMapper.listPrintFinishExamDetailCourseIds(examId, paperNumber, paperType);
    }
}
