package com.qmth.jkserver.core.kj.plugin;

import cn.hutool.core.date.DateUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.google.gson.reflect.TypeToken;
import com.qmth.jkserver.common.FileData;
import com.qmth.jkserver.constant.SpringContextHolder;
import com.qmth.jkserver.constant.SystemConstant;
import com.qmth.jkserver.core.RoomClientMonitorService;
import com.qmth.jkserver.core.exception.JkServerException;
import com.qmth.jkserver.enums.ProgressType;
import com.qmth.jkserver.model.*;
import com.qmth.jkserver.service.*;
import com.qmth.jkserver.util.FileStoreUtil;
import com.qmth.jkserver.util.GsonUtil;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;

/**
 * 数据存储 此操作在数据校验通过之后
 * 基于业务逻辑实现的事物控制
 *
 * @author tianjun
 */
public class NormalKjStore {

    private static final Logger LOGGER = LoggerFactory.getLogger(NormalKjStore.class);

    //考生答案缓存
    private Map<String, ExamStudentAnswer> ANSWER_CACHE = new LinkedHashMap<String, ExamStudentAnswer>();
    //需要删除的考生旧答案
    private List<Long> OLD_ANSWER_LIST = new ArrayList<Long>();
    //重考的考生批次ID
    private Set<String> BATCHIDS = new HashSet<String>();
    //重考考生信息
    private List<ExamRepeatRecord> REPEAT_RECORDS = new ArrayList<ExamRepeatRecord>();

    private InputStream absentCsv;
    private InputStream reexamCsv;
    private InputStream omrTxt;
    private InputStream subjectiveTxt;
    private InputStream timeTxt;
    private InputStream pathTxt;
    private InputStream cheatCsv;
    private InputStream abortCsv;

    private String placeCode;
    private String roomCode;
    private String examCode;

    private File zip;
    private InputStream inputStream;
    private ZipFile zipFile;
    private ZipInputStream zipInputStream;
    private Long encryptedFileId;
    private ExamStudentService examStudentService;
    private ExamStudentAnswerService examStudentAnswerService;
    private JkExamPlanService examPlanService;
    private ExamKjHZService examKjHZService;
    private Map<String, ExamStudent> STUDENT_CAHE;


    public NormalKjStore(
            File zip,
            String examCode,
            Long encryptedFileId,
            String placeCode,
            String roomCode,
            Map<String, ExamStudent> STUDENT_CAHE
    ) throws ZipException, IOException {
        this.zip = zip;
        zipFile = new ZipFile(this.zip);
        inputStream = new BufferedInputStream(new FileInputStream(zip));
        zipInputStream = new ZipInputStream(inputStream);
        this.examCode = examCode;
        this.encryptedFileId = encryptedFileId;
        this.placeCode = placeCode;
        this.roomCode = roomCode;
        this.STUDENT_CAHE = STUDENT_CAHE;
        this.examStudentService = SpringContextHolder.getBean(ExamStudentService.class);
        this.examStudentAnswerService = SpringContextHolder.getBean(ExamStudentAnswerService.class);
        this.examPlanService = SpringContextHolder.getBean(JkExamPlanService.class);
        this.examKjHZService = SpringContextHolder.getBean(ExamKjHZService.class);
    }

    protected void initCache() {
        ANSWER_CACHE.clear();
        OLD_ANSWER_LIST.clear();
        BATCHIDS.clear();
        REPEAT_RECORDS.clear();
    }

    public void execute(ProgressType type) throws IOException {
        try {
            //0\解析文件
            initCache();
            extractFile();
            // 1\提取考生缺考信息
            extractAbsent();
            // 2\提取异常处信息
            extractAbort();
            // 3\提取考生违纪信息
            extractCheat();
            // 4\提取考生重考信息
            extractRepeat();
            // 5\提取考生客观题答案信息
            extractObjectiveAnswer();
            // 提取主观题作答信息
            extractSubjectiveInfo();
            // 6\提取考生答题时间
            extractTime();
            // 7\提取答题轨迹
            extractAnswerPath();
            // 9\保存答案历史记录
            Attachment storagedOriginalAnswerFile = saveOriginalHistory();
            //10 保存解密之后的文件
            Attachment storagedDecodedFile = storeKjDecodedFile(placeCode, roomCode);
            //10\保存考生答案
            saveAnswer(storagedOriginalAnswerFile, storagedDecodedFile);
            //11\保存上传进度
            saveProgress(storagedOriginalAnswerFile, type);
            //12\提取监考端日志文件以及数据备份文件
            RoomClientMonitorService roomClientMonitorService = SpringContextHolder.getBean(RoomClientMonitorService.class);
            roomClientMonitorService.execute(storagedDecodedFile, placeCode, roomCode);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (inputStream != null) {
                IOUtils.closeQuietly(inputStream);
            }
            if (zipInputStream != null) {
                IOUtils.closeQuietly(zipInputStream);
            }
            if (zipFile != null) {
                IOUtils.closeQuietly(zipFile);
            }
            //关闭打开的文件的输入流
            if (absentCsv != null) {
                IOUtils.closeQuietly(absentCsv);
            }
            if (reexamCsv != null) {
                IOUtils.closeQuietly(reexamCsv);
            }
            if (omrTxt != null) {
                IOUtils.closeQuietly(omrTxt);
            }
            if (subjectiveTxt != null) {
                IOUtils.closeQuietly(subjectiveTxt);
            }
            if (timeTxt != null) {
                IOUtils.closeQuietly(timeTxt);
            }
            if (pathTxt != null) {
                IOUtils.closeQuietly(pathTxt);
            }
            if (cheatCsv != null) {
                IOUtils.closeQuietly(cheatCsv);
            }
            if (abortCsv != null) {
                IOUtils.closeQuietly(abortCsv);
            }
        }
    }

    //提取考生的答题时间
    protected void extractTime() throws IOException {
        if (timeTxt != null) {
            FileData fileData = new FileData(timeTxt, ",");
            while (fileData.nextLine()) {
                String[] datas = fileData.getDatas();
                String ticketNo = datas[0];
                String courseCode = datas[1];
                String time = datas[2];
                ExamStudentAnswer examStudentAnswer = invokeAnswer(ticketNo, courseCode);
                examStudentAnswer.setAnswerTime(time);
            }
            fileData.close();
        }
    }

    //答题轨迹
    protected void extractAnswerPath() throws IOException {
        if (pathTxt != null) {
            FileData fileData = new FileData(pathTxt, ",");
            while (fileData.nextLine()) {
                String[] datas = fileData.getDatas();
                String ticketNo = datas[0];
                String courseCode = datas[1];
                String answerPath = datas[2];
                ExamStudentAnswer examStudentAnswer = invokeAnswer(ticketNo, courseCode);
                examStudentAnswer.setAnswerPath(answerPath);
            }
            fileData.close();
        }
    }

    protected Attachment saveOriginalHistory() throws Exception {
        // 2、原始文件转存
        if (StringUtils.isEmpty(placeCode) || StringUtils.isEmpty(roomCode)) {
            throw new JkServerException("考点代码和考场代码都不能够为空");
        }
        Attachment storagedFile = storeKjOriginalFile(placeCode, roomCode, encryptedFileId);
        KjOriginalFileHistory history = new KjOriginalFileHistory();
        history.setId(SystemConstant.getDbUuid());
        history.setTime(System.currentTimeMillis());
        history.setExamCode(examCode);
        history.setOriginalFileId(storagedFile.getId());
        history.setPlaceCode(placeCode);
        history.setRoomCode(roomCode);
        examKjHZService.saveOriginalHistory(history);
        return storagedFile;
    }

    // 监考端上传的原始数据包 转存到 存储区
    protected Attachment storeKjOriginalFile(String placeCode, String roomCode, Long tempId) throws Exception {
        FileStoreUtil fileStoreConfig = SpringContextHolder.getBean(FileStoreUtil.class);
        JkExamPlanService examPlanService = SpringContextHolder.getBean(JkExamPlanService.class);
        AttachmentService attachmentService = SpringContextHolder.getBean(AttachmentService.class);
        Attachment temp = attachmentService.findByIdService(tempId);
        ExamPlan examPlan = examPlanService.findByExamCode(temp.getExamCode());
        File storeDir = new File(fileStoreConfig.getStorageRoot(), examPlan.getExamType().name());
        storeDir = new File(storeDir, temp.getExamCode());
        storeDir = new File(storeDir, "kj");
        storeDir = new File(storeDir, "original");
        storeDir = new File(storeDir, placeCode + "_" + roomCode);
        if (!storeDir.exists()) {
            storeDir.mkdirs();
        }
        String path = examPlan.getExamType().name() + File.separator + temp.getExamCode() + File.separator + "kj"
                + File.separator + "original" + File.separator + (placeCode + "_" + roomCode) + File.separator + new SimpleDateFormat("yyyy-MM-dd").format(new Date()) +
                File.separator + SystemConstant.getNanoId();
        String accessUri = path + File.separator + temp.getOriginalName();
        File target = new File(fileStoreConfig.getStorageRoot(), accessUri);
        if (!target.getParentFile().exists()) {
            target.getParentFile().mkdirs();
        }
        if (!target.exists()) {
            target.createNewFile();
        }
        File src = null;
        if (fileStoreConfig.workIsOss()) {
            StringJoiner stringJoiner = new StringJoiner("").add(fileStoreConfig.getWorkRoot())
                    .add(File.separator).add(temp.getAccessUri());
            src = fileStoreConfig.ossDownload(stringJoiner.toString().replaceAll("\\\\", "/"), fileStoreConfig.getWorkRoot() + File.separator + temp.getAccessUri(), fileStoreConfig.getWorkRoot());
        } else {
            src = new File(fileStoreConfig.getWorkRoot(), temp.getAccessUri());
        }
        if (!src.exists()) {
            throw new JkServerException("原始文件不存在");
        }
        attachmentService.copyFile(src, target);
        //2020-12-19日加入上传到阿里云
        String md5 = DigestUtils.md5Hex(new FileInputStream(target));
        if (fileStoreConfig.storageIsOss()) {
            StringJoiner stringJoiner = new StringJoiner("").add(fileStoreConfig.getStorageRoot()).add(File.separator).add(accessUri);
            fileStoreConfig.ossUpload(stringJoiner.toString(), target, md5, fileStoreConfig.getStorageRoot());
        }
        Attachment attachment = new Attachment();
        attachment.setId(SystemConstant.getDbUuid());
        attachment.setName(temp.getOriginalName());
        attachment.setExamCode(temp.getExamCode());
        attachment.setExt(temp.getExt());
        attachment.setMoved(false);
        attachment.setOriginalName(temp.getOriginalName());
        attachment.setPath(path);
        attachment.setSize(temp.getSize());
        attachment.setTime(System.currentTimeMillis());
        attachment.setRelateId(temp.getId());
        attachment.setAccessUri(accessUri);
        attachment.setMd5(md5);
        attachmentService.saveOrUpdate(attachment);
        // 修改临时文件的转存属性值
        temp.setMoved(true);
        attachmentService.saveOrUpdate(temp);
        if (fileStoreConfig.storageIsOss()) {
            target.delete();
        }
        return attachment;
    }


    // 将解压之后的文件存储
    protected Attachment storeKjDecodedFile(String placeCode, String roomCode) throws Exception {
        FileStoreUtil fileStoreConfig = SpringContextHolder.getBean(FileStoreUtil.class);
        JkExamPlanService examPlanService = SpringContextHolder.getBean(JkExamPlanService.class);
        AttachmentService attachmentService = SpringContextHolder.getBean(AttachmentService.class);
        ExamPlan examPlan = examPlanService.findByExamCode(examCode);
        File storeDir = new File(fileStoreConfig.getStorageRoot(), examPlan.getExamType().name());
        storeDir = new File(storeDir, examCode);
        storeDir = new File(storeDir, "kj");
        storeDir = new File(storeDir, "decoded");
        storeDir = new File(storeDir, placeCode + "_" + roomCode);
        String timestamp = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
        storeDir = new File(storeDir, timestamp);
        String random = SystemConstant.getNanoId();
        storeDir = new File(storeDir, random);
        if (!storeDir.exists()) {
            storeDir.mkdirs();
        }
        String path = examPlan.getExamType().name() + File.separator + examCode + File.separator + "kj"
                + File.separator + "decoded" + File.separator + (placeCode + "_" + roomCode) + File.separator +
                timestamp + File.separator + random;
        String accessUri = path + File.separator + zip.getName();
        File target = new File(fileStoreConfig.getStorageRoot(), accessUri);
        target.createNewFile();
        FileUtils.copyFile(zip, target);
        //2020-12-19日加入上传到阿里云
        String md5 = DigestUtils.md5Hex(new FileInputStream(target));
        String zipMd5 = DigestUtils.md5Hex(new FileInputStream(zip));
        if (fileStoreConfig.workIsOss()) {
            StringJoiner stringJoiner = new StringJoiner("").add(fileStoreConfig.getStorageRoot()).add(File.separator).add(accessUri);
            fileStoreConfig.ossUpload(stringJoiner.toString(), target, md5, fileStoreConfig.getWorkRoot());
            fileStoreConfig.ossUpload(zip.getAbsolutePath().replace(fileStoreConfig.getWorkRoot(), fileStoreConfig.getWorkRoot()), zip, zipMd5, fileStoreConfig.getWorkRoot());
        }
        Attachment attachment = new Attachment();
        attachment.setId(SystemConstant.getDbUuid());
        attachment.setExamCode(examCode);
        attachment.setName(target.getName());
        attachment.setOriginalName(target.getName());
        attachment.setPath(path);
        attachment.setAccessUri(accessUri);
        attachment.setSize((double) target.length());
        attachment.setMd5(md5);
        attachmentService.saveOrUpdate(attachment);
        if (fileStoreConfig.workIsOss()) {
            target.delete();
            File qm = new File(zip.getAbsolutePath().replace(SystemConstant.ZIP_PREFIX, ""));
            qm.delete();
            zip.delete();
        }
        return attachment;
    }

    /**
     * 获取文件的后缀
     */
    protected String getExtension(String fileName) {
        int k = fileName.lastIndexOf(".");
        String ext = "";
        if (k > 0) {
            ext = fileName.substring(k + 1, fileName.length());
        }
        return ext;
    }

    // 提取考生客观题答案信息
    protected void extractObjectiveAnswer() throws IOException {
        if (omrTxt != null) {
            FileData fileData = new FileData(omrTxt, ",");
            while (fileData.nextLine()) {
                String[] datas = fileData.getDatas();
                String ticketNo = datas[0];
                String courseCode = datas[1];
                String paperId = datas[3];
                String paperName = datas[4];
                String objectiveAnswer = datas[5];
                ExamStudentAnswer examStudentAnswer = invokeAnswer(ticketNo, courseCode);
                examStudentAnswer.setPaperId(paperId);
                examStudentAnswer.setPaperName(paperName);
                examStudentAnswer.setObjectiveAnswer(objectiveAnswer);
            }
            fileData.close();
        }
    }

    // 提取主观题作答信息、非答案
    protected void extractSubjectiveInfo() throws IOException {
        if (subjectiveTxt != null) {
            FileData fileData = new FileData(subjectiveTxt, ",");
            while (fileData.nextLine()) {
                String[] datas = fileData.getDatas();
                String ticketNo = datas[0];
                String courseCode = datas[1];
                String paperId = datas[2];
                ExamStudentAnswer examStudentAnswer = invokeAnswer(ticketNo, courseCode);
                examStudentAnswer.setPaperId(paperId);
                examStudentAnswer.setIsAbsent(false);
            }
            fileData.close();
        }
    }

    // 提取考生重考信息
    protected void extractRepeat() throws IOException {
        if (reexamCsv != null) {
            FileData fileData = new FileData(reexamCsv, "~");
            while (fileData.nextLine()) {
                String[] datas = fileData.getDatas();
                String batchId = datas[0];
                String batchName = datas[1];
                String stageId = datas[2];
                String ticketNo = datas[3];
                String studentName = datas[4];
                String courseCode = datas[5];
                ExamStudent examStudent = getCachedStudent(examCode, ticketNo, courseCode);
                if (examStudent == null) {
                    throw new JkServerException(
                            "无法获取到此考生的考务数据,考试识别码:" + examCode + ",准考证号:" + ticketNo + ",课程编码:" + courseCode);
                }
                if (StringUtils.isEmpty(this.placeCode)) {
                    this.placeCode = examStudent.getPlaceCode();
                }
                if (StringUtils.isEmpty(this.roomCode)) {
                    this.roomCode = examStudent.getRoomCode();
                }
                if (StringUtils.isEmpty(this.placeCode) || StringUtils.isEmpty(this.roomCode)) {
                    throw JkServerException.withFieldValidateError("考点代码以及考场代码不能够为空");
                }
                String courseName = datas[6];
                String time = datas[7];
                String newBatchId = datas[8];
                String newBatchName = datas[9];
                String newStageId = datas[10];
                String reason = datas[11];
                BATCHIDS.add(batchId);
                ExamRepeatRecord examRepeatRecord = new ExamRepeatRecord();
                examRepeatRecord.setId(SystemConstant.getDbUuid());
                examRepeatRecord.setExamCode(examCode);
                examRepeatRecord.setPlaceCode(this.placeCode);
                examRepeatRecord.setRoomCode(this.roomCode);
                examRepeatRecord.setBatchId(batchId);
                examRepeatRecord.setBatchName(batchName);
                examRepeatRecord.setStageId(stageId);
                examRepeatRecord.setTicketNo(ticketNo);
                examRepeatRecord.setStudentName(studentName);
                examRepeatRecord.setCourseCode(courseCode);
                examRepeatRecord.setCourseName(courseName);
                examRepeatRecord.setTime(Objects.nonNull(time) ? DateUtil.parse(time).getTime() : null);
                examRepeatRecord.setNewBatchId(newBatchId);
                examRepeatRecord.setNewBatchName(newBatchName);
                examRepeatRecord.setNewStageId(newStageId);
                examRepeatRecord.setReason(reason);
                REPEAT_RECORDS.add(examRepeatRecord);
            }
            fileData.close();
        }
    }

    // 提取考生违纪信息
    protected void extractCheat() throws IOException {
        if (cheatCsv != null) {
            JkExamCheatService jkExamCheatService = SpringContextHolder.getBean(JkExamCheatService.class);
            FileData fileData = new FileData(cheatCsv, "~");
            List<ExamCheat> examCheatList = new ArrayList<>();
            while (fileData.nextLine()) {
                String[] datas = fileData.getDatas();
                String ticketNo = datas[3];
                String courseCode = datas[5];
                ExamStudentAnswer examStudentAnswer = invokeAnswer(ticketNo, courseCode);
                examStudentAnswer.setIsCheat(true);
                ExamCheat examCheat = new ExamCheat();
                examCheat.setId(SystemConstant.getDbUuid());
                examCheat.setOperateType(datas[7]);
                examCheat.setOperateTime(Objects.nonNull(datas[8]) ? DateUtil.parse(datas[8]).getTime() : null);
                examCheat.setCreateTime(System.currentTimeMillis());
                examCheat.setReason(datas[9]);
                examCheat.setIntroduce(datas[10]);
//                examStudentAnswer.setExamCheat(examCheat);
                examCheat.setExamStudentAnswerId(examStudentAnswer.getId());
                examCheatList.add(examCheat);
            }
            fileData.close();
            jkExamCheatService.saveBatch(examCheatList);
        }
    }

    // 提取异常处理信息
    protected void extractAbort() throws IOException {
        // 1、提取异常处理信息
        if (abortCsv != null) {
            JkExamAbortService jkExamAbortService = SpringContextHolder.getBean(JkExamAbortService.class);
            FileData fileData = new FileData(abortCsv, "~");
            List<ExamAbort> examAbortList = new ArrayList<>();
            while (fileData.nextLine()) {
                String[] datas = fileData.getDatas();
                String ticketNo = datas[3];
                String courseCode = datas[5];
                ExamStudentAnswer examStudentAnswer = invokeAnswer(ticketNo, courseCode);
                examStudentAnswer.setIsAbort(true);
                ExamAbort examAbort = new ExamAbort();
                examAbort.setId(SystemConstant.getDbUuid());
                String loginTime = datas[7];
                if (StringUtils.isNotEmpty(loginTime)) {
                    Long time = DateUtil.parse(loginTime).getTime();
                    examAbort.setTime(time);
                    examStudentAnswer.setLoginTime(time);
                }
                if (datas.length >= 9) {
                    examAbort.setReason(StringUtils.trimToNull(datas[8]));
                }
                if (datas.length >= 10) {
                    examAbort.setRemark(StringUtils.trimToNull(datas[9]));
                }
//                examStudentAnswer.getExamAborts().add(examAbort);
                examAbort.setExamStudentAnswerId(examStudentAnswer.getId());
                examAbortList.add(examAbort);
            }
            fileData.close();
            jkExamAbortService.saveBatch(examAbortList);
        }
    }

    // 提取考生缺考信息
    protected void extractAbsent() throws IOException {
        // 1、提取考生缺考信息
        if (absentCsv != null) {
            JkExamAbsentService jkExamAbsentService = SpringContextHolder.getBean(JkExamAbsentService.class);
            FileData fileData = new FileData(absentCsv, "~");
            List<ExamAbsent> examAbsentList = new ArrayList<>();
            while (fileData.nextLine()) {
                String[] datas = fileData.getDatas();
                String ticketNo = datas[3];
                String courseCode = datas[5];
                ExamStudentAnswer examStudentAnswer = invokeAnswer(ticketNo, courseCode);
                examStudentAnswer.setIsAbsent(true);
                ExamAbsent examAbsent = new ExamAbsent();
                examAbsent.setId(SystemConstant.getDbUuid());
                examAbsent.setCreateTime(System.currentTimeMillis());
                examAbsent.setRemark("缺考");
//                examStudentAnswer.setExamAbsent(examAbsent);
                examAbsent.setExamStudentAnswerId(examStudentAnswer.getId());
                examAbsentList.add(examAbsent);
            }
            jkExamAbsentService.saveBatch(examAbsentList);
            fileData.close();
        }
    }

    protected void extractFile() throws IOException {
        ZipEntry ze = null;
        while ((ze = zipInputStream.getNextEntry()) != null) {
            if (!ze.isDirectory()) {
                long size = ze.getSize();
                if (size > 0 && (ze.getName().endsWith("csv") || ze.getName().endsWith("txt"))) {
                    if (StringUtils.trimToNull(ze.getName()).equalsIgnoreCase("absent.csv")) {
                        absentCsv = zipFile.getInputStream(ze);
                    }
                    if (StringUtils.trimToNull(ze.getName()).equalsIgnoreCase("reexam.csv")) {
                        reexamCsv = zipFile.getInputStream(ze);
                    }
                    if (StringUtils.trimToNull(ze.getName()).equalsIgnoreCase("omr.txt")) {
                        omrTxt = zipFile.getInputStream(ze);
                    }
                    if (StringUtils.trimToNull(ze.getName()).equalsIgnoreCase("subjective.txt")) {
                        subjectiveTxt = zipFile.getInputStream(ze);
                    }
                    if (StringUtils.trimToNull(ze.getName()).equalsIgnoreCase("cheat.csv")) {
                        cheatCsv = zipFile.getInputStream(ze);
                    }
                    if (StringUtils.trimToNull(ze.getName()).equalsIgnoreCase("abort.csv")) {
                        abortCsv = zipFile.getInputStream(ze);
                    }
                    if (StringUtils.trimToNull(ze.getName()).equalsIgnoreCase("time.txt")) {
                        timeTxt = zipFile.getInputStream(ze);
                    }
                    if (StringUtils.trimToNull(ze.getName()).equalsIgnoreCase("path.txt")) {
                        pathTxt = zipFile.getInputStream(ze);
                    }
                }
            }
            zipInputStream.closeEntry();
        }
    }

    protected ExamStudentAnswer invokeAnswer(String ticketNo, String courseCode) {
        String key = examCode + ticketNo + courseCode;
        ExamStudentAnswer examStudentAnswer = ANSWER_CACHE.get(key);
        if (examStudentAnswer == null) {
            examStudentAnswer = new ExamStudentAnswer();
            examStudentAnswer.setId(SystemConstant.getDbUuid());
            examStudentAnswer.setExamCode(examCode);
            long time1 = System.currentTimeMillis();
            ExamStudent examStudent = getCachedStudent(examCode, ticketNo, courseCode);
            long time2 = System.currentTimeMillis();
            LOGGER.info("-----!!!find student use:" + (time2 - time1) + "ms");
            if (examStudent == null) {
                throw new JkServerException(
                        "无法获取到此考生的考务数据,考试识别码:" + examCode + ",准考证号:" + ticketNo + ",课程编码:" + courseCode);
            }
            examStudentAnswer.setStudentId(examStudent.getId());
            if (StringUtils.isEmpty(placeCode)) {
                placeCode = examStudent.getPlaceCode();
            }
            if (StringUtils.isEmpty(roomCode)) {
                roomCode = examStudent.getRoomCode();
            }
            ANSWER_CACHE.put(key, examStudentAnswer);
            Long oldId = examStudentAnswerService.findAnswerIdByStudentId(examStudent.getId());
            if (oldId != null) {
                OLD_ANSWER_LIST.add(oldId);
            }
        }
        return examStudentAnswer;
    }


    protected void saveAnswer(Attachment storagedOriginalAnswerFile, Attachment storagedDecodedFile) {
//        MongoTemplate mongoOperations = BeanFactory.getBean(MongoTemplate.class);
        List<ExamStudentAnswer> list = new ArrayList<ExamStudentAnswer>();
        for (ExamStudentAnswer answer : ANSWER_CACHE.values()) {
            answer.setOriginalAnswerId(storagedOriginalAnswerFile.getId());
            answer.setDecodeFileId(storagedDecodedFile.getId());
            answer.setTime(System.currentTimeMillis());
//            Object object = mongoOperations.getConverter().convertToMongoType(answer);
//            DBObject dbObject = (DBObject) JSON.parse(object.toString());
//            dbObject.put("_class", ExamStudentAnswer.class.getName());
//            list.add(dbObject);
            list.add(answer);
        }
        //删除此次上传的考生的旧答案
        ExamStudentAnswerService examStudentAnswerService = SpringContextHolder.getBean(ExamStudentAnswerService.class);
        if (OLD_ANSWER_LIST.size() > 0) {
            long time1 = System.currentTimeMillis();
//            DBObject delObject = new BasicDBObject("$in", OLD_ANSWER_LIST);
//            DBObject queryDel = new BasicDBObject("_id", delObject);
//            mongoOperations.getCollection("jk_exam_student_answer").remove(queryDel);
            examStudentAnswerService.removeByIds(OLD_ANSWER_LIST);
            long time2 = System.currentTimeMillis();
            LOGGER.info(".....@DELETE old size:" + OLD_ANSWER_LIST.size() + ",and use time :" + (time2 - time1) + "ms");
        }
        //保存当前考场新的答案
        System.out.println("###insert answer to mongodb!!!!");
        long time3 = System.currentTimeMillis();
//        mongoOperations.getCollection("jk_exam_student_answer").insert(list);
        examStudentAnswerService.saveOrUpdateBatch(list);
        long time4 = System.currentTimeMillis();
        LOGGER.info(".....@INSERT new size:" + list.size() + ",use :" + (time4 - time3) / 1000 + "ms");
        //如果上传了重考考生，那么此处需要处理
        JkExamRepeatRecordService jkExamRepeatRecordService = SpringContextHolder.getBean(JkExamRepeatRecordService.class);
        if (BATCHIDS.size() > 0) {
//            DBObject delRepeatQuery = new BasicDBObject();
//            delRepeatQuery.put("exam_code", examCode);
//            delRepeatQuery.put("place_code", placeCode);
//            delRepeatQuery.put("room_code", roomCode);
//            delRepeatQuery.put("batch_id", new BasicDBObject("$in", BATCHIDS));
            QueryWrapper<ExamRepeatRecord> examRepeatRecordQueryWrapper = new QueryWrapper<>();
            examRepeatRecordQueryWrapper.lambda().eq(ExamRepeatRecord::getExamCode, examCode)
                    .eq(ExamRepeatRecord::getPlaceCode, placeCode)
                    .eq(ExamRepeatRecord::getRoomCode, roomCode)
                    .in(ExamRepeatRecord::getBatchId, BATCHIDS);
            long time8 = System.currentTimeMillis();
//            mongoOperations.getCollection("jk_exam_repeat_record").remove(delRepeatQuery);
            jkExamRepeatRecordService.remove(examRepeatRecordQueryWrapper);
            long time9 = System.currentTimeMillis();
            LOGGER.info("----remove old exam repeat dbobject use time:" + (time9 - time8) + "ms");
            //插入新的数据
            if (REPEAT_RECORDS.size() > 0) {
                List<ExamRepeatRecord> recordList = new ArrayList<ExamRepeatRecord>();
                for (ExamRepeatRecord record : REPEAT_RECORDS) {
                    record.setCreateTime(System.currentTimeMillis());
//                    Object object = mongoOperations.getConverter().convertToMongoType(record);
//                    DBObject recordObject = (DBObject) JSON.parse(object.toString());
//                    recordObject.put("_class", ExamRepeatRecord.class.getName());
//                    recordList.add(recordObject);
                    recordList.add(record);
                }
                long time10 = System.currentTimeMillis();
//                mongoOperations.getCollection("jk_exam_repeat_record").insert(recordList);
                jkExamRepeatRecordService.saveOrUpdateBatch(recordList);
                long time11 = System.currentTimeMillis();
                LOGGER.info("----insert new exam repeat dbobject use time:" + (time11 - time10) + "ms");
            }
        } else {
            LOGGER.info("----------no reexam file------------------");
        }
        //保存答案历史记录
        try {
//            List<DBObject> anshistoryList = list.stream().map(obj -> {
//                obj.put("_class", ExamStudentAnswerHistory.class.getName());
//                return obj;
//            }).collect(Collectors.toList());
//            mongoOperations.getCollection("jk_exam_student_answer_history").insert(anshistoryList);
//            LOGGER.info("----------insert history list size:" + anshistoryList.size() + "------------------");
            JkExamStudentAnswerHistoryService jkExamStudentAnswerHistoryService = SpringContextHolder.getBean(JkExamStudentAnswerHistoryService.class);
            List<ExamStudentAnswerHistory> listHistory = GsonUtil.fromJson(GsonUtil.toJson(list), new TypeToken<List<ExamStudentAnswerHistory>>() {
            }.getType());
            jkExamStudentAnswerHistoryService.saveOrUpdateBatch(listHistory);
        } catch (Exception e) {
            LOGGER.warn("----------insert history list error:", e);
        }
    }

    protected void saveProgress(Attachment storagedAnswerFile, ProgressType type) {
        //存储当前考点考场结果上传进度
        if (StringUtils.isEmpty(placeCode) || StringUtils.isEmpty(roomCode)) {
            throw new JkServerException("考点考场不能够为空");
        }
        ExamKjProgress examKjProgress = examKjHZService.findProgress(examCode, placeCode, roomCode);
        if (examKjProgress == null) {
            examKjProgress = new ExamKjProgress();
            examKjProgress.setId(SystemConstant.getDbUuid());
            examKjProgress.setExamCode(examCode);
            examKjProgress.setPlaceCode(placeCode);
            examKjProgress.setRoomCode(roomCode);
        }
        //标记这个考场已经上传
        examPlanService.updateRoomKjAlready(examCode, placeCode, roomCode);
        examKjProgress.setType(type);
        examKjProgress.setStartTime(System.currentTimeMillis());
        examKjProgress.setAttachmentId(storagedAnswerFile.getId());
        examKjHZService.saveProgress(examKjProgress);
    }

    private ExamStudent getCachedStudent(String examCode, String ticketNo, String courseCode) {
        String key = examCode + ticketNo + courseCode;
        ExamStudent examStudent = STUDENT_CAHE.get(key);
        if (examStudent != null) {
            LOGGER.info(".....@get student from cache OK!!");
            return examStudent;
        }
        examStudent = examStudentService.findStudent(examCode, ticketNo, courseCode);
        if (examStudent == null) {
            throw new JkServerException(
                    "无法获取到此考生的考务数据,考试识别码:" + examCode + ",准考证号:" + ticketNo + ",课程编码:" + courseCode);
        } else {
            LOGGER.info(".....@cache student OK!!");
            STUDENT_CAHE.put(key, examStudent);
            return examStudent;
        }
    }
}
