package com.ruoyi.web.service;

import com.alibaba.fastjson2.JSONObject;
import com.alibaba.fastjson2.util.DateUtils;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.ruoyi.common.core.content.VistorContextHolder;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.enums.ExamType;
import com.ruoyi.common.enums.UserRegStatus;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.dz.service.IDzControlService;
import com.ruoyi.dz.service.IDzSubjectService;
import com.ruoyi.enums.*;
import com.ruoyi.ie.domain.AMarjorPlan;
import com.ruoyi.ie.service.IAMarjorPlanService;
import com.ruoyi.learn.domain.*;
import com.ruoyi.learn.mapper.*;
import com.ruoyi.learn.service.ILearnPaperService;
import com.ruoyi.learn.service.ILearnPlanService;
import com.ruoyi.mxjb.domain.MxjbContants;
import com.ruoyi.system.service.ISysUserService;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.validation.ValidationException;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 考试服务
 */
@Service
public class ExamService {
    private final LearnAnswerMapper learnAnswerMapper;
    private final LearnExamineeMapper learnExamineeMapper;
    private final LearnStudentMapper learnStudentMapper;
    private final ILearnPlanService learnPlanService;
    private final LearnQuestionsMapper learnQuestionsMapper;
    private final IDzSubjectService dzSubjectService;
    private final LearnKnowledgeCourseMapper learnKnowledgeCourseMapper;
    private final LearnTestStudentMapper learnTestStudentMapper;
    private Set<PaperType> paperTypeSet = Sets.newHashSet(PaperType.Real, PaperType.Custom);
    private final LearnPaperMapper paperMapper;
    private final LearnKnowledgeTreeMapper knowledgeTreeMapper;
    private final LearnExamineeMapper examineeMapper;
    private final LearnWrongBookMapper wrongBookMapper;
    private final LearnWrongDetailMapper wrongDetailMapper;
    private final ILearnPaperService learnPaperService;
    private final PaperService paperService;
    private final IAMarjorPlanService marjorPlanService;
    private final ISysUserService sysUserService;
    private final IDzControlService dzControlService;

    public ExamService(LearnPaperMapper paperMapper, LearnKnowledgeTreeMapper knowledgeTreeMapper, LearnExamineeMapper examineeMapper, ILearnPaperService learnPaperService, PaperService paperService, IAMarjorPlanService marjorPlanService, LearnAnswerMapper learnAnswerMapper, LearnExamineeMapper learnExamineeMapper, ISysUserService sysUserService, LearnStudentMapper learnStudentMapper, ILearnPlanService learnPlanService, LearnQuestionsMapper learnQuestionsMapper, IDzSubjectService dzSubjectService, LearnWrongBookMapper wrongBookMapper, LearnWrongDetailMapper wrongDetailMapper, LearnKnowledgeCourseMapper learnKnowledgeCourseMapper, LearnTestStudentMapper learnTestStudentMapper, IDzControlService dzControlService) {
        this.paperMapper = paperMapper;
        this.knowledgeTreeMapper = knowledgeTreeMapper;
        this.examineeMapper = examineeMapper;
        this.learnPaperService = learnPaperService;
        this.paperService = paperService;
        this.marjorPlanService = marjorPlanService;
        this.learnAnswerMapper = learnAnswerMapper;
        this.learnExamineeMapper = learnExamineeMapper;
        this.sysUserService = sysUserService;
        this.learnStudentMapper = learnStudentMapper;
        this.learnPlanService = learnPlanService;
        this.learnQuestionsMapper = learnQuestionsMapper;
        this.dzSubjectService = dzSubjectService;
        this.wrongBookMapper = wrongBookMapper;
        this.wrongDetailMapper = wrongDetailMapper;
        this.learnKnowledgeCourseMapper = learnKnowledgeCourseMapper;
        this.learnTestStudentMapper = learnTestStudentMapper;
        this.dzControlService = dzControlService;
    }

    /**
     * 开卷 (卷, 实时组卷)
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    public AnswerSheet openExaminee(boolean directed, PaperType paperType, Long relatedId, Long subjectId, Integer questionType) {
        SysUser user = SecurityUtils.getLoginUser().getUser();
        if(paperTypeSet.contains(paperType)) {
            if(UserTypeEnum.isCard(user.getUserType()) && !UserRegStatus.Student.equals(user.getRegStatus())) {
                throw new RuntimeException("VIP功能不可用");
            }
            return openExaminee(paperType, relatedId, SecurityUtils.getUserId());
        } else if(PaperType.Practice.equals(paperType)) {
            if(UserTypeEnum.isCard(user.getUserType()) && !UserRegStatus.Student.equals(user.getRegStatus())) {
                throw new RuntimeException("VIP功能不可用");
            }
            return openExamineeForPractice(relatedId, SecurityUtils.getUserId(), directed, questionType);
        } else if(PaperType.Course.equals(paperType)) {
            if(UserTypeEnum.isCard(user.getUserType()) && !UserRegStatus.Student.equals(user.getRegStatus())) {
                throw new RuntimeException("VIP功能不可用");
            }
            return openExamineeForCourse(relatedId, SecurityUtils.getUserId(), directed);
        } else if(PaperType.Simulated.equals(paperType)) {
            if(null == relatedId) { // 直接打开Paper的模拟卷
                SysUser exist = sysUserService.selectUserById(SecurityUtils.getUserId());
                LearnStudent ls = learnStudentMapper.selectLearnStudentByStudentId(exist.getUserId());
                if(null == ls) {
                    throw new RuntimeException("请先定设置专业学习" + exist.getUserId());
                }
                return openExaminee(ls, subjectId, exist);
            }
            return openExamineeByPaper(relatedId);
        } else if(PaperType.Test.equals(paperType)) {
            SysUser exist = sysUserService.selectUserById(SecurityUtils.getUserId());
            LearnTestStudent ts = learnTestStudentMapper.selectLearnTestStudentById(relatedId);
            if(null == ts || !ts.getStudentId().equals(exist.getUserId())) {
                throw new RuntimeException("无效批次卷" + relatedId);
            }
            return openExamineeForTest(ts, exist);
        }
        throw new RuntimeException("错误类型: " + paperType);
    }

    public AnswerSheet loadExaminee(Long examineeId, Boolean examContinue) {
        LearnExaminee examinee = examineeMapper.selectLearnExamineeByExamineeId(examineeId);
        if(examContinue) {
            if(ExamineeStatus.Sign.getVal().equals(examinee.getState())) {
                LearnExaminee upExaminee = new LearnExaminee();
                upExaminee.setExamineeId(examineeId);
                upExaminee.setBeginTime(new Date());
                upExaminee.setState(ExamineeStatus.Exam.getVal());
                examineeMapper.updateLearnExaminee(upExaminee);
            } else if(!ExamineeStatus.Exam.getVal().equals(examinee.getState())) {
                throw new RuntimeException("已考完的卷不能继续做");
            }
        } else if(ExamineeStatus.Exam.getVal().equals(examinee.getState())) {
            throw new RuntimeException("考试中，不能提取答案");
        }
        LearnPaper learnPaper = learnPaperService.selectLearnPaperById(examinee.getPaperId());
        AnswerSheet answerSheet = buildAnswerSheet(learnPaper, examinee, loadQuestionAnswers(examinee, examContinue));
        answerSheet.setTotalCount(learnPaper.getNumber());
        answerSheet.setWrongCount(examinee.getWrongCount());

        if(null != examinee.getPaperInfo()) {
            JSONObject info = JSONObject.parseObject(examinee.getPaperInfo());
            answerSheet.setCollegeId(info.getLong("universityId"));
            answerSheet.setCollegeName(info.getString("universityName"));
            answerSheet.setMajorId(info.getLong("planId"));
            answerSheet.setMajorName(info.getString("majorName"));
            answerSheet.setSubjectId(learnPaper.getSubjectId());
            answerSheet.setSubjectName(dzSubjectService.selectDzSubjectBySubjectId(learnPaper.getSubjectId()).getSubjectName());
        }
        if(null != examinee.getStats()) {
            answerSheet.setStats(JSONObject.parseObject(examinee.getStats()));
        }
        return answerSheet;
    }

    private List<PaperVO.QuestionAnswer> loadQuestionAnswers(LearnExaminee examinee, boolean examContinue) {
        LearnAnswer aCond = new LearnAnswer();
        aCond.setExamineeId(examinee.getExamineeId());
        Map<Long, LearnAnswer> answerMap = learnAnswerMapper.selectLearnAnswerList(aCond).stream().collect(Collectors.toMap(LearnAnswer::getQuestionId, t -> t));
        List<PaperVO.QuestionAnswer> questionList = paperService.loadPaperQuestionAnswers(SecurityUtils.getUserId(), examinee.getPaperId(), answerMap, !examContinue);
        return questionList;
    }

    /**
     * 交卷
     * @param answerSheet
     */
    @Transactional(rollbackFor = Exception.class)
    public void commitAnswerSheet(AnswerSheet answerSheet) {
        LearnExaminee exitExaminee = learnExamineeMapper.selectLearnExamineeByExamineeId(answerSheet.getExamineeId());
        if(null == exitExaminee) {
            throw new RuntimeException("无此答卷" + answerSheet.getExamineeId());
        }
        LearnAnswer aCond = new LearnAnswer();
        aCond.setExamineeId(answerSheet.getExamineeId());
        Map<Long, LearnAnswer> answerMap = learnAnswerMapper.selectLearnAnswerList(aCond).stream().collect(Collectors.toMap(LearnAnswer::getQuestionId, t -> t));
        List<PaperVO.QuestionAnswer> qaList = paperService.loadPaperQuestionAnswers(null, exitExaminee.getPaperId(), answerMap, true);
        Map<Long, PaperVO.QuestionAnswer> questionMap = Maps.newHashMap();
        for(PaperVO.QuestionAnswer qa : qaList) {
            if(CollectionUtils.isNotEmpty(qa.getSubQuestions())) {
                for(PaperVO.QuestionAnswer sqa : qa.getSubQuestions()) {
                    questionMap.put(sqa.getId(), sqa);
                }
            } else {
                questionMap.put(qa.getId(), qa);
            }
        }

        List<LearnAnswer> answersList = Lists.newArrayList();
        Integer wrongCount = 0;
        Integer totalScore = 0;
        Integer score = 0;
        for(PaperVO.QuestionAnswer question : answerSheet.getQuestions()) {
            if(CollectionUtils.isNotEmpty(question.getSubQuestions())) {
                for(PaperVO.QuestionAnswer sq : question.getSubQuestions()) {
                    PaperVO.QuestionAnswer stdAnswer = questionMap.get(sq.getId());
                    LearnAnswer answer = buildAnswer(answerSheet, questionMap, sq, answersList);
                    if(answerSheet.getIsDone()) {
                        totalScore += stdAnswer.getTotalScore();
                        score += answer.getScore();
                        if(!answer.getState().equals(1)) {
                            wrongCount++;
                        }
                    }
                }
            } else {
                PaperVO.QuestionAnswer stdAnswer = questionMap.get(question.getId());
                LearnAnswer answer = buildAnswer(answerSheet, questionMap, question, answersList);
                if(answerSheet.getIsDone()) {
                    totalScore += stdAnswer.getTotalScore();
                    score += answer.getScore();
                    if(!answer.getState().equals(1)) {
                        wrongCount++;
                    }
                }
            }
        }
        if(answersList.size() > 0) {
            answersList.stream().forEach(t -> {
                if(null == t.getAnswerId()) {
                    learnAnswerMapper.insertLearnAnswer(t);
                } else {
                    learnAnswerMapper.updateLearnAnswer(t);
                }
            });
        }
        LearnExaminee upExaminee = new LearnExaminee();
        upExaminee.setExamineeId(answerSheet.getExamineeId());
        upExaminee.setDuration(answerSheet.getDuration());
        if(answerSheet.getIsDone()) {
            upExaminee.setEndTime(new Date());
            upExaminee.setState(ExamineeStatus.Commit.getVal());
            upExaminee.setScore(score);
            upExaminee.setScoreRate(score * 100 / totalScore);
            upExaminee.setWrongCount(wrongCount);
            learnExamineeMapper.updateLearnExaminee(upExaminee);
            if(PaperType.Simulated.getVal().equals(exitExaminee.getPaperType()) || PaperType.Test.getVal().equals(exitExaminee.getPaperType())) {
                List<JSONObject> paperStatList = learnExamineeMapper.selectExamRankingStats(exitExaminee.getPaperType(), upExaminee.getScore(), Lists.newArrayList(exitExaminee.getPaperId()));
                JSONObject o = paperStatList.get(0);
                Integer totalCount = o.getInteger("totalCount");
                Integer maxScore = o.getInteger("maxScore");
                Integer avgScore = o.getInteger("avgScore");
                Integer lowCount = o.getInteger("lowCount");
                JSONObject stats = new JSONObject();
                stats.put("score", upExaminee.getScore());
                stats.put("rate", upExaminee.getScoreRate());
                stats.put("maxScore", maxScore);
                stats.put("averageScore", avgScore);
                stats.put("hitRate", lowCount * 100 / totalCount);
                upExaminee.setStats(JSONObject.toJSONString(stats));
                learnExamineeMapper.updateLearnExaminee(upExaminee);
            }
        } else {
            learnExamineeMapper.updateLearnExaminee(upExaminee);
        }

        if(answerSheet.getIsDone()) {
            // 关闭试卷
            LearnPaper up = new LearnPaper();
            up.setId(exitExaminee.getPaperId());
            up.setStatus(PaperStatus.Valid.getVal());
            paperMapper.updateLearnPaper(up);

            if(PaperType.Practice.getVal().equals(exitExaminee.getPaperType())) {
                learnPlanService.updateLearnPlan(exitExaminee.getStudentId());
                calculateWrongAnswers(exitExaminee, questionMap, answersList, PaperType.Practice.name());
            } else if(PaperType.Course.getVal().equals(exitExaminee.getPaperType())) {
                learnPlanService.updateLearnPlan(exitExaminee.getStudentId());
                calculateWrongAnswers(exitExaminee, questionMap, answersList, PaperType.Course.name());
            } else if(PaperType.Simulated.getVal().equals(exitExaminee.getPaperType())) {
                calculateWrongAnswers(exitExaminee, questionMap, answersList, PaperType.Simulated.name());
            } else if(PaperType.Test.getVal().equals(exitExaminee.getPaperType())) {
                calculateWrongAnswers(exitExaminee, questionMap, answersList, PaperType.Test.name());
                LearnTestStudent uts = new LearnTestStudent();
                uts.setExamineeId(answerSheet.getExamineeId());
                uts.setStatus(upExaminee.getState());
                learnTestStudentMapper.updateExamineeStatus(uts);
            }

        }
    }

    public Set<Integer> invalidStatusSet = Sets.newHashSet(2, 3);
    public void  calculateWrongAnswers(LearnExaminee examinee, Map<Long, PaperVO.QuestionAnswer> questionMap, List<LearnAnswer> answersList, String source) {
        if (CollectionUtils.isEmpty(answersList)){
            return;
        }
        LearnPaper learnPaper = paperMapper.selectLearnPaperById(examinee.getPaperId());
        answersList.forEach(t -> {
            if (invalidStatusSet.contains(t.getState())) {
                LearnWrongBook wrongBookCond = new LearnWrongBook();
                wrongBookCond.setStudentId(examinee.getStudentId());
                wrongBookCond.setQuestionId(t.getQuestionId());
                PaperVO.QuestionAnswer stdAnswer = questionMap.get(t.getQuestionId());
                wrongBookCond.setKnownledgeId(stdAnswer.getKnowledgeId());
                List<LearnWrongBook> wrongBookList = wrongBookMapper.selectLearnWrongBookList(wrongBookCond);
                if (CollectionUtils.isNotEmpty(wrongBookList)) {
                    LearnWrongBook wrongBook = wrongBookList.get(0);
                    if (null == wrongBook.getWrongCount()) {
                        wrongBook.setWrongCount(0L);
                    }
                    if (null == wrongBook.getTotalCount()) {
                        wrongBook.setTotalCount(0L);
                    }
                    wrongBookCond.setWrongId(wrongBook.getWrongId());
                    wrongBookCond.setStudentId(null);
                    wrongBookCond.setQuestionId(null);
                    wrongBookCond.setWrongCount(wrongBook.getWrongCount() + 1);
                    wrongBookCond.setTotalCount(wrongBook.getTotalCount() + 1);
                } else {
                    wrongBookCond.setAnswer1(stdAnswer.getAnswer1());
                    wrongBookCond.setAnswer2(stdAnswer.getAnswer2());
                    wrongBookCond.setSubjectId(learnPaper.getSubjectId());
                    wrongBookCond.setRightCount(0L);
                    wrongBookCond.setWrongCount(1L);
                    wrongBookCond.setTotalCount(1L);
                    // wrongBookCond.setScoreTotal(standard.getScoreTotal());
                }
                // wrongBookCond.setKnownledgeId(standard.getKnowledgeId());
                wrongBookCond.setPaperId(examinee.getPaperId());
                wrongBookCond.setScore(t.getScore());
                wrongBookCond.setSource(source);
                wrongBookCond.setAnswer(t.getAnswer());
                wrongBookCond.setState(MxjbContants.QuestionScoreWrong);
                if(null == wrongBookCond.getWrongId()) {
                    wrongBookMapper.insertLearnWrongBook(wrongBookCond);
                } else {
                    wrongBookMapper.updateLearnWrongBook(wrongBookCond);
                }
                LearnWrongDetail wrongDetail = new LearnWrongDetail();
                wrongDetail.setWrongId(wrongBookCond.getWrongId());
                wrongDetail.setCreatedTime(new Date());
                wrongDetail.setKnownledgeId(stdAnswer.getKnowledgeId());
                wrongDetail.setSource(wrongBookCond.getSource());
                wrongDetail.setExamineeId(examinee.getExamineeId());
                wrongDetail.setStudentId(examinee.getStudentId());
                wrongDetail.setPaperId(examinee.getPaperId());
                wrongDetailMapper.insertLearnWrongDetail(wrongDetail);
            }
        });
    }

    private LearnAnswer buildAnswer(AnswerSheet answerSheet, Map<Long, PaperVO.QuestionAnswer> questionMap, PaperVO.QuestionAnswer question,
                             List<LearnAnswer> answerList) {
        LearnAnswer answer = new LearnAnswer();
        PaperVO.QuestionAnswer stdAnswer = questionMap.get(question.getId());
        if(null == stdAnswer.getAnswerId()) {
            answer.setExamineeId(answerSheet.getExamineeId());
            answer.setStudentId(SecurityUtils.getUserId());
            answer.setSeq(question.getSeq());
        } else {
            answer.setAnswerId(stdAnswer.getAnswerId());
        }
        answerList.add(answer);
        answer.setQuestionId(question.getId()); // 错题计算要使用
        answer.setKnowledgeId(stdAnswer.getKnowledgeId());
        answer.setAnswer(StringUtils.join(question.getAnswers(), ","));
        answer.setMark(null != question.getIsMark() && question.getIsMark());
        answer.setNotKnow(null != question.getIsNotKnow() && question.getIsNotKnow());
        answer.setState(question.calcState(stdAnswer, answerSheet.getIsDone()));
        answer.setDuration(question.getDuration());
        if (answerSheet.getIsDone()) {
            answer.setScore(answer.getState() == 1 ? stdAnswer.getTotalScore() : 0);
            answer.setScoreRate(answer.getScore() * 100 / stdAnswer.getTotalScore());
        }
        return answer;
    }

    private AnswerSheet openExamineeForTest(PaperType paperType, Long paperId, LearnTestStudent ts) {
        LearnPaper paper = paperMapper.selectLearnPaperById(paperId);

        LearnExaminee learnExaminee = new LearnExaminee();
        learnExaminee.setStudentId(ts.getStudentId());
        learnExaminee.setPaperId(paperId);
        learnExaminee.setPaperType(paperType.getVal());
        learnExaminee.setState(ExamineeStatus.Exam.getVal());
        List<LearnExaminee> examineeList = examineeMapper.selectLearnExamineeList(learnExaminee);

        List<PaperVO.QuestionAnswer> questionAnswerList = null;
        if (CollectionUtils.isNotEmpty(examineeList)) {
            learnExaminee = examineeList.get(0);
            questionAnswerList = loadQuestionAnswers(learnExaminee, true);
        } else {
            if(StringUtils.isNotBlank(ts.getDirectKey())) { // 全量，只分科目
                LearnStudent ls = learnStudentMapper.selectLearnStudentByStudentId(ts.getStudentId());
                AMarjorPlan plan = marjorPlanService.selectAMarjorPlanById(ls.getMajorPlanId());
                learnExaminee.setPaperInfo(buildPaperInfo(ls, plan));
            }
            learnExaminee.setState(ExamineeStatus.Exam.getVal());
            learnExaminee.setBeginTime(new Date());
            learnExaminee.setDuration(0L);
            examineeMapper.insertLearnExaminee(learnExaminee);
        }
        return buildAnswerSheet(paper, learnExaminee, questionAnswerList);
    }

    private AnswerSheet openExaminee(PaperType paperType, Long paperId, Long studentId) {
        LearnPaper paper = paperMapper.selectLearnPaperById(paperId);

        LearnExaminee learnExaminee = new LearnExaminee();
        learnExaminee.setStudentId(studentId);
        learnExaminee.setPaperId(paperId);
        learnExaminee.setPaperType(paperType.getVal());
        learnExaminee.setState(ExamineeStatus.Exam.getVal());
        List<LearnExaminee> examineeList = examineeMapper.selectLearnExamineeList(learnExaminee);
        List<PaperVO.QuestionAnswer> questionAnswerList = null;
        if (CollectionUtils.isNotEmpty(examineeList)) {
            learnExaminee = examineeList.get(0);
            questionAnswerList = loadQuestionAnswers(learnExaminee, true);
        } else {
            learnExaminee.setState(ExamineeStatus.Exam.getVal());
            learnExaminee.setBeginTime(new Date());
            learnExaminee.setDuration(0L);
            examineeMapper.insertLearnExaminee(learnExaminee);
        }
        return buildAnswerSheet(paper, learnExaminee, questionAnswerList);
    }
    @Transactional
    public AnswerSheet openExamineeForTest(LearnTestStudent ts, SysUser sysUser) {
        if(null != ts.getExamineeId()) {
            return loadExaminee(ts.getExamineeId(), true);
        }
        AnswerSheet answerSheet = openExamineeForTest(PaperType.Test, ts.getPaperId(), ts);

        LearnTestStudent uts = new LearnTestStudent();
        uts.setId(ts.getId());
        uts.setExamineeId(answerSheet.getExamineeId());
        uts.setStatus(answerSheet.getState());
        learnTestStudentMapper.updateLearnTestStudent(uts);
        return answerSheet;
    }

    private AnswerSheet openExaminee(LearnStudent ls, Long subjectId, SysUser sysUser) {
//        if(StringUtils.isNotBlank(ls.getModules()) && ls.getModules().equals("技能展示/实践操作")) {
//            throw new ValidationException("该专业只考技能展示/实践操作");
//        }
        AMarjorPlan plan = marjorPlanService.selectAMarjorPlanById(ls.getMajorPlanId());
        if(null == plan || !plan.getMatchYear().equals(dzControlService.selectDzControl(VistorContextHolder.getContext()).getPlanYear())) {
            throw new ValidationException("定向没设置或过期，请先重新选择定向");
        }
        LearnExaminee examinee = new LearnExaminee();
        examinee.setStudentId(SecurityUtils.getLoginUser().getUser().getUserId());
        examinee.setPaperType(PaperType.Simulated.getVal());
        examinee.setPaperKey(subjectId.toString());
        List<LearnExaminee> examineeList = examineeMapper.selectLearnExamineeList(examinee);
        Set<Long> existPaperIdSet = Sets.newHashSet();
        for(LearnExaminee e : examineeList) {
            if(ExamineeStatus.Sign.getVal().equals(e.getState())) {
                LearnPaper learnPaper = paperMapper.selectLearnPaperById(e.getPaperId());
                return buildAnswerSheet(learnPaper, e, loadQuestionAnswers(e, true));
            }
            existPaperIdSet.add(e.getPaperId());
        }
        LearnPaper paper = getBestPaper(sysUser.getExamType(), plan, subjectId, existPaperIdSet); // getAvailPaper(sysUser.getExamType(), ls, subjectId, existPaperIdSet)

        JSONObject evalCountObj = JSONObject.parseObject(sysUser.getEvalCounts());
        Integer evalCount = evalCountObj.getInteger(subjectId.toString());
        if(null == evalCount || evalCount <= 0) {
            throw new ValidationException("超过最大次数限制" + evalCount );
        }
        SysUser upUser = new SysUser();
        upUser.setUserId(sysUser.getUserId());
        evalCountObj.put(subjectId.toString(), evalCount - 1);
        upUser.setEvalCounts(evalCountObj.toJSONString());
        sysUserService.updateUserProfile(upUser);

        examinee.setPaperInfo(buildPaperInfo(ls, plan));
        AnswerSheet answerSheet = openNewExamineeByPaper(examinee, paper);
        answerSheet.setCollegeId(ls.getUniversityId());
        answerSheet.setCollegeName(plan.getUniversityName());
        answerSheet.setMajorId(ls.getMajorPlanId());
        answerSheet.setMajorName(plan.getMajorName());
        return answerSheet;
    }
    private AnswerSheet openExamineeByPaper(Long paperId) {
        LearnPaper learnPaper = paperMapper.selectLearnPaperById(paperId);

        LearnExaminee examinee = new LearnExaminee();
        examinee.setStudentId(SecurityUtils.getLoginUser().getUser().getUserId());
        examinee.setPaperType(PaperType.Simulated.getVal());
        examinee.setPaperKey("P" + "_" + paperId + "_" + learnPaper.getSubjectId());
        List<LearnExaminee> examineeList = examineeMapper.selectLearnExamineeList(examinee);
        if(CollectionUtils.isNotEmpty(examineeList)) {
            LearnExaminee exist = examineeList.get(0);
            if(exist.getState() >= ExamineeStatus.Commit.getVal()) {
                throw new ValidationException("试卷不能重复打开做, 请查问答卷");
            }
            return buildAnswerSheet(learnPaper, exist, loadQuestionAnswers(exist, true));
        }
        return openNewExamineeByPaper(examinee, learnPaper);
    }
    private AnswerSheet openNewExamineeByPaper(LearnExaminee examinee, LearnPaper paper) {
        examinee.setPaperId(paper.getId());
        examinee.setState(ExamineeStatus.Sign.getVal());
        examinee.setDuration(0L);
        if(StringUtils.isBlank(examinee.getPaperInfo())) { // 定向有自己的专业信息，否则使用卷名称
            JSONObject info = new JSONObject();
            info.put("paperName", paper.getPaperName());
            examinee.setPaperInfo(info.toJSONString());
        }
        examineeMapper.insertLearnExaminee(examinee);
        AnswerSheet answerSheet = buildAnswerSheet(paper, examinee, null);
        answerSheet.setSubjectId(paper.getSubjectId());
        answerSheet.setSubjectName(dzSubjectService.selectDzSubjectBySubjectId(paper.getSubjectId()).getSubjectName());
        return answerSheet;

    }

    private String buildPaperInfo(LearnStudent ls, AMarjorPlan plan) {
        JSONObject info = new JSONObject();
        info.put("universityId", ls.getUniversityId());
        info.put("universityName", plan.getUniversityName());
        info.put("planId", ls.getMajorPlanId());
        info.put("majorName", plan.getMajorName());
        return info.toJSONString();
    }

    private AnswerSheet openExamineeForCourse(Long knowledgeId, Long studentId, Boolean directed) {
        LearnKnowledgeCourse kc = learnKnowledgeCourseMapper.selectLearnKnowledgeCourseById(knowledgeId);
        if(null == kc || StringUtils.isBlank(kc.getKnowledges())) {
            throw new RuntimeException("无效同步知识点");
        }
        return openExaminee(PaperType.Course, knowledgeId, Stream.of(kc.getKnowledges().split(",")).map(Long::parseLong).collect(Collectors.toList()), studentId, false,
                0L, kc.getName(), null, null, "", 0) ;
    }

    private AnswerSheet openExamineeForPractice(Long knowledgeId, Long studentId, Boolean directed, Integer questionType) {
        String directKey;
        AMarjorPlan plan = null;
        LearnStudent ls = null;
        if(!directed) {
            directKey = "";
        } else {
            ls = learnStudentMapper.selectLearnStudentByStudentId(SecurityUtils.getUserId());
            if(null == (plan = marjorPlanService.selectAMarjorPlanById(ls.getMajorPlanId()))) {
                throw new ValidationException("专业id无效");
            }
            directKey = StringUtils.trimToEmpty(ls.getDirectKey());
        }
        LearnKnowledgeTree knowledgeTree = knowledgeTreeMapper.selectLearnKnowledgeTreeById(knowledgeId);
        return openExaminee(PaperType.Practice, knowledgeId, Lists.newArrayList(knowledgeId), studentId, directed,
                knowledgeTree.getSubjectId(), knowledgeTree.getName(), ls, plan, directKey, questionType) ;
    }

    /**
     * 根据知识点生成一次性练习卷并开始做题
     * @return
     */
    private AnswerSheet openExaminee(PaperType paperType, Long knowledgeId, List<Long> useKnowledgeIds, Long studentId, Boolean directed,
                                     Long subjectId, String knowledName, LearnStudent ls, AMarjorPlan plan, String directKey, Integer questionType) {
        LearnPaper paper = new LearnPaper();
        paper.setPaperType(paperType.name());
        paper.setRelateId(knowledgeId);
        paper.setYear(Calendar.getInstance().get(Calendar.YEAR));
        paper.setStatus(PaperStatus.TmpValid.getVal());
        paper.setDirectKey(studentId + "_" + directKey + "_" + (null == questionType ? "0" : questionType));
        List<LearnPaper> paperList = paperMapper.selectLearnPaperList(paper);
        if (CollectionUtils.isNotEmpty(paperList)) { // 有未做完的
            LearnPaper existPaper = paperList.get(0);
            LearnExaminee learnExaminee = new LearnExaminee();
            learnExaminee.setStudentId(studentId);
            learnExaminee.setPaperId(existPaper.getId());
            learnExaminee.setPaperType(paperType.getVal());
            learnExaminee.setState(ExamineeStatus.Exam.getVal());
            List<LearnExaminee> examineeList = examineeMapper.selectLearnExamineeList(learnExaminee);
            if (CollectionUtils.isNotEmpty(examineeList) && ExamineeStatus.Exam.getVal().equals(examineeList.get(0).getState())) {
                LearnExaminee examinee = examineeList.get(0);
                AnswerSheet answerSheet = buildAnswerSheet(existPaper, examinee, loadQuestionAnswers(examinee, false));
                answerSheet.setKnowledgeId(existPaper.getRelateId());
                return answerSheet;
            }
            // 关闭试卷
            LearnPaper up = new LearnPaper();
            up.setId(learnExaminee.getPaperId());
            up.setStatus(PaperStatus.Valid.getVal());
            paperMapper.updateLearnPaper(up);
        }
        List<String> types = (null == questionType || questionType.equals(0))
                ? Arrays.stream(QuestionType.values()).map(QuestionType::getTitle).collect(Collectors.toList())
                : Lists.newArrayList(QuestionType.Single.getTitle(), QuestionType.Judgment.getTitle());
        List<LearnPaperQuestion> pqList = paperService.getQuestionsByRandom(studentId,  15, useKnowledgeIds, types, 0);
        if(CollectionUtils.isEmpty(pqList)) {
            throw new RuntimeException("本知识点题已做完");
        }
        paper.setPaperSource(0);
        paper.setFenshu(0);
        paper.setSubjectId(subjectId);
        paper.setPaperName(studentId + "-" + knowledName + "-" + DateUtils.format(new Date(), "yyyyMMddHHmmss"));
        paper.setRelateId(knowledgeId);
        paperService.savePaper(paper, pqList);

        LearnExaminee learnExaminee = new LearnExaminee();
        learnExaminee.setStudentId(studentId);
        learnExaminee.setPaperType(paperType.getVal());
        learnExaminee.setPaperId(paper.getId());
        learnExaminee.setPaperKey(String.valueOf(knowledgeId));
        learnExaminee.setState(ExamineeStatus.Exam.getVal());
        learnExaminee.setBeginTime(new Date());
        learnExaminee.setDuration(0L);
        if(directed && null != ls.getMajorPlanId()) {
            JSONObject info = new JSONObject();
            info.put("universityId", ls.getUniversityId());
            info.put("universityName", plan.getUniversityName());
            info.put("planId", ls.getMajorPlanId());
            info.put("majorName", plan.getMajorName());
            learnExaminee.setPaperInfo(info.toJSONString());
        }
        examineeMapper.insertLearnExaminee(learnExaminee);

        AnswerSheet answerSheet = buildAnswerSheet(paper, learnExaminee, null);
        List<PaperVO.QuestionAnswer> questionList = paperService.loadPaperQuestionAnswers(SecurityUtils.getUserId(), paper.getId(), null, true);
        answerSheet.setQuestions(questionList);
        answerSheet.setKnowledgeId(paper.getRelateId());
        return answerSheet;
    }

    private LearnPaper getAvailPaper(ExamType examType, LearnStudent student, Long subjectId, Set<Long> existPaperIdSet) {
        LearnPaper paperCond = new LearnPaper();
        paperCond.setSubjectId(subjectId);
        paperCond.setPaperType(PaperType.Simulated.name());
        paperCond.setStatus(1);
        paperCond.setDirectKey(student.getUniversityId() + "_" + examType.name() + "_" + student.getDirectKey());
        List<LearnPaper> paperList = learnPaperService.selectLearnPaperList(paperCond);
        for(LearnPaper paper : paperList) {
            if(existPaperIdSet.add(paper.getId())) {
                return paper;
            }
        }
        throw new ValidationException("未初始化院校模拟题库: " + subjectId + ":" + student.getDirectKey());
    }

    private LearnPaper getBestPaper(ExamType examType, AMarjorPlan plan, Long subjectId, Set<Long> existPaperIdSet) {
        String groupName = StringUtils.trimToEmpty(plan.getMajorGroup());
        LearnPaper paperCond = new LearnPaper();
        paperCond.setSubjectId(subjectId);
        paperCond.setPaperType(PaperType.Simulated.name());
        paperCond.setStatus(1);
        String baseKey = plan.getUniversityId() + "_" + examType.name() + "_";
        paperCond.setDirectKey(baseKey);
        if(!subjectId.equals(11L)) {
            List<LearnPaper> paperList = learnPaperService.selectLearnPaperList(paperCond);
            if(paperList.isEmpty()) {
                paperCond.setDirectKey("");
                paperList = learnPaperService.selectLearnPaperList(paperCond);
            }
            for(LearnPaper paper : paperList) {
                if(existPaperIdSet.add(paper.getId())) {
                    return paper;
                }
            }
            existPaperIdSet.clear(); // TODO 忽略已做的卷
            for(LearnPaper paper : paperList) {
                if(existPaperIdSet.add(paper.getId())) {
                    return paper;
                }
            }
            throw new ValidationException("未初始化院校模拟题库: " + subjectId + ":" + plan.getId());
        }
        List<LearnPaper> paperList = learnPaperService.selectLearnPaperList(paperCond);
        for(int i = 3; i>0; i--) {
            if(i == 3) {
                String prefix = baseKey + groupName + "_";
                for(LearnPaper paper : paperList) {
                    if(paper.getDirectKey().startsWith(prefix)) {
                        Set<String> majorNameSet = Sets.newHashSet(paper.getDirectKey().substring(prefix.length()).split("、"));
                        if(majorNameSet.contains(plan.getMajorName()) && existPaperIdSet.add(paper.getId())) {
                            return paper;
                        }
                    }
                }
            } else if(i == 2) {
                String prefix = baseKey + groupName;
                for(LearnPaper paper : paperList) {
                    if(paper.getDirectKey().equals(prefix)) {
                        if(existPaperIdSet.add(paper.getId())) {
                            return paper;
                        }
                    }
                }
            } else if(StringUtils.isBlank(groupName)) {
                break;
            } else {
                for(LearnPaper paper : paperList) {
                    if(paper.getDirectKey().equals(baseKey)) {
                        if(existPaperIdSet.add(paper.getId())) {
                            return paper;
                        }
                    }
                }
            }
        }
        throw new ValidationException("未初始化院校定向模拟题库：" + subjectId + ":" + plan.getId() + "\n  " + plan.getEnrollFormula());
    }

    private AnswerSheet buildAnswerSheet(LearnPaper paper, LearnExaminee examinee, List<PaperVO.QuestionAnswer> questionList) {
        AnswerSheet answerSheet = new AnswerSheet();
        answerSheet.setExamineeId(examinee.getExamineeId());
        answerSheet.setPaperId(examinee.getPaperId());
        answerSheet.setName(paper.getPaperName());
        answerSheet.setBeginTime(examinee.getBeginTime());
        answerSheet.setDuration(examinee.getDuration());
        if(StringUtils.isNotBlank(paper.getPaperInfo())) {
            answerSheet.setPaperInfo(JSONObject.parseObject(paper.getPaperInfo(), AnswerSheet.PaperCond.class));
        }
        if(PaperType.Practice.name().equals(paper.getPaperType()) || PaperType.Course.name().equals(paper.getPaperType())) {
            answerSheet.setKnowledgeId(paper.getRelateId());
        }
        answerSheet.setScoringType("1");
        answerSheet.setMode(0L);
        answerSheet.setState(examinee.getState());
        answerSheet.setAllowAnswer(true);
        answerSheet.setAllowScore(false);

        if(null != examinee.getPaperInfo()) {
            JSONObject info = JSONObject.parseObject(examinee.getPaperInfo());
            answerSheet.setCollegeId(info.getLong("universityId"));
            answerSheet.setCollegeName(info.getString("universityName"));
            answerSheet.setMajorId(info.getLong("planId"));
            answerSheet.setMajorName(info.getString("majorName"));
            answerSheet.setSubjectId(paper.getSubjectId());
            answerSheet.setSubjectName(dzSubjectService.selectDzSubjectBySubjectId(paper.getSubjectId()).getSubjectName());
        }
        answerSheet.setQuestions(questionList);
        return answerSheet;
    }
}
