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

import com.alibaba.fastjson.JSONArray;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ynfy.buss.exam.paper.dto.PaperDTO;
import com.ynfy.buss.exam.paper.enmus.JoinType;
import com.ynfy.buss.exam.paper.enmus.LevelType;
import com.ynfy.buss.exam.paper.entity.Paper;
import com.ynfy.buss.exam.paper.mapper.PaperMapper;
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.paperquestionanswer.entity.PaperQuestionAnswer;
import com.ynfy.buss.exam.paperquestionanswer.service.IPaperQuestionAnswerService;
import com.ynfy.buss.exam.paperruledetail.entity.PaperRuleDetail;
import com.ynfy.buss.exam.paperruledetail.service.IPaperRuleDetailService;
import com.ynfy.buss.exam.paperrulegroup.entity.PaperRuleGroup;
import com.ynfy.buss.exam.paperrulegroup.service.IPaperRuleGroupService;
import com.ynfy.buss.exam.question.dto.QuestionConditionDTO;
import com.ynfy.buss.exam.question.dto.QuestionDTO;
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.repository.service.IRepositoryService;
import com.ynfy.common.utils.ThreadPoolUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.jeecg.common.exception.JeecgBootException;
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.concurrent.Callable;
import java.util.concurrent.FutureTask;
import java.util.stream.Collectors;


/**
 * @Description: paper
 * @Author: jeecg-boot
 * @Date: 2023-02-22
 * @Version: V1.0
 */
@Slf4j
@Service
public class PaperServiceImpl extends ServiceImpl<PaperMapper, Paper> implements IPaperService {

    @Autowired
    private IPaperRuleGroupService paperRuleGroupService;

    @Autowired
    private IPaperRuleDetailService paperRuleDetailService;

    @Autowired
    private IQuestionService questionService;

    @Autowired
    private IPaperQuestionService paperQuestionService;

    @Autowired
    private IPaperQuestionAnswerService paperQuestionAnswerService;

    @Autowired
    private IRepositoryService repositoryService;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void save(PaperDTO paperDTO) {
        // 校验数据
        this.checkData(paperDTO);
        Paper paper = new Paper();
        BeanUtils.copyProperties(paperDTO, paper);
        //保存试卷
        this.saveOrUpdate(paper);
        //保存试卷规则组和规则明细以及选的试题
        paperRuleGroupService.saveAll(paper, paperDTO.getGroupList());
    }

    /**
     * 校验试卷信息
     *
     * @throws Exception
     */
    public void checkData(PaperDTO dto) {
        if (StringUtils.isEmpty(dto.getTitle())) {
            throw new JeecgBootException("试卷名称不能为空！");
        }
        if (Objects.isNull(dto.getJoinType())) {
            throw new JeecgBootException("组卷方式不能为空！");
        }
        if (Objects.isNull(dto.getTotalScore()) || dto.getTotalScore().intValue() == 0) {
            throw new JeecgBootException("试卷总分不能为空！");
        }
        if (Objects.isNull(dto.getQuestionCount()) || dto.getQuestionCount().intValue() == 0) {
            throw new JeecgBootException("试卷总题数不能为空！");
        }

        checkFillOption(dto);
    }

    /**
     * 如果多选题配置了漏选也给分，汇总单个选项分值和题目分值比较，不允许超出
     * 如果填空题配置了按空给分，汇总单个选项分值和题目分值比较，不允许超出
     *
     * @param dto
     */
    public void checkFillOption(PaperDTO dto) {
        List<PaperRuleGroup> groupList = dto.getGroupList();
        if (!CollectionUtils.isEmpty(groupList)) {
            for (PaperRuleGroup group : groupList) {
                //如果多选题配置了漏选也给分，汇总单个选项分值和题目分值比较，不允许超出
                if (QuestionType.MULTI.getCode().equals(group.getQuestionType()) && !CollectionUtils.isEmpty(group.getQuestionList())
                        && !Objects.isNull(group.getCanMissOption()) && group.getCanMissOption()) {
                    checkPathScore(group, QuestionType.MULTI.getValue());
                    //如果填空题配置了按空给分，汇总单个选项分值和题目分值比较，不允许超出
                } else if (QuestionType.BLANK.getCode().equals(group.getQuestionType()) && !CollectionUtils.isEmpty(group.getQuestionList())
                        && !Objects.isNull(group.getCanBlankOption()) && group.getCanBlankOption()) {
                    checkPathScore(group, QuestionType.BLANK.getValue());
                }
            }
        }
    }

    /**
     * 检查答案选项分值
     *
     * @param group
     */
    public void checkPathScore(PaperRuleGroup group, String questionTypeName) {
        int index = 1;
        for (Question question : group.getQuestionList()) {
            double sum = question.getAnswerList().stream().filter(q -> !Objects
                    .isNull(q.getPathScore()) && q.getPathScore() > 0).mapToDouble(QuestionAnswer::getPathScore).sum();
            if (sum == 0) {
                throw new JeecgBootException(questionTypeName + "第" + index + "题，题目：" + (question.getContent().length() > 20
                        ? question.getContent().substring(0, 20) : question.getContent()) + "...。答案选项给分总和不能为0！");
            }
            if (question.getScore() < sum) {
                throw new JeecgBootException(questionTypeName + "第" + index + "题，题目：" + (question.getContent().length() > 20
                        ? question.getContent().substring(0, 20) : question.getContent()) + "...。答案选项给分溢出！");
            }
            index++;
        }
    }


    /**
     * 生成试卷试题
     *
     * @param paperId
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public List<PaperQuestion> generateQuestion(String paperId) {
        long start = System.currentTimeMillis();
        //获取规则组及规则明细
        List<PaperRuleGroup> ruleGroups = paperRuleGroupService.listGroupAndDetail(paperId);
        //从规则组中获取规则明细
        List<PaperRuleDetail> ruleDetails = listRuleDetail(ruleGroups);

        //生成题目查询条件
        List<QuestionConditionDTO> dtoList = generateQuestionCondition(ruleDetails);
        //根据查询条件一次性获取题目，防止每个规则明细都查询一次，降低数据库负载，提高性能
        List<Question> ruleQuestions = questionService.listQuestionByCondition(dtoList);

        //按每组规则生成试题
        Map<String, List<Question>> groupQuestionMap = generateGroupQuestionMap(ruleGroups, ruleQuestions);
        //生成试卷试题
        List<PaperQuestion> paperQuestions = generatePaperQuestions(paperId, ruleGroups, groupQuestionMap);
        log.info("生成试卷试题完成，耗时:" + (System.currentTimeMillis() - start) / 1000 + "秒");
        //试卷试题排序
        return paperQuestionService.sort(paperQuestions);
    }

    /**
     * 从规则组中获取规则明细
     *
     * @param ruleGroups
     * @return
     */
    public List<PaperRuleDetail> listRuleDetail(List<PaperRuleGroup> ruleGroups) {
        List<PaperRuleDetail> ruleDetails = new ArrayList<>();
        if (!CollectionUtils.isEmpty(ruleGroups)) {
            ruleGroups.stream().forEach(g -> ruleDetails.addAll(g.getRuleList()));
        }
        return ruleDetails;
    }

    /**
     * 生成题目查询条件
     *
     * @param ruleDetails
     * @return
     */
    public List<QuestionConditionDTO> generateQuestionCondition(List<PaperRuleDetail> ruleDetails) {
        List<QuestionConditionDTO> dtoList = new ArrayList<>();
        if (!CollectionUtils.isEmpty(ruleDetails)) {
            ruleDetails.stream().forEach(r -> {
                QuestionConditionDTO dto = new QuestionConditionDTO();
                BeanUtils.copyProperties(r, dto);
                if (!dtoList.contains(dto)) {
                    dtoList.add(dto);
                }
            });
        }
        return dtoList;
    }

    /**
     * 按每组规则生成试题
     *
     * @param ruleGroups
     * @return
     */
    public Map<String, List<Question>> generateGroupQuestionMap(List<PaperRuleGroup> ruleGroups,
                                                                List<Question> ruleQuestions) {
        long start = System.currentTimeMillis();
        Map<String, List<Question>> groupQuestionMap = new LinkedHashMap<>();
        if (!CollectionUtils.isEmpty(ruleGroups)) {
            //根据规则明细，多线程并发生成试题
            Map<String, FutureTask<List<Question>>> resultMap = generateQuestion(ruleGroups, ruleQuestions);

            //获取试题并按分组归类
            for (Map.Entry<String, FutureTask<List<Question>>> entry : resultMap.entrySet()) {
                try {
                    String key = entry.getKey();
                    String[] strings = key.split("-");
                    String groupId = strings[0];
                    List<Question> questionList = entry.getValue().get();
                    List<Question> questions = groupQuestionMap.get(groupId);
                    if (CollectionUtils.isEmpty(questions)) {
                        questions = new ArrayList<>();
                    }
                    questions.addAll(questionList);
                    groupQuestionMap.put(groupId, questions);
                } catch (Exception e) {
                    log.error("生成：{} 试题时出错：{}", entry.getKey(), e.getMessage());
                    e.printStackTrace();
                }
            }
        }
        log.info("多线程并发生成试题成功，耗时:" + (System.currentTimeMillis() - start) + "毫秒");
        return groupQuestionMap;
    }

    /**
     * 多线程并发生成试题
     *
     * @param ruleGroups
     * @return
     */
    public Map<String, FutureTask<List<Question>>> generateQuestion(List<PaperRuleGroup> ruleGroups, List<Question> ruleQuestions) {
        Map<String, FutureTask<List<Question>>> resultMap = new LinkedHashMap<>();
        for (PaperRuleGroup g : ruleGroups) {
            //组内规则明细排序
            List<PaperRuleDetail> details = g.getRuleList().stream().sorted(Comparator.comparing(PaperRuleDetail::getSort)).collect(Collectors.toList());
            for (PaperRuleDetail d : details) {
                Callable<List<Question>> genCall = () -> questionService.generateQuestionsByRule(d.getRepositoryId(),
                        g.getQuestionType(), d.getLevel(), d.getNum(), ruleQuestions);
                FutureTask<List<Question>> genTask = new FutureTask<>(genCall);
                ThreadPoolUtil.getExecutor().submit(genTask);
                resultMap.put(g.getId() + "-" + d.getId(), genTask);
            }
        }
        return resultMap;
    }

    /**
     * 生成试卷试题
     *
     * @param paperId
     * @param ruleGroups
     * @param groupQuestionMap
     * @return
     */
    public List<PaperQuestion> generatePaperQuestions(String paperId, List<PaperRuleGroup> ruleGroups, Map<String, List<Question>> groupQuestionMap) {
        Map<String, PaperRuleGroup> groupMap = ruleGroups.stream().collect(Collectors.toMap(PaperRuleGroup::getId, a -> a, (k1, k2) -> k1));
        //构建试卷试题
        List<PaperQuestion> paperQuestions = new ArrayList<>();
        for (Map.Entry<String, List<Question>> entity : groupQuestionMap.entrySet()) {
            PaperRuleGroup ruleGroup = groupMap.get(entity.getKey());
            List<Question> questions = entity.getValue();
            if (!CollectionUtils.isEmpty(questions)) {
                for (Question q : questions) {
                    PaperQuestion paperQuestion = new PaperQuestion();
                    paperQuestion.setPaperId(paperId);
                    paperQuestion.setQuestionId(q.getId());
                    paperQuestion.setQuestionType(ruleGroup.getQuestionType());
                    paperQuestion.setQuestionScore(ruleGroup.getPerScore());
                    paperQuestions.add(paperQuestion);
                }
            }
        }
        return paperQuestions;
    }

    /**
     * 查看试卷明细
     *
     * @param paperId
     */
    @Override
    public PaperDTO detail(String paperId) {
        Paper paper = getById(paperId);
        if (paper == null) {
            return null;
        }
        List<PaperRuleGroup> groups = paperRuleGroupService.listGroupAndDetail(paperId);

        PaperDTO paperDTO = new PaperDTO();
        BeanUtils.copyProperties(paper, paperDTO);
        if (!CollectionUtils.isEmpty(groups)) {
            if (paper.getJoinType().equals(JoinType.XT.getCode()) || paper.getJoinType().equals(JoinType.CT.getCode())) {
                joinTypeOfSelect(paperId, groups);
            } else if (paper.getJoinType().equals(JoinType.SJ.getCode())) {
                joinTypeOfRandom(groups);
            }
        }
        paperDTO.setGroupList(groups);
        return paperDTO;
    }

    /**
     * 选题方式返回
     *
     * @param paperId
     * @param groups
     */
    public void joinTypeOfSelect(String paperId, List<PaperRuleGroup> groups) {
        List<PaperQuestion> paperQuestions = paperQuestionService.listByPaperId(paperId);
        Map<String, List<PaperQuestion>> listMap = paperQuestions.stream().collect(Collectors
                .groupingBy(PaperQuestion::getGroupId, LinkedHashMap::new, Collectors.toList()));

        //获取多选题漏选分值
        List<PaperQuestionAnswer> paperQuestionAnswers = paperQuestionAnswerService.listByPaperId(paperId);
        Map<String, PaperQuestionAnswer> answerMap = paperQuestionAnswers.stream().collect(Collectors.toMap(PaperQuestionAnswer::getAnswerId, a -> a, (k1, k2) -> k1));

        for (PaperRuleGroup g : groups) {
            List<PaperQuestion> paperQuestionList = listMap.get(g.getId()).stream().sorted(Comparator
                    .comparing(PaperQuestion::getSort)).collect(Collectors.toList());
            List<String> questionIdList = paperQuestionList.stream().map(PaperQuestion::getQuestionId).collect(Collectors.toList());
            List<Question> questions = questionService.selectQuestionList(questionIdList, true, false);

            List<Question> sortQuestions = new ArrayList<>();
            paperQuestionList.stream().forEach(paperQuestion -> {
                List<Question> list = questions.stream().filter(question -> question.getId().equals(paperQuestion.getQuestionId())).collect(Collectors.toList());
                if (!CollectionUtils.isEmpty(list)) {
                    Question question = list.get(0);
                    question.setScore(paperQuestion.getQuestionScore());
                    //多选题回填漏选选项分值
                    fillMissOptionScore(g, question, answerMap);
                    //填空题按空设置分值
                    fillBlankOptionScore(g, question, answerMap);
                    sortQuestions.add(question);
                }
            });
            g.setQuestionList(sortQuestions);
        }
    }


    /**
     * 多选题回填漏选选项分值
     *
     * @param g
     * @param question
     * @param answerMap
     */
    public void fillMissOptionScore(PaperRuleGroup g, Question question, Map<String, PaperQuestionAnswer> answerMap) {
        //漏选也给分
        if (QuestionType.MULTI.getCode().equals(question.getType()) && !Objects.isNull(g.getCanMissOption()) && g.getCanMissOption()) {
            setPathScore(question, answerMap);
        }
    }

    /**
     * 填空题按空设置分值
     *
     * @param g
     * @param question
     * @param answerMap
     */
    public void fillBlankOptionScore(PaperRuleGroup g, Question question, Map<String, PaperQuestionAnswer> answerMap) {
        if (QuestionType.BLANK.getCode().equals(question.getType()) && !Objects.isNull(g.getCanBlankOption()) && g.getCanBlankOption()) {
            setPathScore(question, answerMap);
        }
    }

    /**
     * 设置答案选项分值
     *
     * @param question
     * @param answerMap
     */
    public void setPathScore(Question question, Map<String, PaperQuestionAnswer> answerMap) {
        List<QuestionAnswer> answerList = question.getAnswerList();
        if (!CollectionUtils.isEmpty(answerList) && !Objects.isNull(answerMap) && answerMap.size() > 0) {
            answerList.stream().forEach(a -> {
                PaperQuestionAnswer pqa = answerMap.get(a.getId());
                if (!Objects.isNull(pqa)) {
                    a.setPathScore(pqa.getPathScore());
                }
            });
        }
    }

    /**
     * 随机方式返回
     *
     * @param groups
     */
    public void joinTypeOfRandom(List<PaperRuleGroup> groups) {
        for (PaperRuleGroup g : groups) {
            //组内规则明细排序
            List<PaperRuleDetail> details = g.getRuleList().stream().sorted(Comparator.comparing(PaperRuleDetail::getSort)).collect(Collectors.toList());
            details.stream().forEach(detail -> {
                detail.setRepositoryName(repositoryService.getById(detail.getRepositoryId()).getTitle());
                detail.setLevel_dictText(LevelType.getByCode(detail.getLevel()).getValue());
            });
            g.setRuleList(details);
        }
    }

    /**
     * 删除试卷 试题 规则
     *
     * @param paperId
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void remove(String paperId) {
        paperRuleGroupService.remove(paperId);
        paperQuestionService.remove(paperId);
        paperRuleDetailService.remove(paperId);
        removeById(paperId);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void removeBatch(String ids) {
        String[] idArr = ids.split(",");
        paperRuleGroupService.removeByQuestionIds(idArr);
        paperQuestionService.removeByQuestionIds(idArr);
        paperRuleDetailService.removeByQuestionIds(idArr);
        removeByIds(Arrays.asList(idArr));
    }

    /**
     * 随机组卷抽取部分试题
     *
     * @return
     */
    @Override
    public List<Question> extractPartQuestion(String ruleDetail, String questionIds) {
        List<QuestionDTO> questionDTOS = JSONArray.parseArray(ruleDetail, QuestionDTO.class);
        List<PaperRuleDetail> ruleDetailList = new ArrayList<>();
        questionDTOS.forEach(dto -> {
            PaperRuleDetail detail = new PaperRuleDetail();
            BeanUtils.copyProperties(dto, detail);
            detail.setQuestionType(dto.getType());
            ruleDetailList.add(detail);
        });
        //生成题目查询条件
        List<QuestionConditionDTO> dtoList = generateQuestionCondition(ruleDetailList);
        List<Question> ruleQuestions = questionService.listQuestionByCondition(dtoList);

        if (StringUtils.isNotBlank(questionIds)) {
            List<String> questionIdList = Arrays.asList(questionIds.split(","));
            ruleQuestions = ruleQuestions.stream().filter(rq -> !questionIdList.contains(rq.getId())).collect(Collectors.toList());
        }
        List<Question> questionList = new ArrayList<>();
        if (!CollectionUtils.isEmpty(ruleDetailList)) {
            for (PaperRuleDetail detail : ruleDetailList) {
                questionList.addAll(questionService.generateQuestionsByRule(detail.getRepositoryId(), detail.getQuestionType(),
                        detail.getLevel(), detail.getNum(), ruleQuestions));
            }
        }
        if (!CollectionUtils.isEmpty(questionList)) {
            List<String> idList = questionList.stream().map(Question::getId).collect(Collectors.toList());
            return questionService.selectQuestionList(idList, true, false);
        }
        return null;
    }
}
