package com.ruoyi.web.service;

import cn.hutool.core.lang.Dict;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.ruoyi.common.annotation.Excel;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.enums.ExamType;
import com.ruoyi.common.enums.SubjectType;
import com.ruoyi.common.utils.CommonUtils;
import com.ruoyi.common.utils.NumberUtils;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.dz.domain.*;
import com.ruoyi.dz.dto.CardStudentDTO;
import com.ruoyi.dz.mapper.DzClassesMapper;
import com.ruoyi.dz.mapper.DzTeacherMapper;
import com.ruoyi.dz.service.IDzCardsService;
import com.ruoyi.dz.service.IDzControlService;
import com.ruoyi.dz.service.IDzSubjectService;
import com.ruoyi.enums.ExamineeStatus;
import com.ruoyi.enums.PaperStatus;
import com.ruoyi.enums.PaperType;
import com.ruoyi.enums.QuestionType;
import com.ruoyi.ie.domain.AMarjorPlan;
import com.ruoyi.ie.mapper.AMarjorPlanMapper;
import com.ruoyi.learn.domain.*;
import com.ruoyi.learn.mapper.*;
import com.ruoyi.learn.service.ILearnTestService;
import com.ruoyi.system.mapper.SysUserMapper;
import com.ruoyi.system.service.ISysConfigService;
import com.ruoyi.syzy.domain.BBusiWishUniversities;
import com.ruoyi.syzy.mapper.BBusiWishUniversitiesMapper;
import com.ruoyi.web.domain.TestRecordCond;
import lombok.Data;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
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 javax.validation.ValidationException;
import java.util.*;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Service
public class LearnTeacherService {
    private final DzClassesMapper dzClassesMapper;
    private final LearnKnowledgeTreeMapper learnKnowledgeTreeMapper;
    private final LearnStudentMapper learnStudentMapper;
    private final AMarjorPlanMapper marjorPlanMapper;
    private final BBusiWishUniversitiesMapper busiWishUniversitiesMapper;
    private final LearnDirectedKnowledgeMapper learnDirectedKnowledgeMapper;
    private final LearnCultureKnowledgeMapper learnCultureKnowledgeMapper;
    private final PaperService paperService;
    private final CommService commService;
    private final LearnTestPaperMapper learnTestPaperMapper;
    private final LearnQuestionsMapper learnQuestionsMapper;
    private final LearnKnowledgeCourseMapper learnKnowledgeCourseMapper;
    private final LearnTestStudentMapper learnTestStudentMapper;
    private final IDzSubjectService dzSubjectService;
    private final ILearnTestService learnTestService;
    private final LearnAnswerMapper learnAnswerMapper;
    private final DzTeacherMapper dzTeacherMapper;
    private final SysUserMapper sysUserMapper;
    private final IDzControlService dzControlService;
    private final ISysConfigService sysConfigService;
    @Autowired
    private IDzCardsService dzCardsService;

    public LearnTeacherService(DzClassesMapper dzClassesMapper, LearnKnowledgeTreeMapper learnKnowledgeTreeMapper, LearnStudentMapper learnStudentMapper, AMarjorPlanMapper marjorPlanMapper, BBusiWishUniversitiesMapper busiWishUniversitiesMapper, LearnDirectedKnowledgeMapper learnDirectedKnowledgeMapper, PaperService paperService, CommService commService, LearnTestPaperMapper learnTestPaperMapper, LearnQuestionsMapper learnQuestionsMapper, LearnKnowledgeCourseMapper learnKnowledgeCourseMapper, LearnTestStudentMapper learnTestStudentMapper, IDzSubjectService dzSubjectService, ILearnTestService learnTestService, LearnAnswerMapper learnAnswerMapper, DzTeacherMapper dzTeacherMapper, SysUserMapper sysUserMapper, LearnCultureKnowledgeMapper learnCultureKnowledgeMapper, IDzControlService dzControlService, ISysConfigService sysConfigService) {
        this.dzClassesMapper = dzClassesMapper;
        this.learnKnowledgeTreeMapper = learnKnowledgeTreeMapper;
        this.learnStudentMapper = learnStudentMapper;
        this.marjorPlanMapper = marjorPlanMapper;
        this.busiWishUniversitiesMapper = busiWishUniversitiesMapper;
        this.learnDirectedKnowledgeMapper = learnDirectedKnowledgeMapper;
        this.paperService = paperService;
        this.commService = commService;
        this.learnTestPaperMapper = learnTestPaperMapper;
        this.learnQuestionsMapper = learnQuestionsMapper;
        this.learnKnowledgeCourseMapper = learnKnowledgeCourseMapper;
        this.learnTestStudentMapper = learnTestStudentMapper;
        this.dzSubjectService = dzSubjectService;
        this.learnTestService = learnTestService;
        this.learnAnswerMapper = learnAnswerMapper;
        this.dzTeacherMapper = dzTeacherMapper;
        this.sysUserMapper = sysUserMapper;
        this.learnCultureKnowledgeMapper = learnCultureKnowledgeMapper;
        this.dzControlService = dzControlService;
        this.sysConfigService = sysConfigService;
        // processDirectStudy();
    }

    private void processDirectStudy() {
        List<SysUser> userList = sysUserMapper.selectUserByUserIds(Stream.of(1,3,5,6,9,10,11,15,16,20,21,23,24,26,27,33,34,44,47,60,61,69,70,71,72,73,74,83,84,94,95,96,97,98,99,100,101,102,103,104,105,110,114,115,116,117,118,119,124,125,126,127,128,129,134,135,136,137,140,144,151,166,170,172,174,178).map(t -> t.longValue()).collect(Collectors.toList()));
        SysUser upUser = new SysUser();
        Set<Long> idSet = Sets.newHashSet(14663L,10836L,14139L,12597L,13942L);
        for(SysUser user : userList) {
            if(StringUtils.isBlank(user.getDirectedStudy())) {
                continue;
            }
            JSONArray jsonList = JSONArray.parse(user.getDirectedStudy());
            List<JSONObject> finalList = Lists.newArrayList();
            for(int i=0;i<jsonList.size();i++) {
                JSONObject o = jsonList.getJSONObject(i);
                Long id = o.getLong("universityId");
                if(idSet.contains(id)){
                    continue;
                }
                finalList.add(o);
            }
            upUser.setUserId(user.getUserId());
            upUser.setDirectedStudy(JSONArray.toJSONString(finalList));
            // sysUserMapper.updateUser(upUser);
        }

    }

    public List<LearnQuestions> getQuestionTypes(Long subjectId, Collection<Long> knowledgeIds) {
        return learnQuestionsMapper.statQuestionTypes(subjectId, knowledgeIds);
    }

    public List<JSONObject> getClassesBuildStats(TestPaperVO.TestPaperBuildReq req, Long teacherId) {
        req.setTeacherId(teacherId);
        String buildType = req.getBuildType();
        if(buildType.startsWith("Exact")) {
            req.setSubjectId(11L);
        }
        if("ExactIntelligent".equals(buildType)||"ExactHand".equals(buildType)){
            return dzClassesMapper.selectClassesDirectedBuildStats(req.toMap());
        }
        return dzClassesMapper.selectClassesBuildStats(req.toMap());
    }

    public List<JSONObject> getPaperClassRecords(TestPaperVO.TestPaperBuildReq req, Long teacherId) {
        req.setTeacherId(teacherId);
        String buildType = req.getBuildType();
        if(buildType.startsWith("Exact")) {
            req.setSubjectId(11L);
        }
        if("ExactIntelligent".equals(buildType)||"ExactHand".equals(buildType)){
            return dzClassesMapper.selectClassesDirectedBuildStats(req.toMap());
        }
        return dzClassesMapper.selectClassesBuildStats(req.toMap());
    }

    public List<JSONObject> getPapers(TestPaperVO.TestPaperBuildReq req, Long creatorId) {
        Map<String, Object> params = Maps.newHashMap();
        params.put("creatorId", creatorId);
        params.put("examType", req.getExamType());
        params.put("buildType", req.getBuildType());
        params.put("batchId", req.getBatchId());
        return learnTestPaperMapper.selectPapers(params);
    }

    public List<JSONObject> getPaperStudentRecords(TestPaperVO.TestPaperBuildReq req) {
        return dzClassesMapper.getPaperStudentRecords(req.toMap());
    }

    public List<JSONObject> getPaperStudentDetail(Long examineeId) {
        List<JSONObject> list = learnAnswerMapper.selectPaperKnowledgeStats(Dict.create().set("examineeId", examineeId));
        list.forEach(o -> {
            CommonUtils.normalRate(o);
        });
        return list;
    }

    /**
     * 获取班级组卷统计详情（学生列表）
     * @param req 请求参数
     * @param teacherId 教师ID
     * @param statType 统计类型：send/total/unexact/unfinish/unsend
     * @return 学生信息列表
     */
    public List<JSONObject> getClassesBuildStatsDetail(TestPaperVO.TestPaperBuildReq req, Long teacherId, String statType) {
        req.setTeacherId(teacherId);
        String buildType = req.getBuildType();
        if(buildType.startsWith("Exact")) {
            req.setSubjectId(11L);
        }
        Map<String, Object> map = req.toMap();
        map.put("statType", statType);
        return dzClassesMapper.selectClassesBuildStatsDetail(map);
    }

    public List<LearnTestPaper> getBuiltTestPaper(TestPaperVO.TestPaperBuildReq req) {
        LearnTestPaper cond = new LearnTestPaper();
        BeanUtils.copyProperties(req, cond);
        if(null != req.getMajorPlanId()) {
            AMarjorPlan plan = marjorPlanMapper.selectAMarjorPlanById(req.getMajorPlanId());
            LearnDirectedKnowledge dkCond = new LearnDirectedKnowledge();
            dkCond.setUniversityId(plan.getUniversityId());
            dkCond.setMatchYear(plan.getYear());
            SysUser sysUser = SecurityUtils.getLoginUser().getUser();
            dkCond.setLocations(sysUser.getLocation());
            dkCond.setExamineeTypes(sysUser.getExamType().name());
            Map<String, LearnDirectedKnowledge> directedKnowledgeMap = learnDirectedKnowledgeMapper.selectLearnDirectedKnowledgeList(dkCond).stream().collect(Collectors.toMap(LearnDirectedKnowledge::getDirectKey, t -> t));
            LearnDirectedKnowledge directedKnowledge = getBestLearnDirectedKnowledge(plan, directedKnowledgeMap);
            cond.setDirectKey(buildDirectKey(directedKnowledge));
        }
        return learnTestPaperMapper.selectLearnTestPaperList(cond);
    }

    public List<DzClasses> getClasses(Long teacherId)
    {
        return dzClassesMapper.selectClassesForTeacher(teacherId);
    }

    public List<LearnStudent> getStudents(Long batchId, Long classId) {
        return learnStudentMapper.selectClassStudents(batchId, new Long[] {classId});
    }

    public List<BBusiWishUniversities> selectUniversityList(Long teacherId, Long batchId, String examType) {
        return busiWishUniversitiesMapper.selectUniversityListForTeacher(teacherId, batchId, examType);
    }

    public List<Dict> selectMajorList(Long teacherId, Long universityId, Integer year, Long batchId, String examType) {
        // TODO 由于未区分考生类型，好些会重复，暂取最小计划，使用这个去匹配相关的类型
        return marjorPlanMapper.selectMajorForUniversity(teacherId, universityId, year, "ExactHand", batchId, 11L, examType).stream().map(t -> Dict.create().set("id", t.getId()).set("majorGroup", t.getMajorGroup())
                .set("majorName", t.getMajorName()).set("count", t.getXuefei()).set("total", t.getPlanTotal())).collect(Collectors.toList());
    }

    public String buildDirectKey(LearnDirectedKnowledge dk) {
        return dk.getUniversityId() + "_" + dk.getExamineeTypes() + "_" + StringUtils.trimToEmpty(dk.getDirectKey());
    }
    public String buildDirectKey(LearnStudent dk) {
        return dk.getUniversityId() + "_" + dk.getExamType() + "_" + StringUtils.trimToEmpty(dk.getDirectKey());
    }

    public void updateBuildInfo(Long teacherId, TestRecordCond cond) {
        DzTeacher upTeacher = new DzTeacher();
        upTeacher.setTeacherId(teacherId);
        upTeacher.setBuildInfo(JSONObject.toJSONString(cond));
        dzTeacherMapper.updateDzTeacher(upTeacher);
    }

    @Transactional(rollbackFor = Exception.class)
    public String buildPapersDirect(TestPaperVO.TestPaperBuildReq req) {
        // 查询待生成或发布的学生列表
        List<LearnStudent> studentList = learnStudentMapper.selectLearnStudentsByMap(req.toMap());
        if(CollectionUtils.isEmpty(studentList)) {
            throw new ValidationException("无学生可发布");
        }
        SysUser sysUser = SecurityUtils.getLoginUser().getUser();
        ExamType examType;
        if(null == req.getExamType()) {
            examType = null != sysUser.getExamType() ? sysUser.getExamType() : ExamType.OHS;
        } else {
            examType = ExamType.valueOf(req.getExamType());
        }
        DzControl control = dzControlService.selectDzControl(sysUser.getLocation(), examType);
        req.setMatchYear(control.getPlanYear());

        // 定向手动是精准到一个计划，定向自动是对应一组计划
        Map<Long, Map<String, List<LearnStudent>>> universityDirectStudentMap = studentList.stream()
                .collect(Collectors.groupingBy(LearnStudent::getUniversityId, Collectors.groupingBy(this::buildDirectKey, Collectors.toList())));
        // 查询已经生成的
        Map<Long, Map<String, LearnTestPaper>> universityDirectPaperMap = learnTestPaperMapper.selectByBatchAndUniversityIds(req.getBuildType(), req.getBatchId(), req.getSubjectId(), req.getTeacherId(), universityDirectStudentMap.keySet()).stream()
                .collect(Collectors.groupingBy(LearnTestPaper::getUniversityId, Collectors.toMap(t -> StringUtils.trimToEmpty(t.getDirectKey()), a -> a)));
        // 补充发卷
        if("ExactHand".equals(req.getBuildType())) { // 肯定只有一个计划
            for(Long universityId : universityDirectStudentMap.keySet()) {
                Map<String, List<LearnStudent>> directedStudentsMap = universityDirectStudentMap.get(universityId);
                Map<String, LearnTestPaper> directPaperMap = universityDirectPaperMap.get(universityId);
                buildPapersDirect(req, universityId, req.getExamType(), null, directedStudentsMap, directPaperMap);
            }
        } else {
            // directKey = universityId_examType_directKey
            Map<Long, Map<String, LearnDirectedKnowledge>> universityDirectedKnowledgeMap = learnDirectedKnowledgeMapper.selectByUniversityIds(universityDirectStudentMap.keySet().toArray(new Long[universityDirectStudentMap.size()]), req.getMatchYear()).stream().collect(
                    Collectors.groupingBy(LearnDirectedKnowledge::getUniversityId, Collectors.toMap(this::buildDirectKey, a -> a)));
            for(Long universityId : universityDirectedKnowledgeMap.keySet()) {
                Map<String, LearnDirectedKnowledge> directedKnowledgeMap = universityDirectedKnowledgeMap.get(universityId);
                Map<String, List<LearnStudent>> directedStudentsMap = universityDirectStudentMap.get(universityId);
                Map<String, LearnTestPaper> directPaperMap = universityDirectPaperMap.get(universityId);
                if(null == directedKnowledgeMap) {
                    throw new ValidationException("院校无定向定义");
                }
                buildPapersDirect(req, universityId, req.getExamType(), directedKnowledgeMap, directedStudentsMap, directPaperMap);
            }
        }
        updateBuildInfo(req.getTeacherId(), new TestRecordCond(req.getBuildType(), req.getBatchId(), req.getClassId(), req.getSubjectId(), null, null));
        return "";
    }

    private void buildPapersDirect(TestPaperVO.TestPaperBuildReq req, Long universityId, String examType, Map<String, LearnDirectedKnowledge> directedKnowledgeMap, Map<String, List<LearnStudent>> directedStudentsMap, Map<String, LearnTestPaper> directPaperMap) {
        if(null == directPaperMap) {
            directPaperMap = Maps.newHashMap();
        }
        DzSubject dzSubject = dzSubjectService.selectDzSubjectBySubjectId(req.getSubjectId());
        LearnTest learnTest = learnTestService.selectLearnTestByBatchId(req.getBatchId());
        for(String directedKey : directedStudentsMap.keySet()) {
            LearnTestPaper learnTestPaper;
            Boolean noPaper = false;
            if(null == (learnTestPaper = directPaperMap.get(directedKey))) {
                LearnPaper paper = new LearnPaper();
                paper.setSubjectId(req.getSubjectId());
                String paperName = StringUtils.isBlank(req.getName()) ? learnTest.getName() + "_" + dzSubject.getSubjectName() + "_" + directedKey : req.getName();
                paper.setPaperName(paperName.length() > 254 ? paperName.substring(0, 254) : paperName);
                paper.setYear(Calendar.getInstance().get(Calendar.YEAR)); // TODO  年度
                paper.setPaperType(PaperType.Test.name());
                paper.setDirectKey(directedKey);
                paper.setFenshu(0);
                if(null == directedKnowledgeMap) {
                    saveHandPaper(paper, req.getQuestions());
                } else {
                    LearnDirectedKnowledge directedKnowledge = directedKnowledgeMap.get(directedKey);
                    if(null != directedKnowledge) {
                        examType = directedKnowledge.getExamineeTypes();
                        if(StringUtils.isNotBlank(directedKnowledge.getConditions())) {
                            TestPaperVO.PaperDef2 paperDef = new TestPaperVO.PaperDef2(directedKnowledge.getKnowledges(), directedKnowledge.getConditions());
                            paperDef.setFillExclude(false);
                            AnswerSheet.PaperCond info = new AnswerSheet.PaperCond();
                            info.setScore(paper.getFenshu());
                            info.setTime(directedKnowledge.getTime() * 60);
                            info.setTypes(paperDef.getTypes().stream().map(t -> new AnswerSheet.PaperCondType(t.getType().getTitle(), t.getCount(), t.getScore())).collect(Collectors.toList()));
                            paper.setPaperInfo(JSONObject.toJSONString(info));
                            Pair<LearnPaper, List<LearnPaperQuestion>> paperResult = paperService.buildPaper2(null, null, paper, paperDef);
                            paperService.savePaper(paperResult.getKey(), paperResult.getValue());
                        } else if(!sysConfigService.selectSkillTestOnly(directedKnowledge.getModules())) {
                            throw new ValidationException("未配置定向题型" + directedKnowledge.getId());
                        } else {
                            noPaper = true;
                        }
                    } else {
                        noPaper = true;
                    }

                    /* 暂定使用知识定一样的类型处理
                    TestPaperVO.PaperDef paperDef = req.getPaperDef();
                    examType = directedKnowledge.getExamineeTypes();
                    paperDef.setKnowIds(Stream.of(directedKnowledge.getKnowledges().split(",")).map(t -> Long.parseLong(t.trim())).collect(Collectors.toList()));
                    if (CollectionUtils.isEmpty(paperDef.getTypes())) {
                        paperDef.setTypes(getQuestionTypes(null, paperDef.getKnowIds()).stream().map(t -> {
                            TestPaperVO.TypeDef typeDef = new TestPaperVO.TypeDef();
                            typeDef.setType(t.getQtpye());
                            typeDef.setScore(1);
                            typeDef.setTitle(t.getQtpye());
                            return typeDef;
                        }).collect(Collectors.toList()));
                    }
                    if(paperDef.getTotal().equals(0L)) { // TODO  总数怎么定？
                        paperDef.setTotal(15L);
                    }
                    Pair<LearnPaper, List<LearnPaperQuestion>> paperResult = paperService.buildPaper(null, paper, paperDef);
                    paperService.savePaper(paperResult.getKey(), paperResult.getValue());*/
                }
                if(!noPaper) {
                    LearnTestPaper testPaper = new LearnTestPaper();
                    testPaper.setBuildType(req.getBuildType());
                    testPaper.setUniversityId(universityId);
                    testPaper.setExamType(examType);
                    testPaper.setBatchId(req.getBatchId());
                    testPaper.setSubjectId(req.getSubjectId());
                    testPaper.setDirectKey(directedKey);
                    testPaper.setPaperId(paper.getId());
                    testPaper.setConditions(JSONObject.toJSONString(req));
                    testPaper.setCreatorId(SecurityUtils.getUserId());
                    testPaper.setTeacherId(req.getTeacherId());
                    learnTestPaperMapper.insertLearnTestPaper(testPaper);
                    directPaperMap.put(directedKey, testPaper);
                    learnTestPaper = testPaper;
                }
            }
            for(LearnStudent student : directedStudentsMap.get(directedKey)) {
                LearnTestStudent lts = new LearnTestStudent();
                lts.setBatchId(req.getBatchId());
                lts.setStudentId(student.getStudentId());
                lts.setBuildType(req.getBuildType());
                lts.setSubjectId(req.getSubjectId());
                lts.setDirectKey(directedKey);
                if(noPaper || sysConfigService.selectSkillTestOnly(student.getModules())) {
                    lts.setStatus(ExamineeStatus.Commit.getVal());
                } else {
                    lts.setPaperId(learnTestPaper.getPaperId());
                    lts.setStatus(ExamineeStatus.Sign.getVal());
                }
                lts.setClassId(student.getClassId());
                lts.setCreatorId(SecurityUtils.getUserId());
                lts.setSchoolId(student.getSchoolId());
                lts.setCampusId(student.getCampusId());
                lts.setPlanId(student.getMajorPlanId());
                learnTestStudentMapper.insertLearnTestStudent(lts);
            }
        }
    }

    @Transactional(rollbackFor = Exception.class)
    public String buildPapersFull(TestPaperVO.TestPaperBuildReq req) {
        List<LearnStudent> studentList = learnStudentMapper.selectLearnStudentsByMap(req.toMap());
        if(CollectionUtils.isEmpty(studentList)) {
            throw new ValidationException("无学生可发布");
        }
        DzSubject dzSubject = dzSubjectService.selectDzSubjectBySubjectId(req.getSubjectId());
        LearnTest learnTest = learnTestService.selectLearnTestByBatchId(req.getBatchId());
        // 查询已经生成的
        List<LearnTestPaper> learnTestPaperList = learnTestPaperMapper.selectByBatchAndUniversityIds(req.getBuildType(), req.getBatchId(), req.getSubjectId(),req.getTeacherId(), null);
        LearnTestPaper learnTestPaper;
        if(CollectionUtils.isEmpty(learnTestPaperList)) {
            LearnTestPaper testPaper = getPaper( req, dzSubject, learnTest);
            learnTestPaper = testPaper;
        } else {
            learnTestPaper = learnTestPaperList.get(0);
        }
        for(LearnStudent student : studentList) {
            LearnTestStudent lts = new LearnTestStudent();
            lts.setBatchId(req.getBatchId());
            lts.setStudentId(student.getStudentId());
            lts.setBuildType(req.getBuildType());
            lts.setSubjectId(req.getSubjectId());
            lts.setDirectKey("");
            lts.setPaperId(learnTestPaper.getPaperId());
            lts.setCampusId(student.getCampusId());
            lts.setSchoolId(student.getSchoolId());
            lts.setClassId(student.getClassId());
            lts.setStatus(ExamineeStatus.Sign.getVal());
            lts.setCreatorId(SecurityUtils.getUserId());
            learnTestStudentMapper.insertLearnTestStudent(lts);
        }
        updateBuildInfo(req.getTeacherId(), new TestRecordCond(req.getBuildType(), req.getBatchId(), req.getClassId(), req.getSubjectId(), null, null));
        return "";
    }

    public void saveHandPaper(LearnPaper paper, List<LearnPaperQuestion> questions) {
        List<LearnPaperQuestion> pqList = Lists.newArrayList();
        List<AnswerSheet.PaperCondType> typeList = Lists.newArrayList();
        Map<String, AnswerSheet.PaperCondType> typeMap = Maps.newHashMap();
        Double fenScore = 0.0;
        for (LearnPaperQuestion q : questions) {
            LearnPaperQuestion pq = new LearnPaperQuestion();
            pq.setQuestionId(q.getId()); // 没传questionId?
            pq.setSeq(pqList.size() + 1);
            pq.setKnowledgeId(q.getKnowledgeId());
            pq.setScore(null == q.getScore() ? 1.0 : q.getScore());
            fenScore += q.getScore();
            AnswerSheet.PaperCondType pt = typeMap.get(q.getType());
            if(null == pt) {
                pt = new AnswerSheet.PaperCondType();
                pt.setType(q.getType());
                pt.setCount(1);
                pt.setScore(pq.getScore());
                typeMap.put(q.getType(), pt);
                typeList.add(pt);
            } else {
                pt.setCount(pt.getCount() + 1);
                pt.setScore((pq.getScore() + q.getScore()) / 2);
            }
            pqList.add(pq);
        }
        AnswerSheet.PaperCond info = new AnswerSheet.PaperCond();
        info.setScore(paper.getFenshu());
        info.setTime(90 * 60);
        typeList.stream().forEach(t -> t.setScore(Math.round(t.getScore() * 10) / 10.0));
        info.setTypes(typeList);
        paper.setPaperInfo(JSONObject.toJSONString(info));
        paper.setFenshu((int) Math.round(fenScore));
        paperService.savePaper(paper, pqList);
    }

    public List<TreeNode> getKnowledgeTree(String examineeType, Long subjectId, Set<Long> knowledgeIdSet, Long studentId, Integer questionType) {
        LearnKnowledgeTree ktCond = new LearnKnowledgeTree();
        if (null != subjectId && !subjectId.equals(100L)) {
            ktCond.setSubjectId(subjectId);
        }
        ktCond.setExamineeTypes(examineeType);
        List<LearnKnowledgeTree> ktList = learnKnowledgeTreeMapper.selectLearnKnowledgeTreeList(ktCond);
        Map<Long, Integer[]> knowCountMap = null;
        if(NumberUtils.isPositive(studentId) && !CollectionUtils.isEmpty(ktList)) {
            Map cond = Maps.newHashMap();
            cond.put("studentId", studentId);
            cond.put("knowIds", ktList.stream().map(LearnKnowledgeTree::getId).collect(Collectors.toList()));
            if(null != questionType && !questionType.equals(0)) {
                cond.put("types", Lists.newArrayList(QuestionType.Single.getTitle(), QuestionType.Judgment.getTitle()));
            }
            knowCountMap = Maps.newHashMap();
            for(LearnQuestions qs : learnQuestionsMapper.statByKnowledge(cond)) {
                knowCountMap.put(qs.getKnowledgeId(), new Integer[] { qs.getNumber().intValue(), qs.getId().intValue(), qs.getYear().intValue()} );
            }
        }
        return commService.buildTree(ktList, knowledgeIdSet, knowCountMap);
    }

    public List<TreeNode> getCourseKnowledgeTree(Long studentId) {
        LearnKnowledgeCourse ktCond = new LearnKnowledgeCourse();
        List<LearnKnowledgeCourse> ktList = learnKnowledgeCourseMapper.selectLearnKnowledgeCourseList(ktCond);
        Map<Long, Integer[]> knowCountMap = null;
        if(NumberUtils.isPositive(studentId) && !CollectionUtils.isEmpty(ktList)) {
            Map<Long, Set<Long>> q2coursesMap = Maps.newHashMap();
            for(LearnKnowledgeCourse kt : ktList) {
                if(StringUtils.isNotBlank(kt.getKnowledges())) {
                    for(String idStr : kt.getKnowledges().split(",")) {
                        Long id = Long.parseLong(idStr);
                        Set<Long> courseSet = q2coursesMap.computeIfAbsent(id, k -> Sets.newHashSet());
                        courseSet.add(kt.getId());
                    }
                }
            }
            Map cond = Maps.newHashMap();
            cond.put("studentId", studentId);
            cond.put("knowIds", q2coursesMap.keySet());
            knowCountMap = Maps.newHashMap();
            for(LearnQuestions qs : learnQuestionsMapper.statByKnowledge(cond)) {
                for(Long qnId : q2coursesMap.get(qs.getKnowledgeId())) {
                    Integer[] counts = knowCountMap.get(qnId);
                    if(null == counts) {
                        knowCountMap.put(qnId, new Integer[] { qs.getNumber().intValue(), qs.getId().intValue(), qs.getYear().intValue()} );
                    } else {
                        knowCountMap.put(qnId, new Integer[] { counts[0] + qs.getNumber().intValue(), counts[1] + qs.getId().intValue(), counts[2] + qs.getYear().intValue()} );
                    }
                }
            }
        }
        return commService.buildCourseKnowledgeTree(ktList, knowCountMap);
    }

    public String updateDirected(Long studentId, Long universityId, Long planId) {
        LearnStudent upStudent = new LearnStudent();
        upStudent.setStudentId(studentId);
        upStudent.setUniversityId(universityId);
        upStudent.setMajorPlanId(planId);
        upStudent.setDirectKey("");
        AMarjorPlan plan = marjorPlanMapper.selectAMarjorPlanById(planId);
        LearnDirectedKnowledge dkCond = new LearnDirectedKnowledge();
        dkCond.setUniversityId(plan.getUniversityId());
        dkCond.setMatchYear(plan.getMatchYear());
        SysUser sysUser = SecurityUtils.getLoginUser().getUser();
        dkCond.setLocations(sysUser.getLocation());
        dkCond.setExamineeTypes(sysUser.getExamType().name());
        Map<String, LearnDirectedKnowledge> directedKnowledgeMap = learnDirectedKnowledgeMapper.selectLearnDirectedKnowledgeList(dkCond).stream().collect(Collectors.toMap(LearnDirectedKnowledge::getDirectKey, t -> t));
        LearnDirectedKnowledge directedKnowledge = getBestLearnDirectedKnowledge(plan, directedKnowledgeMap);
        String modules = null;
        if(null != directedKnowledge) {
            upStudent.setDirectKey(directedKnowledge.getDirectKey());
            modules = directedKnowledge.getModules();
            upStudent.setModules(modules);
        }
        // TODO 补充保存在注册和创建关联用户时没有初始化的, 但这时没有classId
        if(null == learnStudentMapper.selectLearnStudentByStudentId(upStudent.getStudentId())) {
            learnStudentMapper.insertLearnStudent(upStudent);
        } else {
            learnStudentMapper.updateLearnStudent(upStudent);
        }
        return sysConfigService.selectSkillTestOnly(modules) ? "该专业只考" + modules : "";
    }
    public List<LearnCultureKnowledge> getLearnCultureKnowledgeList(Long planId) {
        AMarjorPlan plan = marjorPlanMapper.selectAMarjorPlanById(planId);
        if(null == plan) {
            return Lists.newArrayList();
        }
        LearnCultureKnowledge ckCond = new LearnCultureKnowledge();
        ckCond.setUniversityId(plan.getUniversityId());
        ckCond.setExamineeTypes(ExamType.parse(plan.getExamineeType()).name());
        ckCond.setMatchYear(plan.getMatchYear());
        return getLearnCultureKnowledgeList(plan, ckCond);
    }

    public List<LearnCultureKnowledge> getLearnCultureKnowledgeList(AMarjorPlan plan, LearnCultureKnowledge ckCond) {
        List<LearnCultureKnowledge> list = Lists.newArrayList();
        String groupName = StringUtils.trimToEmpty(plan.getMajorGroup());
        String majorName = StringUtils.trimToEmpty(plan.getMajorName());

        for(LearnCultureKnowledge ck : learnCultureKnowledgeMapper.selectLearnCultureKnowledgeList(ckCond)) {
            String directKey = StringUtils.trimToEmpty(ck.getDirectKey());
            if(StringUtils.isNotBlank(directKey)) {
                String[] groupMajors = directKey.split("_");
                if(StringUtils.isNotBlank(groupMajors[0]) && (StringUtils.isBlank(groupName) ||
                        !Arrays.stream(groupMajors[0].split("_|、")).filter(t -> !t.isEmpty()).collect(Collectors.toSet()).contains(groupName))) {
                    continue;
                }
                if(groupMajors.length > 1 && (StringUtils.isBlank(majorName) ||
                        !Arrays.stream(groupMajors[1].split("_|、")).filter(t -> !t.isEmpty()).collect(Collectors.toSet()).contains(majorName))) {
                    continue;
                }
            }
            list.add(ck);
        }
        return list;
    }

    private LearnDirectedKnowledge getBestLearnDirectedKnowledge(AMarjorPlan plan, Map<String, LearnDirectedKnowledge> directedKnowledgeMap) {
        return getBestLearnDirectedKnowledge(plan.getMajorGroup(), plan.getMajorName(), directedKnowledgeMap);
    }
    public LearnDirectedKnowledge getBestLearnDirectedKnowledge(String majorGroup, String majorName, Map<String, LearnDirectedKnowledge> directedKnowledgeMap) {
        LearnDirectedKnowledge directedKnowledge = null;
        String groupName = StringUtils.trimToEmpty(majorGroup);
        if(null != (directedKnowledge = directedKnowledgeMap.get(groupName + "_" + majorName))) {
            return directedKnowledge;
        }
        for(String directKey : directedKnowledgeMap.keySet()) {
            if(directKey.contains("、") && (directKey.startsWith(groupName + "_"))) {
                Set<String> marjorSet = Arrays.stream(directKey.split("_|、")).filter(t -> !t.isEmpty()).collect(Collectors.toSet());
                if(marjorSet.contains(majorName)) {
                    return directedKnowledgeMap.get(directKey);
                }
            }
        }
        if(null != (directedKnowledge = directedKnowledgeMap.get(groupName))) {
            return directedKnowledge;
        }
        if(null != (directedKnowledge = directedKnowledgeMap.get(""))) {
            return directedKnowledge;
        }
        return directedKnowledge;
    }

    public Set<Long> getSubjectIdSet(Long[] planIds) {
        Set<Long> subjectIdSet = Sets.newHashSet();
        if(ArrayUtils.isNotEmpty(planIds)) {
            List<AMarjorPlan> planList = marjorPlanMapper.selectAMarjorPlanByIds(planIds);
            AMarjorPlan curr = planList.get(0);
            LearnDirectedKnowledge dkCond = new LearnDirectedKnowledge();
            dkCond.setUniversityId(curr.getUniversityId());
            dkCond.setMatchYear(curr.getMatchYear());
            SysUser sysUser = SecurityUtils.getLoginUser().getUser();
            dkCond.setLocations(sysUser.getLocation());
            dkCond.setExamineeTypes(sysUser.getExamType().name());
            Map<String, LearnDirectedKnowledge> directedKnowledgeMap = learnDirectedKnowledgeMapper.selectLearnDirectedKnowledgeList(dkCond).stream().collect(Collectors.toMap(LearnDirectedKnowledge::getDirectKey, t -> t));
            LearnCultureKnowledge ckCond = new LearnCultureKnowledge();
            ckCond.setUniversityId(curr.getUniversityId());
            ckCond.setMatchYear(curr.getMatchYear());
            ckCond.setLocations(sysUser.getLocation());
            ckCond.setExamineeTypes(sysUser.getExamType().name());
            learnCultureKnowledgeMapper.selectLearnCultureKnowledgeList(ckCond).stream().filter(t -> StringUtils.isNotBlank(t.getSubjects()) && StringUtils.isNotBlank(t.getKnowledges())).forEach(t -> {
                subjectIdSet.addAll(Stream.of(t.getSubjects().split(",")).map(Long::parseLong).collect(Collectors.toList()));
            });
            LearnDirectedKnowledge directedKnowledge = null;
            for(AMarjorPlan plan : planList) {
                if(null != (directedKnowledge = getBestLearnDirectedKnowledge(plan, directedKnowledgeMap)) && StringUtils.isNotBlank(directedKnowledge.getSubjects())) {
                    subjectIdSet.addAll(Stream.of(directedKnowledge.getSubjects().split(",")).map(t -> Long.parseLong(t.trim())).collect(Collectors.toList()));
                }
            }
        }
        return subjectIdSet;
    }

    public Set<Long> getKnowledgeIdSet(Long subjectId, Long planId, Integer year, String examType) {
        if(NumberUtils.isPositive(planId)) {
            SysUser sysUser = SecurityUtils.getLoginUser().getUser();
            List<AMarjorPlan> planList = marjorPlanMapper.selectAMarjorPlanByIds(new Long[] { planId });
            if(CollectionUtils.isEmpty(planList)) {
                return Sets.newHashSet();
            }
            if(null == examType) {
                examType = null != sysUser.getExamType() ? sysUser.getExamType().name() : ExamType.OHS.name();
            }
            AMarjorPlan plan = planList.get(0);
            Set<Long> idSet = getStudentKnowledgeIdSet(plan.getMatchYear(), plan, sysUser.getLocation(), examType);
            if(null == subjectId || subjectId < 10) {
                LearnCultureKnowledge ckCond = new LearnCultureKnowledge();
                ckCond.setUniversityId(plan.getUniversityId());
                ckCond.setMatchYear(plan.getMatchYear());
                ckCond.setLocations(sysUser.getLocation());
                ckCond.setExamineeTypes(sysUser.getExamType().name());
                if(null != subjectId) {
                    ckCond.setSubjects(subjectId.toString());
                }
                getLearnCultureKnowledgeList(plan, ckCond).stream().filter(t -> StringUtils.isNotBlank(t.getKnowledges())).forEach(t -> {
                    idSet.addAll(Stream.of(t.getKnowledges().split(",")).map(Long::parseLong).collect(Collectors.toList()));
                });
            }
            return idSet;
        }
        return Sets.newHashSet();
    }

    public Set<Long> getStudentKnowledgeIdSet(Integer year, AMarjorPlan curr, String location, String examType) {
        Set<Long> knowledgeIdSet = Sets.newHashSet();
        LearnDirectedKnowledge dkCond = new LearnDirectedKnowledge();
        dkCond.setUniversityId(curr.getUniversityId());
        dkCond.setMatchYear(year);

        dkCond.setLocations(location);
        dkCond.setExamineeTypes(examType);
        Map<String, LearnDirectedKnowledge> directedKnowledgeMap = learnDirectedKnowledgeMapper.selectLearnDirectedKnowledgeList(dkCond).stream().collect(Collectors.toMap(LearnDirectedKnowledge::getDirectKey, t -> t));
        LearnDirectedKnowledge directedKnowledge = null;
        if(null != (directedKnowledge = getBestLearnDirectedKnowledge(curr, directedKnowledgeMap)) && StringUtils.isNotBlank(directedKnowledge.getKnowledges())) {
            knowledgeIdSet.addAll(Stream.of(directedKnowledge.getKnowledges().split(",")).map(t -> Long.parseLong(t.trim())).collect(Collectors.toList()));
        }
        if(!knowledgeIdSet.isEmpty()) {
            knowledgeIdSet.addAll(learnKnowledgeTreeMapper.selectLearnKnowledgeTreeByParentIds(knowledgeIdSet).stream().map(LearnKnowledgeTree::getId).collect(Collectors.toSet()));
        }
        return knowledgeIdSet;
    }

    @Data
    public static class TreeNode {
        private Long id;
        private String name;
        private Integer status;
        private Integer questionCount;
        private Integer finishedCount;
        private Integer rightCount;
        private Double finishedRatio;
        private Long subjectId;

        List<TreeNode> children = Lists.newArrayList();
        public TreeNode(Long id, String name,Long subjectId, Integer[] counts) {
            this.id = id;
            this.name = name;
            this.status = 1;
            this.subjectId = subjectId;
            this.questionCount = null != counts ? counts[0] : 0;
            this.finishedCount = null != counts ? counts[1] : 0;
            this.rightCount = null != counts ? counts[2] : 0;
            calcRatio();
        }

        public void calcRatio() {
            this.finishedRatio = finishedCount > 0 ? Math.max(Math.round(rightCount * 1000.0 / finishedCount) / 10.0, 0.1) : 0.0;
        }

        public boolean setChecked(Set<Long> idSet) {
            boolean checkSelf = idSet.contains(id);
            if(CollectionUtils.isEmpty(children)) {
                this.status = checkSelf ? 1 : 0;
                return true;
            }
            int count = 0;
            for(TreeNode tn : children) {
                if(checkSelf || idSet.contains(tn.getId())) {
                    tn.setStatus(1);
                    count++;
                }
            }
            if(children.size() == count) {
                this.status = 1;
                return true;
            }
            return false;
        }
    }

    private LearnTestPaper getPaper(TestPaperVO.TestPaperBuildReq req,DzSubject dzSubject,LearnTest learnTest){
        LearnPaper paper = new LearnPaper();
        paper.setSubjectId(req.getSubjectId());
        String paperName = StringUtils.isBlank(req.getName()) ? learnTest.getName() + "_" + (null==dzSubject?"":dzSubject.getSubjectName()) : req.getName();
        paper.setPaperName(paperName.length() > 254 ? paperName.substring(0, 254) : paperName);
        paper.setYear(Calendar.getInstance().get(Calendar.YEAR)); // TODO  年度
        paper.setPaperType(PaperType.Test.name());
        paper.setDirectKey("");
        paper.setStatus(PaperStatus.Valid.getVal());
        paper.setRelateId(req.getBatchId().longValue());
        paper.setPaperInfo(JSONObject.toJSONString(req));
        paper.setFenshu(0);
        if("FullHand".equals(req.getBuildType())) {
            saveHandPaper(paper, req.getQuestions());
        } else {
            Pair<LearnPaper, List<LearnPaperQuestion>> paperResult = paperService.buildPaper(null, paper, req.getPaperDef());
            paperService.savePaper(paperResult.getKey(), paperResult.getValue());
        }
        LearnTestPaper testPaper = new LearnTestPaper();
        testPaper.setBuildType(req.getBuildType());
        testPaper.setBatchId(req.getBatchId());
        testPaper.setExamType(req.getExamType());
        testPaper.setSubjectId(req.getSubjectId());
        testPaper.setDirectKey("");
        testPaper.setPaperId(paper.getId());
        testPaper.setConditions(JSONObject.toJSONString(req));
        testPaper.setCreatorId(SecurityUtils.getUserId());
        testPaper.setTeacherId(req.getTeacherId());
        learnTestPaperMapper.insertLearnTestPaper(testPaper);
        return testPaper;
    }

    /**
     * 根据知识点自由组卷
     * @param req
     * @return
     */
    public String buildPapersFullByKnowledge(TestPaperVO.TestPaperBuildReq req) {

        DzSubject dzSubject = dzSubjectService.selectDzSubjectBySubjectId(req.getSubjectId());
        LearnTest learnTest = learnTestService.selectLearnTestByBatchId(req.getBatchId());
        // 生成试卷
        LearnTestPaper testPaper = getPaper( req, dzSubject, learnTest);

        updateBuildInfo(req.getTeacherId(), new TestRecordCond(req.getBuildType(), req.getBatchId(), req.getClassId(), req.getSubjectId(), null, null));
        return "";
    }

    /**
     * 发送试卷给学生
     * @param req
     * @param paperId
     * @return
     */
    public String sendPaper(Long testPaperId,List<Long> cardIds) {
        LearnTestPaper testPaper = learnTestPaperMapper.selectLearnTestPaperById(testPaperId);
        if (null == testPaper){
            throw new ValidationException("试卷不存在");
        }
        Long paperId = testPaper.getPaperId();

        if (CollectionUtils.isEmpty(cardIds)) {
            throw new ValidationException("未选择发送的学生");
        }

        // 通过 cardIds 查询用户，然后通过 userId 查询学生
        DzCards dzCards = new DzCards();
        dzCards.setCardIds(cardIds);
        List<CardStudentDTO> studentList = dzCardsService.selectDzCardsList3(dzCards);

        if(CollectionUtils.isEmpty(studentList)) {
            throw new ValidationException("无学生可发布");
        }

        // 查询已发布的学生ID列表（根据paperId）
        LearnTestStudent queryCondition = new LearnTestStudent();
        queryCondition.setPaperId(paperId);
        List<LearnTestStudent> publishedStudents = learnTestStudentMapper.selectLearnTestStudentList(queryCondition);
        Set<Long> publishedStudentIds = publishedStudents.stream()
                .map(LearnTestStudent::getStudentId)
                .collect(Collectors.toSet());

//        LearnTestPaper learnTestPaper = learnTestPaperMapper.selectLearnTestPaperByPaperId(paperId);
        for(CardStudentDTO student : studentList) {
            // 如果学生已经发布过，跳过
            if (publishedStudentIds.contains(student.getUserId())) {
                continue;
            }

            LearnTestStudent lts = new LearnTestStudent();
            lts.setBatchId(testPaper.getBatchId());
            lts.setStudentId(student.getUserId());
            lts.setBuildType(testPaper.getBuildType());
            lts.setSubjectId(testPaper.getSubjectId());
            lts.setDirectKey("");
            lts.setPaperId(paperId);
            lts.setCampusId(student.getCampusId());
            lts.setSchoolId(student.getSchoolId());
            lts.setClassId(student.getClassId());
            lts.setStatus(ExamineeStatus.Sign.getVal());
            lts.setCreatorId(SecurityUtils.getUserId());
            learnTestStudentMapper.insertLearnTestStudent(lts);
        }
        return "";
    }
}
