package com.ynfy.buss.exam.exam.service.impl;

import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ynfy.buss.exam.exam.dto.*;
import com.ynfy.buss.exam.exam.entity.Exam;
import com.ynfy.buss.exam.exam.enums.ExamState;
import com.ynfy.buss.exam.exam.enums.PaperState;
import com.ynfy.buss.exam.exam.mapper.ExamMapper;
import com.ynfy.buss.exam.exam.service.IExamService;
import com.ynfy.buss.exam.job.BreakExamJob;
import com.ynfy.buss.exam.paper.enmus.JoinType;
import com.ynfy.buss.exam.paper.entity.Paper;
import com.ynfy.buss.exam.paper.service.IPaperService;
import com.ynfy.buss.exam.paperquestion.entity.PaperQuestion;
import com.ynfy.buss.exam.paperquestion.service.IPaperQuestionService;
import com.ynfy.buss.exam.paperrulegroup.entity.PaperRuleGroup;
import com.ynfy.buss.exam.paperrulegroup.service.IPaperRuleGroupService;
import com.ynfy.buss.exam.question.entity.Question;
import com.ynfy.buss.exam.question.enums.QuestionType;
import com.ynfy.buss.exam.question.service.IQuestionService;
import com.ynfy.buss.exam.questionanswer.entity.QuestionAnswer;
import com.ynfy.buss.exam.userexam.entity.UserExam;
import com.ynfy.buss.exam.userexam.service.IUserExamService;
import com.ynfy.buss.exam.userexamquestion.entity.UserExamQuestion;
import com.ynfy.buss.exam.userexamquestion.service.IUserExamQuestionService;
import com.ynfy.buss.exam.userexamquestionanswer.entity.UserExamQuestionAnswer;
import com.ynfy.buss.exam.userexamquestionanswer.service.IUserExamQuestionAnswerService;
import com.ynfy.buss.exam.userexamresult.service.IUserExamResultService;
import com.ynfy.common.utils.CharUtil;
import com.ynfy.common.utils.CronUtils;
import com.ynfy.common.utils.ThreadPoolUtil;
import org.apache.commons.lang.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.constant.CommonConstant;
import org.jeecg.common.exception.JeecgBootException;
import org.jeecg.common.system.vo.LoginUser;
import org.jeecg.common.util.RedisUtil;
import org.jeecg.modules.quartz.entity.QuartzJob;
import org.jeecg.modules.quartz.service.IQuartzJobService;
import org.jeecg.modules.system.entity.SysDepart;
import org.jeecg.modules.system.service.ISysDepartService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.stream.Collectors;

import static java.util.stream.Collectors.toList;

/**
 * @Description: exam
 * @Author: jeecg-boot
 * @Date: 2023-02-23
 * @Version: V1.0
 */
@Service
public class ExamServiceImpl extends ServiceImpl<ExamMapper, Exam> implements IExamService {

    @Autowired
    private ExamMapper examMapper;

    @Autowired
    private IPaperService paperService;

    @Autowired
    private IUserExamService userExamService;

    @Autowired
    private IPaperQuestionService paperQuestionService;

    @Autowired
    private IUserExamQuestionService userExamQuestionService;

    @Autowired
    private IQuestionService questionService;

    @Autowired
    private IUserExamResultService userExamResultService;

    @Autowired
    public RedisUtil redisUtil;

    @Autowired
    private IQuartzJobService quartzJobService;

    @Autowired
    private ISysDepartService sysDepartService;

    @Autowired
    private IUserExamQuestionAnswerService userExamQuestionAnswerService;

    @Autowired
    private IPaperRuleGroupService paperRuleGroupService;

    @Override
    public Exam detail(String id) {
        return examMapper.detail(id);
    }

    /**
     * 分页查询
     */
    @Override
    public IPage<Exam> selectExamList(IPage<Exam> page, Exam exam) {
        LoginUser user = (LoginUser) SecurityUtils.getSubject().getPrincipal();
        if (!Objects.isNull(exam) && StringUtils.isNotBlank(exam.getTitle())) {
            exam.setTitle(exam.getTitle().replace("*", ""));
        }
        if (!Objects.isNull(user) && StringUtils.isNotBlank(user.getOrgCode())) {
            SysDepart depart = sysDepartService.queryByOrgCode(user.getOrgCode());
            if (!Objects.isNull(depart)) {
                user.setOrgCode(depart.getId());
            }
        }
        return examMapper.selectExamList(page, exam, user);
    }


    /**
     * 创建考试
     *
     * @param userId
     * @param examId
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public UserExam createExam(String userId, String examId) {
        Exam exam = detail(examId);
        if (exam == null) {
            throw new JeecgBootException("考试不存在！");
        }
        if (!ExamState.ENABLE.equals(exam.getState())) {
            throw new JeecgBootException("不在考试时间！");
        }
        if (!Objects.isNull(exam.getLimitCount())) {
            long userExamNum = userExamService.countUserExam(userId, examId);
            if (userExamNum >= exam.getLimitCount()) {
                throw new JeecgBootException("考试次数达到限制！");
            }
        }
        Paper paper = exam.getPaper();
        if (Objects.isNull(paper)) {
            throw new JeecgBootException("试卷不存在！");
        }
        List<PaperQuestion> paperQuestionList = null;
        if (paper.getJoinType().equals(JoinType.XT.getCode()) || paper.getJoinType().equals(JoinType.CT.getCode())) {//选题或者抽题
            paperQuestionList = paperQuestionService.listByPaperId(paper.getId());
            //题目乱序
            if (!Objects.isNull(exam.getQuestionDisorder()) && exam.getQuestionDisorder()) {
                //题目洗牌
                paperQuestionList = questionShuffle(paperQuestionList);
            }
        } else if (paper.getJoinType().equals(JoinType.SJ.getCode())) {//随机抽题
            //生成题目
            paperQuestionList = paperService.generateQuestion(paper.getId());
        }
        if (CollectionUtils.isEmpty(paperQuestionList)) {
            throw new JeecgBootException("无对应的考题！");
        }
        //保存考试
        return this.saveExam(userId, exam, paper, paperQuestionList);
    }

    /**
     * 题目乱序
     */
    public List<PaperQuestion> questionShuffle(List<PaperQuestion> paperQuestionList) {
        List<PaperQuestion> shuffleList = new ArrayList<>();
        Map<Integer, List<PaperQuestion>> map = paperQuestionList.stream().collect(Collectors.groupingBy(PaperQuestion::getQuestionType, LinkedHashMap::new, Collectors.toList()));
        for (Map.Entry<Integer, List<PaperQuestion>> item : map.entrySet()) {
            //题目洗牌
            Collections.shuffle(item.getValue());
            shuffleList.addAll(item.getValue());
        }
        //重新排序
        return paperQuestionService.sort(shuffleList);
    }


    /**
     * 保存考试
     *
     * @param userId
     * @param exam
     * @param paperQuestionList
     * @return
     */
    public UserExam saveExam(String userId, Exam exam, Paper paper, List<PaperQuestion> paperQuestionList) {
        UserExam userExam = new UserExam();
        userExam.setId(IdUtil.getSnowflake(1, 1).nextIdStr());
        userExam.setExamId(exam.getId());
        userExam.setUserScore(0);
        userExam.setUserId(userId);
        userExam.setPaperId(paper.getId());
        userExam.setState(PaperState.ING);
        userExam.setTotalScore(exam.getTotalScore());
        userExam.setQualifyScore(exam.getQualifyScore());
        userExam.setHasSubjective(paper.getHasSubjective());//是否有主观题

        //保存考试关联的试题列表
        if (!CollectionUtils.isEmpty(paperQuestionList)) {
            this.savePaperQuestion(userExam.getId(), paperQuestionList);
            //保存用户考试的题目答案
            this.saveQuestionAnswer(userExam.getId(), exam, paperQuestionList);
        }

        // 截止时间
        Calendar cl = Calendar.getInstance();
        cl.setTimeInMillis(System.currentTimeMillis());
        cl.add(Calendar.MINUTE, exam.getTotalTime());
        userExam.setLimitTime(cl.getTime());

        //保存考试记录
        userExamService.save(userExam);

        //异步创建定时任务，到交卷时间的10分钟后强制交卷
        ThreadPoolUtil.getExecutor().execute(() -> this.createJob(userExam.getId(), userExam.getLimitTime()));
        return userExam;
    }

    /**
     * 保存用户考试的题目答案
     *
     * @param userExamId
     * @param paperQuestionList
     */
    public void saveQuestionAnswer(String userExamId, Exam exam, List<PaperQuestion> paperQuestionList) {
        //获取题目和答案
        List<Question> questionList = listQuestion(paperQuestionList);
        if (!CollectionUtils.isEmpty(questionList)) {
            List<UserExamQuestionAnswer> userExamQuestionAnswerList = new ArrayList<>();
            questionList.stream().forEach(question -> {
                List<QuestionAnswer> answerList = question.getAnswerList();
                if (!CollectionUtils.isEmpty(answerList)) {
                    //答案乱序(支持单选题，多选题)
                    if (!Objects.isNull(exam.getAnswerDisorder()) && exam.getAnswerDisorder() && (QuestionType.RADIO.getCode().equals(question.getType()) || QuestionType.MULTI.getCode().equals(question.getType()))) {
                        //答案洗牌
                        Collections.shuffle(answerList);
                    }
                    int sort = 1;
                    for (QuestionAnswer questionAnswer : answerList) {
                        UserExamQuestionAnswer userExamQuestionAnswer = new UserExamQuestionAnswer();
                        userExamQuestionAnswer.setUserExamId(userExamId);
                        userExamQuestionAnswer.setQuestionId(question.getId());
                        userExamQuestionAnswer.setAnswerId(questionAnswer.getId());
                        userExamQuestionAnswer.setIsRight(questionAnswer.getIsRight());
                        userExamQuestionAnswer.setSort(sort);
                        //填空题不再生成tag
                        if (QuestionType.BLANK.getCode().equals(question.getType())) {
                            userExamQuestionAnswer.setTag(questionAnswer.getTag());
                        } else {
                            userExamQuestionAnswer.setTag(CharUtil.getZm(sort - 1));
                        }
                        userExamQuestionAnswerList.add(userExamQuestionAnswer);
                        sort++;
                    }
                }
            });
            userExamQuestionAnswerService.saveBatch(userExamQuestionAnswerList);
        }
    }


    /**
     * 创建定时任务
     *
     * @param userExamId
     * @param limitTime
     */
    public void createJob(String userExamId, Date limitTime) {
        // 强制交卷任务
        QuartzJob quartzJob = new QuartzJob();

        //交卷时间的10分钟后强制交卷
        DateTime offsetTime = DateUtil.offsetMinute(limitTime, 10);
        quartzJob.setCronExpression(CronUtils.dateToCron(offsetTime));
        quartzJob.setJobClassName(BreakExamJob.class.getName());
        quartzJob.setParameter(userExamId);
        quartzJob.setStatus(CommonConstant.STATUS_NORMAL);
        quartzJobService.saveAndScheduleJob(quartzJob);
    }

    /**
     * 获取规则组map
     *
     * @param paperQuestionList
     * @return
     */
    public Map<String, PaperRuleGroup> getGroupMap(List<PaperQuestion> paperQuestionList) {
        List<String> groupIdList = paperQuestionList.stream().map(PaperQuestion::getGroupId).collect(Collectors.toList());
        List<PaperRuleGroup> groupList = paperRuleGroupService.listByIds(groupIdList);
        return groupList.stream().collect(Collectors.toMap(PaperRuleGroup::getId, a -> a, (k1, k2) -> k1));
    }

    /**
     * 保存试卷试题列表
     */
    private void savePaperQuestion(String userExamId, List<PaperQuestion> paperQuestionList) {
        paperQuestionList = paperQuestionList.stream().sorted(Comparator.comparing(PaperQuestion::getQuestionType).thenComparing(PaperQuestion::getSort)).collect(Collectors.toList());
        //获取规则组map
        Map<String, PaperRuleGroup> groupMap = getGroupMap(paperQuestionList);

        List<UserExamQuestion> userExamQuestionList = new ArrayList<>();
        int index = 1;
        for (PaperQuestion item : paperQuestionList) {
            UserExamQuestion userExamQuestion = new UserExamQuestion();
            BeanUtils.copyProperties(item, userExamQuestion);

            if (!Objects.isNull(groupMap)) {
                PaperRuleGroup ruleGroup = groupMap.get(item.getGroupId());
                if (!Objects.isNull(ruleGroup)) {
                    //设置题目是否允许漏选
                    userExamQuestion.setCanMissOption(ruleGroup.getCanMissOption());
                    //设置题目是否按空给分
                    userExamQuestion.setCanBlankOption(ruleGroup.getCanBlankOption());
                }
            }
            userExamQuestion.setId(null);
            userExamQuestion.setQuestionIndex(index);
            userExamQuestion.setUserExamId(userExamId);
            userExamQuestionList.add(userExamQuestion);
            index++;
        }
        userExamQuestionService.saveBatch(userExamQuestionList);
    }


    /**
     * 在线考试试卷详情，包括答题卡，试题（不包含正确答案和解析）
     *
     * @param userExamId
     * @return
     */
    @Override
    public UserExamDTO examDetail(String userExamId) {
        return examDetail(userExamId, false, false, true, false);
    }

    public UserExamDTO examDetail(String userExamId, boolean needAnswerFlag, boolean needAnalysis, boolean needClearBlankContent, boolean needRenderBlank) {
        UserExamDTO dto = new UserExamDTO();
        UserExam userExam = userExamService.listUserExam(userExamId);
        if (Objects.isNull(userExam)) {
            throw new JeecgBootException("考试不存在！");
        }
        BeanUtils.copyProperties(userExam, dto);
        List<UserExamQuestion> userExamQuestionList = dto.getUserExamQuestionList();
        List<Map<String, AnswerCardDTO>> answerCardList = new ArrayList<>();
        if (!CollectionUtils.isEmpty(userExamQuestionList)) {
            //获取题目和答案
            Map<String, Question> questionMap = getUserExamQuestionMap(userExamId, userExamQuestionList, needAnswerFlag, needAnalysis, false, true, needClearBlankContent);

            //设置题目
            setUserExamQuestion(questionMap, userExamQuestionList, needRenderBlank);

            //设置答题卡
            setAnswerCard(userExamQuestionList, answerCardList);
        }
        dto.setAnswerCardList(answerCardList);
        return dto;
    }

    /**
     * 用户考试成绩详情，包括答题卡，试题（包含正确答案和解析）
     *
     * @param userExamId
     * @return
     */
    @Override
    public UserExamDTO userExamResultDetail(String userExamId) {
        UserExamDTO userExamDTO = examDetail(userExamId, true, true, false, true);
        //设置单选题/多选题/判断题的答案tag
        setAnswerTag(userExamDTO);
        return userExamDTO;
    }

    /**
     * 设置单选题/多选题/判断题的答案tag
     *
     * @param userExamDTO
     */
    public void setAnswerTag(UserExamDTO userExamDTO) {
        List<UserExamQuestion> userExamQuestionList = userExamDTO.getUserExamQuestionList();
        if (!CollectionUtils.isEmpty(userExamQuestionList)) {
            userExamQuestionList.stream().forEach(userExamQuestion -> userExamQuestion
                    .setAnswer(generateQuestionAnswerTag(userExamQuestion.getAnswer(), userExamQuestion.getQuestion())));
        }
    }

    /**
     * 生成单选，单选，填空答案tag
     *
     * @param userAnswer
     * @param question
     * @return
     */
    @Override
    public String generateQuestionAnswerTag(String userAnswer, Question question) {
        if (Objects.isNull(question)) {
            return null;
        }
        if (QuestionType.RADIO.getCode().equals(question.getType()) || QuestionType.MULTI.getCode().equals(question.getType())
                || QuestionType.JUDGE.getCode().equals(question.getType())) {
            List<QuestionAnswer> answerList = question.getAnswerList();
            if (StringUtils.isNotBlank(userAnswer) && !CollectionUtils.isEmpty(answerList)) {
                List<String> answers = Arrays.asList(userAnswer.split(","));
                List<QuestionAnswer> resultAnswerList = answerList.stream().filter(item -> answers.contains(item.getId())).collect(Collectors.toList());
                List<String> tagList = resultAnswerList.stream().map(QuestionAnswer::getTag).collect(Collectors.toList());
                return tagList.stream().sorted().collect(Collectors.joining(","));
            }
        }
        return null;
    }

    /**
     * 设置题目
     *
     * @param questionMap
     * @param userExamQuestionList
     */
    public void setUserExamQuestion(Map<String, Question> questionMap, List<UserExamQuestion> userExamQuestionList, boolean needRenderBlank) {
        //与问题关联
        for (UserExamQuestion userExamQuestion : userExamQuestionList) {
            if (!Objects.isNull(questionMap) && !Objects.isNull(questionMap.get(userExamQuestion.getQuestionId()))) {
                Question question = questionMap.get(userExamQuestion.getQuestionId());
                //渲染填空题的题干，填充进答案
                if (needRenderBlank && QuestionType.BLANK.getCode().equals(userExamQuestion.getQuestionType())) {
                    renderBlank(question, userExamQuestion.getAnswer());
                }
                userExamQuestion.setQuestion(question);
                userExamQuestion.setQuestionTypeName(QuestionType.getByCode(userExamQuestion.getQuestionType()).getValue());
            }
        }
    }

    /**
     * 渲染填空题的题干，填充进答案
     *
     * @param question
     */
    @Override
    public void renderBlank(Question question, String answer) {
        String content = question.getContent();
        if (StringUtils.isNotBlank(content) && StringUtils.isNotBlank(answer)) {
            List<QuestionAnswer> answerList = JSONArray.parseArray(answer, QuestionAnswer.class);
            Map<String, QuestionAnswer> answerMap = answerList.stream().collect(Collectors.toMap(QuestionAnswer::getTag, a -> a, (k1, k2) -> k1));
            for (QuestionAnswer a : answerList) {
                String fontColor = !Objects.isNull(a.getIsRight()) && a.getIsRight() ? "#00bb00" : "red";
                content = content.replaceAll("\\>" + a.getTag() + "\\<\\/span\\>", " style=\"color:" + fontColor + ";font-weight:bold\">" + answerMap.get(a.getTag()).getContent() + "</span>");
            }
        }
        question.setContent(content);
    }

    /**
     * 设置答题卡
     *
     * @param userExamQuestionList
     * @param answerCardList
     */
    public void setAnswerCard(List<UserExamQuestion> userExamQuestionList, List<Map<String, AnswerCardDTO>> answerCardList) {
        Map<Integer, List<UserExamQuestion>> map = userExamQuestionList.stream().collect(Collectors.groupingBy(UserExamQuestion::getQuestionType, LinkedHashMap::new, Collectors.toList()));
        for (Map.Entry<Integer, List<UserExamQuestion>> item : map.entrySet()) {
            AnswerCardDTO answerCard = new AnswerCardDTO();
            List<UserExamQuestion> questions = item.getValue();
            //题数和分数
            answerCard.setQuestionCount(questions.size());
            answerCard.setQuestionScore(questions.stream().mapToInt(UserExamQuestion::getQuestionScore).sum());
            //答题卡数字面板
            answerCard.setIndexList(questions.stream().map(UserExamQuestion::getQuestionIndex).collect(Collectors.toList()));
            Map<String, AnswerCardDTO> answerCardMap = new LinkedHashMap<>();
            answerCardMap.put(QuestionType.getByCode(item.getKey()).getValue(), answerCard);
            answerCardList.add(answerCardMap);
        }
    }

    /**
     * 交卷
     *
     * @param dto
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void submitExam(ExamSubmitDTO dto) {
        if (StringUtils.isEmpty(dto.getUserExamId())) {
            throw new JeecgBootException("考试ID不能为空");
        }
        UserExam userExam = userExamService.listUserExam(dto.getUserExamId());
        if (Objects.isNull(userExam)) {
            throw new JeecgBootException("考试记录不存在");
        }
        if (PaperState.FINISHED.equals(userExam.getState()) || PaperState.WAIT_OPT.equals(userExam.getState())) {
            throw new JeecgBootException("该考试已经交卷，不允许重复交卷");
        }
        List<UserExamQuestion> userExamQuestionList = userExam.getUserExamQuestionList();
        if (CollectionUtils.isEmpty(userExamQuestionList)) {
            throw new JeecgBootException("考题查询失败");
        }

        //如果没有作答
        List<ExamAnswerDTO> examAnswers = dto.getExamAnswers();
        if (CollectionUtils.isEmpty(examAnswers)) {
            updateUserExamAndResult(0, userExam);
            //没作答时需要更新客观题
            updateObjectiveQuestion(userExamQuestionList);
        } else {
            //有作答
            Map<Integer, UserExamQuestion> userExamQuestionMap = userExamQuestionList.stream().collect(Collectors.toMap(UserExamQuestion::getQuestionIndex, a -> a, (k1, k2) -> k1));

            //获取题目和答案
            Map<String, Question> questionMap = getUserExamQuestionMap(dto.getUserExamId(), userExamQuestionList, true, false, true, false, false);

            //计算客观题得分
            int score = calcObjectiveScore(examAnswers, userExamQuestionMap, questionMap, userExamQuestionList);
            //更新用户考试信息和成绩
            updateUserExamAndResult(score, userExam);
        }

        //异步删除自动交卷任务，清除redis缓存
        ThreadPoolUtil.getExecutor().execute(() -> {
            this.deleteAutoCommitJob(dto.getUserExamId());
            deleteCacheAnswer(dto.getUserExamId());
        });
    }

    /**
     * 没作答时需要更新客观题
     *
     * @param userExamQuestionList
     */
    public void updateObjectiveQuestion(List<UserExamQuestion> userExamQuestionList) {
        List<UserExamQuestion> objectiveList = userExamQuestionList.stream().filter(o -> !o.getQuestionType().equals(QuestionType.SIMPLE.getCode())).collect(Collectors.toList());
        if (!CollectionUtils.isEmpty(objectiveList)) {
            objectiveList.stream().forEach(o -> {
                o.setAnswered(false);
                o.setIsRight(false);
            });
            userExamQuestionService.updateBatchById(objectiveList);
        }
    }

    //获取题目和答案
    public Map<String, Question> getUserExamQuestionMap(String userExamId, List<UserExamQuestion> userExamQuestionList, boolean needAnswerFlag, boolean needAnalysis, boolean needPathScore, boolean needSubjective, boolean needClearBlankContent) {
        List<String> subjectiveList = null;
        if (needSubjective) {//是否需要主观题
            subjectiveList = userExamQuestionList.stream().filter(o -> o.getQuestionType().equals(QuestionType.SIMPLE.getCode())).map(UserExamQuestion::getQuestionId).collect(Collectors.toList());
        }

        //客观题
        List<String> objectiveList = userExamQuestionList.stream().filter(o -> !o.getQuestionType().equals(QuestionType.SIMPLE.getCode())).map(UserExamQuestion::getQuestionId).collect(Collectors.toList());
        List<Question> questionList = questionService.listUserExamQuestionAnswer(userExamId, objectiveList, subjectiveList, needAnswerFlag, needAnalysis, needPathScore, needSubjective);
        //题目答案重新排序
        if (!CollectionUtils.isEmpty(questionList)) {
            questionList.stream().forEach(question -> {
                List<QuestionAnswer> answerList = question.getAnswerList();
                if (!CollectionUtils.isEmpty(answerList)) {
                    if (QuestionType.BLANK.getCode().equals(question.getType()) && needClearBlankContent) {//填空题是否清空答案内容
                        answerList.stream().forEach(o -> o.setContent(null));
                    }
                    question.setAnswerList(answerList.stream().sorted(Comparator.comparing(QuestionAnswer::getSort)).collect(Collectors.toList()));
                }
            });
        }
        return questionList.stream().collect(Collectors.toMap(Question::getId, a -> a, (k1, k2) -> k1));
    }

    //获取题目和答案
    public List<Question> listQuestion(List<PaperQuestion> paperQuestionList) {
        List<String> questionIdList = paperQuestionList.stream().map(PaperQuestion::getQuestionId).collect(Collectors.toList());
        return questionService.selectQuestionList(questionIdList, true, true);
    }


    /**
     * 计算客观题得分
     *
     * @param examAnswers
     * @param userExamQuestionMap
     * @param questionMap
     * @return
     */
    public int calcObjectiveScore(List<ExamAnswerDTO> examAnswers, Map<Integer, UserExamQuestion> userExamQuestionMap, Map<String, Question> questionMap, List<UserExamQuestion> userExamQuestionList) {
        //最终得分
        int score = 0;
        if (!CollectionUtils.isEmpty(examAnswers)) {
            //对比/计算
            for (ExamAnswerDTO o : examAnswers) {
                UserExamQuestion userExamQuestion = userExamQuestionMap.get(o.getIndex());
                userExamQuestion.setAnswered(true);//已答
                //主观题（简答题）
                if (userExamQuestion.getQuestionType().equals(QuestionType.SIMPLE.getCode())) {
                    userExamQuestion.setAnswer(o.getSubjectiveAnswer());//答案内容
                    continue;
                }

                //单选，多选，判断题获取并设置答案
                String[] answers = o.getAnswers();
                if (!userExamQuestion.getQuestionType().equals(QuestionType.BLANK.getCode())) {
                    if (!Objects.isNull(answers) && answers.length > 0) {
                        userExamQuestion.setAnswer(String.join(",", answers));
                    }
                }

                //填空题获取答案
                String blankAnswer = o.getBlankAnswer();

                //提交的答案和题目答案对比
                Question question = questionMap.get(userExamQuestion.getQuestionId());
                switch (QuestionType.getByCode(question.getType())) {
                    //单选题和判断题直接答案完全匹配
                    case RADIO:
                    case JUDGE:
                        score += compareToScore(userExamQuestion, answers, question);
                        break;
                    case MULTI: //多选题
                        if (!Objects.isNull(userExamQuestion.getCanMissOption()) && userExamQuestion.getCanMissOption()) {
                            score += compareMultiToScore(userExamQuestion, answers, question);
                        } else {
                            score += compareToScore(userExamQuestion, answers, question);
                        }
                        break;
                    case BLANK: //填空题
                        score += compareBlankToScore(userExamQuestion, blankAnswer, question, userExamQuestion.getCanBlankOption());
                        break;
                }
            }

            //获取回答过的题目
            List<Integer> indexList = examAnswers.stream().map(ExamAnswerDTO::getIndex).collect(toList());
            //获取没回答的客观题题目
            List<UserExamQuestion> objectiveNoAnswerList = userExamQuestionList.stream().filter(o -> !indexList.contains(o.getQuestionIndex())).filter(o -> !o.getQuestionType().equals(QuestionType.SIMPLE.getCode())).collect(Collectors.toList());
            objectiveNoAnswerList.stream().forEach(o -> o.setIsRight(false));

            userExamQuestionService.updateBatchById(userExamQuestionList);
        }
        return score;
    }

    /**
     * 提交的答案和题目答案对比计算得分--填空题
     *
     * @return
     */
    public Integer compareBlankToScore(UserExamQuestion userExamQuestion, String userAnswerStr, Question question, Boolean canBlankOption) {
        List<QuestionAnswer> answerList = question.getAnswerList();
        //深拷贝答案
        List<QuestionAnswer> deepList = deepCopyAnswerList(answerList);

        if (StringUtils.isNotBlank(userAnswerStr) && !CollectionUtils.isEmpty(answerList)) {
            List<QuestionAnswer> userAnswerList = JSONArray.parseArray(userAnswerStr, QuestionAnswer.class);
            //过滤无效答案
            userAnswerList = userAnswerList.stream().filter(ua -> StringUtils.isNotBlank(ua.getContent())).collect(Collectors.toList());
            List<BlankAnswerResultDTO> dtoList = new ArrayList<>();
            userAnswerList.stream().forEach(userAnswer -> {
                //严格按照顺序对比
                if (!Objects.isNull(question.getAnswerOrder()) && question.getAnswerOrder()) {
                    compareByOrder(answerList, userAnswer, dtoList);
                } else {
                    compareByNoOrder(answerList, userAnswer, dtoList);
                }
            });
            if (!CollectionUtils.isEmpty(dtoList)) {
                userExamQuestion.setAnswer(JSONArray.toJSONString(dtoList));//设置填空题答案
                if (dtoList.size() != deepList.size()) { //用户只作答了题目部分空，没答的空自然就是错的了（有错误答案）
                    return calcBlankScoreWhenFalse(userExamQuestion, canBlankOption, dtoList);
                } else {
                    //题目所有的填空用户都作答了
                    //找出答错的空
                    long falseNum = dtoList.stream().filter(d -> !Objects.isNull(d.getIsRight()) && !d.getIsRight()).count();
                    if (falseNum == 0) {//全部答对了
                        userExamQuestion.setIsRight(true);
                        userExamQuestion.setActualScore(userExamQuestion.getQuestionScore());
                        return userExamQuestion.getQuestionScore();
                    } else {//有错误答案
                        return calcBlankScoreWhenFalse(userExamQuestion, canBlankOption, dtoList);
                    }
                }
            }
        }
        return 0;
    }

    /**
     * 当用户答案有错误时，填空题的算分方法
     *
     * @param userExamQuestion
     * @param canBlankOption
     * @param dtoList
     * @return
     */
    public Integer calcBlankScoreWhenFalse(UserExamQuestion userExamQuestion, Boolean canBlankOption, List<BlankAnswerResultDTO> dtoList) {
        if (!Objects.isNull(canBlankOption) && canBlankOption) {//是否允许按空得分
            if (dtoList.stream().filter(d -> !Objects.isNull(d.getIsRight()) && d.getIsRight()).count() == 0) {//全部错
                userExamQuestion.setIsRight(false);
                userExamQuestion.setActualScore(0);
                return 0;
            }
            //有答对的，部分得分
            int actualScore = sumRightAnswerScore(dtoList);
            userExamQuestion.setIsRight(true);
            userExamQuestion.setActualScore(actualScore);
            return actualScore;
        } else {
            userExamQuestion.setIsRight(false);
            userExamQuestion.setActualScore(0);
            return 0;
        }
    }

    /**
     * 正确项求和
     *
     * @param dtoList
     * @return
     */
    public int sumRightAnswerScore(List<BlankAnswerResultDTO> dtoList) {
        return dtoList.stream().filter(d -> !Objects.isNull(d.getIsRight()) && d.getIsRight() && !Objects.isNull(d.getScore())).mapToInt(BlankAnswerResultDTO::getScore).sum();
    }

    /**
     * 深拷贝答案list
     *
     * @param answerList
     * @return
     */
    public List<QuestionAnswer> deepCopyAnswerList(List<QuestionAnswer> answerList) {
        List<QuestionAnswer> deepList = new ArrayList<>();
        answerList.stream().forEach(o -> {
            try {
                deepList.add(o.clone());
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
        return deepList;
    }

    /**
     * 严格按照顺序对比
     *
     * @param answerList
     * @param userAnswer
     * @param dtoList
     */
    public void compareByOrder(List<QuestionAnswer> answerList, QuestionAnswer userAnswer, List<BlankAnswerResultDTO> dtoList) {
        List<QuestionAnswer> list = answerList.stream().filter(a -> a.getTag().equals(userAnswer.getTag())).collect(toList());
        if (!CollectionUtils.isEmpty(list)) {
            QuestionAnswer tmp = list.get(0);
            String[] answerArray = tmp.getContent().split("\\|\\|");
            if (!Objects.isNull(answerArray) && answerArray.length > 0) {
                List<String> asList = Arrays.asList(answerArray);
                if (asList.contains(userAnswer.getContent().trim())) {
                    dtoList.add(new BlankAnswerResultDTO(userAnswer.getTag(), userAnswer.getContent().trim(), true, !Objects.isNull(tmp.getPathScore()) ? tmp.getPathScore().intValue() : null));
                } else {
                    dtoList.add(new BlankAnswerResultDTO(userAnswer.getTag(), userAnswer.getContent().trim(), false, null));
                }
            }
        }
    }

    /**
     * 不按顺序对比，只要答案在其中一个选项中找到符合的就行
     *
     * @param answerList
     * @param userAnswer
     * @param dtoList
     */
    public void compareByNoOrder(List<QuestionAnswer> answerList, QuestionAnswer userAnswer, List<BlankAnswerResultDTO> dtoList) {
        boolean isTrue = false;
        Iterator<QuestionAnswer> iterator = answerList.iterator();
        while (iterator.hasNext()) {
            QuestionAnswer qa = iterator.next();
            String[] answerArray = qa.getContent().split("\\|\\|");
            if (!Objects.isNull(answerArray) && answerArray.length > 0) {
                List<String> asList = Arrays.asList(answerArray);
                if (asList.contains(userAnswer.getContent().trim())) {
                    dtoList.add(new BlankAnswerResultDTO(userAnswer.getTag(), userAnswer.getContent().trim(), true, !Objects.isNull(qa.getPathScore()) ? qa.getPathScore().intValue() : null));
                    isTrue = true;
                    iterator.remove();
                    break;
                }
            }
        }
        if (!isTrue) {
            dtoList.add(new BlankAnswerResultDTO(userAnswer.getTag(), userAnswer.getContent().trim(), false, null));
        }
    }

    /**
     * 多选题，设置题目得分
     *
     * @param userExamQuestion
     * @param answers
     * @param question
     * @return
     */
    public Integer compareMultiToScore(UserExamQuestion userExamQuestion, String[] answers, Question question) {
        Result<Integer> result = compareMultiAnswer(userExamQuestion, answers, question);
        if (Objects.isNull(result)) {
            return null;
        }
        userExamQuestion.setIsRight(result.isSuccess());
        userExamQuestion.setActualScore(result.getResult());
        return result.getResult();
    }

    /**
     * 找出正确答案
     *
     * @param answerList
     * @return
     */
    public List<QuestionAnswer> findRightList(List<QuestionAnswer> answerList) {
        return answerList.stream().filter(o -> !Objects.isNull(o.getIsRight()) && o.getIsRight()).collect(Collectors.toList());
    }

    /**
     * 多选题，答案对比计算得分
     *
     * @param answers
     * @return
     */
    public Result<Integer> compareMultiAnswer(UserExamQuestion userExamQuestion, String[] answers, Question question) {
        List<QuestionAnswer> answerList = question.getAnswerList();
        if (!Objects.isNull(answers) && answers.length > 0 && !CollectionUtils.isEmpty(answerList)) {
            //找出正确答案
            List<QuestionAnswer> rightAnswers = findRightList(answerList);
            List<String> rightAnswerIds = rightAnswers.stream().map(QuestionAnswer::getId).collect(Collectors.toList());
            List<String> userAnswerList = Arrays.asList(answers);

            //如果正确答案的个数和用户答案的个数一样，则取差集
            if (rightAnswerIds.size() == userAnswerList.size()) {
                //两个集合取差集
                //假如正确答案ABC，用户答案BCD，取差集为A
                List<String> reduceList = rightAnswerIds.stream().filter(item -> !userAnswerList.contains(item)).collect(Collectors.toList());
                if (CollectionUtils.isEmpty(reduceList)) {
                    return Result.ok(userExamQuestion.getQuestionScore());//给整题的分
                } else {
                    return Result.error("答题错误", 0);//有错误项，给0分
                }
            } else {
                //去重并集，取交集，再取差集
                //假如正确答案ABCD，用户答案BCD，先取去重并集ABCD，然后取交集为BCD，然后去重并集和交集取差集为A，A为正确答案之一，给BCD的分
                //假如正确答案ABCD，用户答案BCE，先取去重并集ABCDE，然后取交集为BC，然后去重并集和交集取差集为ADE，DE为错误答案，给0分
                //假如正确答案ABCD，用户答案BCDEF，先取去重并集ABCDEF，然后取交集为BCD，然后去重并集和交集取差集为AEF，EF为错误答案，给0分
                List<String> reduceList = getReduceList(rightAnswerIds, userAnswerList);
                if (!CollectionUtils.isEmpty(reduceList)) {
                    List<QuestionAnswer> reduceAnswers = answerList.stream().filter(o -> reduceList.contains(o.getId())).collect(Collectors.toList());
                    //如果用户给的答案中有错误答案的话，给0分
                    List<QuestionAnswer> falseList = reduceAnswers.stream().filter(o -> !Objects.isNull(o.getIsRight()) && !o.getIsRight()).collect(Collectors.toList());
                    if (!CollectionUtils.isEmpty(falseList)) {
                        return Result.error("答题错误", 0);
                    } else {
                        //漏选项单独算分
                        List<QuestionAnswer> trueList = answerList.stream().filter(o -> userAnswerList.contains(o.getId())).collect(Collectors.toList());
                        double sum = trueList.stream().filter(q -> !Objects.isNull(q.getPathScore()) && q.getPathScore() > 0).mapToDouble(QuestionAnswer::getPathScore).sum();
                        return Result.ok((int) sum);
                    }
                }
            }
        }
        return null;
    }

    /**
     * 获取正确答案和错误答案的差异项
     *
     * @param rightAnswerIds
     * @param userAnswerList
     * @return
     */
    public List<String> getReduceList(List<String> rightAnswerIds, List<String> userAnswerList) {
        // 并集
        List<String> listAll = rightAnswerIds.parallelStream().collect(toList());
        List<String> listAll2 = userAnswerList.parallelStream().collect(toList());
        listAll.addAll(listAll2);

        // 去重并集
        List<String> listAllDistinct = listAll.stream().distinct().collect(toList());

        // 交集
        List<String> intersection = rightAnswerIds.stream().filter(item -> userAnswerList.contains(item)).collect(toList());

        // 差集 (listAllDistinct - intersection)
        return listAllDistinct.stream().filter(item -> !intersection.contains(item)).collect(toList());
    }

    /**
     * 和答案对比算分
     *
     * @param userExamQuestion
     * @param answers
     * @param question
     * @return
     */
    public Integer compareToScore(UserExamQuestion userExamQuestion, String[] answers, Question question) {
        boolean succeed = compareAnswer(answers, question);
        if (succeed) {//回答正确
            userExamQuestion.setIsRight(true);
            userExamQuestion.setActualScore(userExamQuestion.getQuestionScore());
            return userExamQuestion.getQuestionScore();
        } else {
            userExamQuestion.setIsRight(false);
            userExamQuestion.setActualScore(0);
            return 0;
        }
    }

    /**
     * 判断是否有主观题
     *
     * @param userExam
     * @return
     */
    public boolean hasSubjective(UserExam userExam) {
        return !Objects.isNull(userExam.getHasSubjective()) && userExam.getHasSubjective() ? true : false;
    }

    /**
     * 更新用户考试信息和成绩
     *
     * @param score
     */
    public void updateUserExamAndResult(int score, UserExam userExam) {
        userExam.setUserScore(score);//最终得分
        userExam.setObjectiveScore(score);//客观题得分
        userExam.setCommitTime(new Date());
        userExam.setState(hasSubjective(userExam) ? PaperState.WAIT_OPT : PaperState.FINISHED);
        //计算考试时长
        int userTime = (int) ((System.currentTimeMillis() - userExam.getCreateTime().getTime()) / 1000 / 60);
        if (userTime == 0) {
            userTime = 1;
        }

        //更新考试记录
        userExam.setUserTime(userTime);
        int pass = score >= userExam.getQualifyScore() ? 1 : 0;
        userExam.setPassed(pass);

        //考试完成后加入成绩
        userExamResultService.joinResult(userExam.getUserId(), userExam.getExamId(), score, pass);

        userExamService.updateById(userExam);
    }

    /**
     * 提交的答案和题目答案对比
     *
     * @param answers
     * @return
     */
    public boolean compareAnswer(String[] answers, Question question) {
        List<QuestionAnswer> answerList = question.getAnswerList();
        if (!Objects.isNull(answers) && answers.length > 0 && !CollectionUtils.isEmpty(answerList)) {
            //找出正确答案
            List<QuestionAnswer> rightAnswers = findRightList(answerList);
            List<String> rightAnswerIds = rightAnswers.stream().map(QuestionAnswer::getId).collect(Collectors.toList());
            List<String> userAnswerList = Arrays.asList(answers);

            //如果正确答案的个数和用户答案的个数一样，则取差集
            if (rightAnswerIds.size() == userAnswerList.size()) {
                //两个集合取差集
                List<String> reduceList = rightAnswerIds.stream().filter(item -> !userAnswerList.contains(item)).collect(Collectors.toList());
                if (CollectionUtils.isEmpty(reduceList)) {
                    return true;
                }
                return false;
            }
            return false;
        }
        return false;
    }


    /**
     * 缓存考试答案，用于定时任务强制交卷
     *
     * @param dto
     */
    @Override
    public void cacheExamAnswer(ExamSubmitDTO dto) {
        if (StringUtils.isNotBlank(dto.getUserExamId()) && !Objects.isNull(dto.getLimitTime())) {
            //缓存答案的时间为截止时间后3天
            long expireMins = DateUtil.between(new Date(), dto.getLimitTime(), DateUnit.MINUTE) + 60 * 24 * 3;
            long expireSec = expireMins * 60;
            redisUtil.set(dto.getUserExamId(), JSON.toJSONString(dto), expireSec);
        }
    }


    /**
     * 获取正在进行中的考试
     *
     * @return
     */
    @Override
    public List<String> listExamIng() {
        return examMapper.listExamIng();
    }


    /**
     * 删除自动交卷任务
     *
     * @param parameter
     */
    @Override
    public void deleteAutoCommitJob(String parameter) {
        //删除定时任务
        List<QuartzJob> jobList = quartzJobService.findByCondition(BreakExamJob.class.getName(), parameter);
        if (!CollectionUtils.isEmpty(jobList)) {
            jobList.stream().forEach(quartzJob -> quartzJobService.deleteAndStopJob(quartzJob));
        }
    }

    /**
     * 检查试卷是否已经被用于考试
     *
     * @param paperId
     * @return
     */
    @Override
    public boolean checkIsUsedInExam(String paperId) {
        LambdaQueryWrapper<Exam> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(Exam::getPaperId, paperId);
        return this.count(wrapper) > 0 ? true : false;
    }

    /**
     * 获取缓存的考试答案
     *
     * @return
     */
    @Override
    public ExamSubmitDTO getCacheAnswer(String userExamId) {
        Object answerObject = redisUtil.get(userExamId);
        if (!Objects.isNull(answerObject)) {
            return JSON.parseObject(JSON.parse(String.valueOf(answerObject)).toString(), ExamSubmitDTO.class);
        }
        return null;
    }

    /**
     * 清除缓存的考试答案
     *
     * @return
     */
    @Override
    public void deleteCacheAnswer(String userExamId) {
        redisUtil.del(userExamId);
    }

    /**
     * 检查是否达到考试限制次数
     *
     * @param userId
     * @param examId
     * @return
     */
    @Override
    public Boolean checkToLimit(String userId, String examId) {
        Exam exam = getById(examId);
        if (!Objects.isNull(exam.getLimitCount())) {
            long userExamNum = userExamService.countUserExam(userId, examId);
            if (userExamNum >= exam.getLimitCount()) { //考试次数达到上限
                return true;
            }
        }
        return false;
    }

    /**
     * 检查是否有考试记录
     *
     * @param examId
     * @return
     */
    @Override
    public Boolean examRecordExist(String userId, String examId) {
        long userExamNum = userExamService.countUserExam(userId, examId);
        return userExamNum > 0;
    }
}
