package com.sundot.airport.exam.logic.manage.service.impl;

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONArray;
import com.github.pagehelper.PageInfo;
import com.sundot.airport.common.exception.ServiceException;
import com.sundot.airport.exam.common.*;
import com.sundot.airport.exam.common.manager.QuesManager;
import com.sundot.airport.exam.common.model.result.QuesOptResult;
import com.sundot.airport.exam.common.service.*;
import com.sundot.airport.exam.domain.*;
import com.sundot.airport.exam.dto.QuestionImportDTO;
import com.sundot.airport.exam.enums.*;
import com.sundot.airport.exam.logic.manage.model.params.*;
import com.sundot.airport.exam.logic.manage.model.params.sub.QuesOptionParam;
import com.sundot.airport.exam.logic.manage.model.result.QuesAddResult;
import com.sundot.airport.exam.logic.manage.model.result.QuesGetResult;
import com.sundot.airport.exam.logic.manage.model.result.QuesPageResult;
import com.sundot.airport.exam.logic.manage.model.result.sub.QuesOptionResult;
import com.sundot.airport.exam.logic.manage.service.QuesBizService;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;


/**
 * <b>功能名：QuesBizServiceImpl</b><br>
 * <b>说明：</b>题目业务处理实现类<br>
 * <b>著作权：</b> Copyright (C) 2021 HUIFANEDU  CORPORATION<br>
 * <b>修改履历：</b><br>
 *
 * @author 2021-06-21 liangxiao
 */
@Service
public class QuesBizServiceImpl implements QuesBizService {

    private static final Logger log = LoggerFactory.getLogger(QuesBizServiceImpl.class);

    @Resource
    private QuesService quesService;
    @Resource
    private QuesOptService quesOptService;
    @Resource
    private QuesCatService quesCatService;
    @Resource
    private QuesManager quesManager;
    @Resource
    private TestPaperQuesService testPaperQuesService;
    @Resource
    private TestPaperService testPaperService;
    @Resource
    private IdUtils idUtils;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public HttpResult<QuesAddResult> add(QuesAddParam param) {
        log.info(String.format("QuesBizServiceImpl.add:新增题目:param=%s", param.toString()));

        // 检查题目分类信息是否正确
        if (StrUtil.isNotBlank(param.getQcId())) {
            Optional<QuesCat> quesCatOpt = quesCatService.get(param.getQcId());
            CheckUtil.boolCheck(!quesCatOpt.isPresent(), CloudSchoolBizExceptionEnum.QUES_CAT_NOT_FOUND);
        }

        // 题干对象
        Ques entity = new Ques();
        BeanUtils.copyProperties(param, entity);
        // 设置题目的业务id
        quesService.setServiceId(entity);

        // 选项参数转换为entity
        List<QuesOpt> quesOptList = transformQuesOpts(param.getOption(), entity.getQuId());
        log.info(String.format("QuesBizServiceImpl.add:新增题目的选项:quesOptList.size=%d", quesOptList.size()));
        // 选项的验证
        validateParam(entity, quesOptList);

        //计算题目选项数目，填空题计算空数
        int sum = getOptSize(param.getQuType(), quesOptList);
        entity.setQuOptCount(sum);

        // 保存题干数据
        Optional<Ques> insertOpt = quesService.insertWithoutServiceId(entity);
        // 新增检查
        CheckUtil.boolCheck(!insertOpt.isPresent(), BizExceptionEnum.OPERATION_INSERT_ERROR);
        // 保存选项内容
        quesOptService.insertList(quesOptList);

        // 封装结果
        QuesAddResult result = new QuesAddResult();
        result.setQuId(entity.getQuId());
        return HttpResult.success(result);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public HttpResult<Boolean> delete(QuesDelParam param) {
        // 根据题干id获取同步修改试卷表的分数
        List<TestPaperQues> testPaperQuesList = testPaperQuesService.byQuIdFindTestPaperQuesList(param.getQuIds());
        //根据试卷id分组，key:tpId,value: 试卷题目计分
        Map<String, List<TestPaperQues>> testPaperQuesMap = testPaperQuesList.stream().collect(Collectors.groupingBy(TestPaperQues::getTpId));
        //获取试卷id列表
        Set<String> tpIds = testPaperQuesMap.keySet();
        //根据试卷id列表获取试卷列表
        List<TestPaper> testPaperList = testPaperService.byTpIdsFindTestPaper(new ArrayList<>(tpIds));
        //根据试卷idf分组 key:tpId value:试卷
        Map<String, List<TestPaper>> testPaperMap = testPaperList.stream().collect(Collectors.groupingBy(TestPaper::getTpId));
        //根据试卷id，进行减分操作
        List<TestPaper> updateTestPaperList = new ArrayList<>();
        tpIds.forEach(tpId -> {
            List<TestPaperQues> testPaperQuesGroup = testPaperQuesMap.getOrDefault(tpId, Collections.emptyList());
            List<TestPaper> testPaperGroup = testPaperMap.getOrDefault(tpId, Collections.emptyList());
            if (CollectionUtils.isNotEmpty(testPaperQuesGroup) && CollectionUtils.isNotEmpty(testPaperGroup)) {
                TestPaperQues testPaperQues = testPaperQuesGroup.get(0);
                TestPaper testPaper = testPaperGroup.get(0);
                testPaper.setTpTotalScore(testPaper.getTpTotalScore() - testPaperQues.getTpqTotalScore());
                testPaper.setTpQuesNum(testPaper.getTpQuesNum() - 1);
                //获得修改试题删除后得试卷分数及题目数
                updateTestPaperList.add(testPaper);
            }
        });
        //批量更新试卷
        boolean testPaper = testPaperService.batchUpdate(updateTestPaperList);
        // 批量删除题干数据
        boolean ques = quesService.deleteList(param.getQuIds());
        // 根据题目去删除对应的选项列表
        boolean quesOpt = quesOptService.byQuIdsDeleteList(param.getQuIds());
        // 去删除试卷中关联的题目计分规则记录
        boolean tpQues = testPaperQuesService.byQuIdsDeleteList(param.getQuIds());
        log.info(String.format("QuesBizServiceImpl.delete:删除题目:param=%s,ques=%b,quesOpt=%b,tpQues=%s",
                param.toString(), ques, quesOpt, tpQues));
        return HttpResult.success(ques && quesOpt);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public HttpResult<Boolean> update(QuesUpdParam param) {
        log.info(String.format("QuesBizServiceImpl.update:更新题目:param=%s", param.toString()));

        // 检查题目分类信息是否正确
        if (StrUtil.isNotBlank(param.getQcId())) {
            Optional<QuesCat> quesCatOpt = quesCatService.get(param.getQcId());
            CheckUtil.boolCheck(!quesCatOpt.isPresent(), CloudSchoolBizExceptionEnum.QUES_CAT_NOT_FOUND);
        }

        // 题干对象
        Ques entity = new Ques();
        BeanUtils.copyProperties(param, entity);

        // 根据题目删除旧选项
        boolean quesOpt = quesOptService.byQuIdsDeleteList(Collections.singletonList(entity.getQuId()));
        log.info(String.format("QuesBizServiceImpl.update:更新题目-就选项删除结果:param=%s,quesOpt=%b", param.toString(), quesOpt));

        // 选项参数转换为entity
        List<QuesOpt> quesOptList = transformQuesOpts(param.getOption(), entity.getQuId());
        log.info(String.format("QuesBizServiceImpl.update:更新题目-新的的选项:quesOptList.size=%d", quesOptList.size()));
        // 选项的验证
        validateParam(entity, quesOptList);

        //计算题目选项数目，填空题计算空数
        int sum = getOptSize(param.getQuType(), quesOptList);
        entity.setQuOptCount(sum);
        // 更新题干数据
        quesService.update(entity);
        // 保存选项内容
        quesOptService.insertList(quesOptList);

        return HttpResult.success(true);
    }

    @Override
    public HttpResult<QuesGetResult> get(String qcId) {
        // 根据题干业务id列表查询关联的选项信息并构建结果（包含选项）
        Map<String, List<QuesOptResult>> map = quesManager.byTpIdsFindAndBuildQoResult(Collections.singletonList(qcId));

        // 查询题干
        Optional<Ques> ques = quesService.get(qcId);
        CheckUtil.boolCheck(!ques.isPresent(), CloudSchoolBizExceptionEnum.OBJ_NOT_FOUND);
        // 查询分类
        Optional<QuesCat> quesCat = quesCatService.get(ques.get().getQcId());

        // 封装结果
        QuesGetResult result = new QuesGetResult();
        BeanUtils.copyProperties(ques.get(), result);
        // 设置分类名称
        result.setQcName(quesCat.map(QuesCat::getQcName).orElse(null));
        // 设置选项内容
        result.setOption(transformQo(map.getOrDefault(qcId, Collections.emptyList())));
        return HttpResult.success(result);
    }

    @Override
    public HttpResult<PageData<QuesPageResult>> pageList(QuesPageParam param) {
        // 题干分页条件查询结果
        PageInfo<Ques> pageInfo = quesService.pageList(param);

        // 选项查询结果，key是quId，value是对应的选项
        Map<String, List<QuesOptResult>> listMap;
        // 判断结果是否是简单模式
        if (param.getEasy()) {
            listMap = new HashMap<>(1);
        } else {
            // 题干分页结果的业务id列表
            List<String> quIds = pageInfo
                    .getList()
                    .stream()
                    .map(Ques::getQuId)
                    .collect(Collectors.toList());

            // 根据题干业务id列表查询关联的选项信息并构建结果（包含选项），然后用试卷业务id进行分组
            listMap = quesManager.byTpIdsFindAndBuildQoResult(quIds);
        }

        // 组装分页数据
        PageData<QuesPageResult> pageData = new PageData<>(pageInfo);
        pageData.setList(pageInfo
                .getList()
                .stream()
                .map(res -> new QuesPageResult() {
                    {
                        // 初始化结果
                        BeanUtils.copyProperties(res, this);
                        // 初始化选项列表
                        this.setOption(transformQo(listMap.getOrDefault(res.getQuId(), Collections.emptyList())));
                    }
                })
                .collect(Collectors.toList()));
        return HttpResult.success(pageData);
    }

    @Override
    public HttpResult<Boolean> batchCatUpdate(QuesUpdCatParam param) {
        log.info(String.format("QuesBizServiceImpl.batchCatUpdate:更新题目分类:param=%s", param.toString()));
        // 更新题干数据
        return HttpResult.success(quesService.batchCatUpdate(param.getQuIds(), param.getQcId()));
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void importData(List<QuestionImportDTO> list) {
        for (QuestionImportDTO dto : list) {
            validateImportData(dto);
            processQuestion(dto);
        }
    }

    /**
     * 校验数据
     * @param dto
     */
    private void validateImportData(QuestionImportDTO dto) {
        if (com.sundot.airport.common.utils.StringUtils.isBlank(dto.getQuestionType())) {
            throw new IllegalArgumentException("题型不能为空");
        }
        if (com.sundot.airport.common.utils.StringUtils.isBlank(dto.getContent())) {
            throw new IllegalArgumentException("题目正文不能为空");
        }
        if (com.sundot.airport.common.utils.StringUtils.isBlank(dto.getAnswer())) {
            throw new IllegalArgumentException("答案不能为空");
        }
    }

    /**
     * 导入数据
     * @param dto
     */
    private void processQuestion(QuestionImportDTO dto) {
        // 转换为枚举类型
        QuestionTypeEnum questionType = QuestionTypeEnum.fromDisplayName(dto.getQuestionType());

        // 查询分类
        String qcId = resolveQuestionCategory(dto.getKnowledgePoint());

        // 保存题干
        Ques question = buildQuestionDO(dto, questionType, qcId);
        quesService.insert(question);


        // 保存选项
        saveQuestionOptions(question.getQuId(), dto, questionType);
    }

    /**
     * 查询分类
     * @param knowledgePoint 知识点
     * @return
     */
    private String resolveQuestionCategory(String knowledgePoint) {
        if (StringUtils.isBlank(knowledgePoint)) {
            return "";
        }

        // 查询是否已存在该分类
        Optional<QuesCat> nameCheck = quesCatService.byNameCheckExistsGet(knowledgePoint);
        if (nameCheck.isPresent()) {
            return nameCheck.get().getQcId();
        }

        // 创建新分类
        QuesCat newCategory = new QuesCat();
        newCategory.setQcName(knowledgePoint);
        newCategory.setDelStatus(LogicStatusEnum.NORMAL.getCode());

        quesCatService.insert(newCategory);
        return newCategory.getQcId();
    }


    /**
     * 保存选项
     * @param quId
     * @param dto
     * @param questionType
     */
    private void saveQuestionOptions(String quId, QuestionImportDTO dto,
                                     QuestionTypeEnum questionType) {
        List<OptionData> options = buildOptions(dto, questionType);

        for (int i = 0; i < options.size(); i++) {
            QuesOpt option = buildOptionDO(quId, options.get(i), i);
            quesOptService.insert(option);
        }
    }

    /**
     * 拼接题目选项表
     * @param quId
     * @param optionData
     * @param sort
     * @return
     */
    private QuesOpt buildOptionDO(String quId, OptionData optionData,
                                         int sort) {
        QuesOpt option = new QuesOpt();
        option.setQoId(idUtils.generateId("QO"));
        option.setQoCorrect(optionData.isCorrect);
        option.setQoOptionType(QuesOptionTypeEnum.TEXT);
        option.setQoOption(optionData.content);
        option.setSort(sort);
        option.setQuId(quId);
        option.setDelStatus(LogicStatusEnum.NORMAL.getCode());
        return option;
    }

    /**
     * 把每一个选择转化为一条数据
     * @param dto
     * @param questionType
     * @return
     */
    private List<OptionData> buildOptions(QuestionImportDTO dto, QuestionTypeEnum questionType) {
        if (questionType == QuestionTypeEnum.JUDGMENT) {
            return Arrays.asList(
                    new OptionData("正确", "对".equals(dto.getAnswer())),
                    new OptionData("错误", "错".equals(dto.getAnswer()))
            );
        }

        List<String> validOptions = getAllOptions(dto).stream()
                .filter(StringUtils::isNotBlank)
                .collect(Collectors.toList());

        if (CollectionUtils.isEmpty(validOptions)) {
            throw new ServiceException("选择题必须提供选项内容");
        }

        Set<Integer> correctIndexes = parseAnswer(dto.getAnswer(), validOptions.size());

        List<OptionData> options = new ArrayList<>();
        for (int i = 0; i < validOptions.size(); i++) {
            options.add(new OptionData(validOptions.get(i), correctIndexes.contains(i)));
        }
        return options;
    }

    /**
     * 正确答案
     * @param answer
     * @param optionCount
     * @return
     */
    private Set<Integer> parseAnswer(String answer, int optionCount) {
        Set<Integer> correctIndexes = new HashSet<>();
        if (StringUtils.isBlank(answer)) return correctIndexes;

        String upperAnswer = answer.toUpperCase(Locale.ROOT);
        for (char c : upperAnswer.toCharArray()) {
            int index = c - 'A';
            if (index >= 0 && index < optionCount) {
                correctIndexes.add(index);
            } else {
                log.warn("无效答案字符: {}, 选项总数: {}", c, optionCount);
                throw new ServiceException("答案选项超出范围: " + c);
            }
        }
        return correctIndexes;
    }


    private static class OptionData {
        final String content;
        final boolean isCorrect;

        OptionData(String content, boolean isCorrect) {
            this.content = content;
            this.isCorrect = isCorrect;
        }
    }

    /**
     * 拼接题干对象
     * @param dto
     * @param questionType
     * @return
     */
    private Ques buildQuestionDO(QuestionImportDTO dto, QuestionTypeEnum questionType,String qcId) {
        Ques question = new Ques();
        question.setQuId(idUtils.generateId("Q"));
        question.setQuType(QuesTypeEnum.fromDisplayName(questionType.dbValue));
        question.setQuContent(dto.getContent());
        question.setQuOptCount(calculateOptionCount(dto, questionType));
        question.setQcId(qcId);
        question.setDelStatus(LogicStatusEnum.NORMAL.getCode());
        return question;
    }

    /**
     * 统计个数
     * @param dto
     * @param questionType
     * @return
     */
    private int calculateOptionCount(QuestionImportDTO dto, QuestionTypeEnum questionType) {
        if (questionType == QuestionTypeEnum.JUDGMENT) {
            return 2;
        }
        return (int) getAllOptions(dto).stream()
                .filter(StringUtils::isNotBlank)
                .count();
    }

    /**
     * 选项转化为list
     * @param dto
     * @return
     */
    private List<String> getAllOptions(QuestionImportDTO dto) {
        return Arrays.asList(
                dto.getOptionA(), dto.getOptionB(), dto.getOptionC(),
                dto.getOptionD(), dto.getOptionE(), dto.getOptionF()
        );
    }


    /**
     * <b>方法名: </b> transformQuesOpts <br>
     * <b>说明: </b> 选项参数转换为entity <br>
     *
     * @param optionParams java.util.List<com.sundot.airport.logic.manage.model.params.sub.QuesOptionParam> 题目选项参数类列表
     * @param quId         java.lang.String 题目业务id
     * @return java.util.List<com.sundot.airport.exam.domain.QuesOpt><br>
     * <b>修改履历: </b>
     * @author 2021/7/20 liangxiao
     */
    private List<QuesOpt> transformQuesOpts(List<QuesOptionParam> optionParams, String quId) {
        // 选项sort排序信号量
        int[] sortSema = {0};
        // 保存新的选项对象
        return optionParams
                .stream()
                .map(opt -> new QuesOpt() {
                    {
                        BeanUtils.copyProperties(opt, this);
                        // 记录关联的关系
                        if (this.getQoConnOptType() == null) {
                            this.setQoConnOptType(QuesOptionTypeEnum.TEXT);
                        }
                        this.setQuId(quId);
                        // 设置选项的排序
                        this.setSort(sortSema[0]++);
                    }
                })
                .collect(Collectors.toList());
    }

    /**
     * <b>方法名: </b> validateParam <br>
     * <b>说明: </b> 题目选项的规则验证 <br>
     *
     * @param entity      com.sundot.airport.exam.domain.Ques 题目题干对象
     * @param quesOptList java.util.List<com.sundot.airport.exam.domain.QuesOpt> 题目选项列表
     * @return void<br>
     * <b>修改履历: </b>
     * @author 2021/6/22 liangxiao
     */
    private void validateParam(Ques entity, List<QuesOpt> quesOptList) {
        // 选项的验证
        long count = quesOptList.stream().filter(QuesOpt::getQoCorrect).count();
        switch (entity.getQuType()) {
            case SINGLE:
                // 选项至少一个
                CheckUtil.boolCheck(quesOptList.size() < 1, CloudSchoolBizExceptionEnum.AT_LEAST_ONE_OPTION);
                // 选项至多十个
                CheckUtil.boolCheck(quesOptList.size() > 10, CloudSchoolBizExceptionEnum.MAX_OPTIONS_TO_10);
                // 只允许一个答案
                CheckUtil.boolCheck(count != 1, CloudSchoolBizExceptionEnum.INCORRECT_ANSWER);
                break;
            case MULTIPLE:
                // 选项至少一个
                CheckUtil.boolCheck(quesOptList.size() < 2, CloudSchoolBizExceptionEnum.AT_LEAST_ONE_OPTION);
                // 选项至多十个
                CheckUtil.boolCheck(quesOptList.size() > 10, CloudSchoolBizExceptionEnum.MAX_OPTIONS_TO_10);
                // 至少两个答案
                CheckUtil.boolCheck(count < 2, CloudSchoolBizExceptionEnum.INCORRECT_ANSWER);
                break;
            case JUDGMENT:
                // 必须有两个选项
                CheckUtil.boolCheck(quesOptList.size() != 2, CloudSchoolBizExceptionEnum.MUST_BE_TWO_OPTION);
                // 只允许一个答案
                CheckUtil.boolCheck(count != 1, CloudSchoolBizExceptionEnum.INCORRECT_ANSWER);
                break;
            case FILL_BLANK:
                // 选项至少一个
                CheckUtil.boolCheck(quesOptList.size() == 0, CloudSchoolBizExceptionEnum.AT_LEAST_ONE_OPTION);
                // 选项至多十个
                CheckUtil.boolCheck(quesOptList.size() > 10, CloudSchoolBizExceptionEnum.MAX_OPTIONS_TO_10);
                break;
            case CONNECTION:
                // 选项至少一个
                CheckUtil.boolCheck(quesOptList.size() == 0, CloudSchoolBizExceptionEnum.AT_LEAST_ONE_OPTION);
                // 选项至多十个
                CheckUtil.boolCheck(quesOptList.size() > 10, CloudSchoolBizExceptionEnum.MAX_OPTIONS_TO_10);
                //判断连线题，被连线选项是否为空
                boolean checkQoConnOpt = quesOptList.stream().map(QuesOpt::getQoConnOpt).anyMatch(StringUtils::isBlank);
                CheckUtil.boolCheck(checkQoConnOpt, CloudSchoolBizExceptionEnum.CONNECTION_OPTIONS_NOT_NULL);
                break;
            default:
                // 兜底后续添加未处理的枚举
                CheckUtil.boolCheck(true, CloudSchoolBizExceptionEnum.QUES_TYPE_ERROR);
        }
    }

    /**
     * <b>方法名: </b> transformQo <br>
     * <b>说明: </b> 将common的题目选项表封装结果转为logic下的封装结果 <br>
     *
     * @param commonTpq java.util.List<com.sundot.airport.exam.common.model.result.QuesOptResult> common的题目选项表封装结果
     * @return java.util.List<com.sundot.airport.logic.manage.model.result.QuesOptionResult><br>
     * <b>修改履历: </b>
     * @author 2021/7/6 liangxiao
     */
    private List<QuesOptionResult> transformQo(List<QuesOptResult> commonTpq) {
        return commonTpq
                .stream()
                .map(i -> new QuesOptionResult() {
                    {
                        BeanUtils.copyProperties(i, this);
                    }
                })
                .collect(Collectors.toList());
    }

    /**
     * <b>方法名: </b> getOptSize <br>
     * <b>说明: 计算题目选项数，填空题计算空数</b> <br>
     *
     * @param quType      题目类型
     * @param quesOptList 题目选项列表
     * @return int<br>
     * <b>修改履历: </b>
     * @author 2021/7/27 lity
     */
    private int getOptSize(QuesTypeEnum quType, List<QuesOpt> quesOptList) {
        int sum;
        if (QuesTypeEnum.FILL_BLANK.equals(quType)) {
            //计算填空题填空数
            sum = quesOptList.stream().map(a -> BigDecimal.valueOf(JSONArray.parseArray(a.getQoOption(), String.class).size())).reduce(BigDecimal.ZERO, BigDecimal::add).intValue();
        } else {
            sum = quesOptList.size();
        }
        return sum;
    }
}
