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

import cn.hutool.core.util.StrUtil;
import com.github.pagehelper.PageInfo;
import com.sundot.airport.exam.common.BizExceptionEnum;
import com.sundot.airport.exam.common.CheckUtil;
import com.sundot.airport.exam.common.HttpResult;
import com.sundot.airport.exam.common.PageData;
import com.sundot.airport.exam.common.manager.TestPaperManager;
import com.sundot.airport.exam.common.model.result.QuesOptResult;
import com.sundot.airport.exam.common.model.result.TestPaperQuesResult;
import com.sundot.airport.exam.common.service.QuesService;
import com.sundot.airport.exam.common.service.TestPaperCatService;
import com.sundot.airport.exam.common.service.TestPaperQuesService;
import com.sundot.airport.exam.common.service.TestPaperService;
import com.sundot.airport.exam.domain.Ques;
import com.sundot.airport.exam.domain.TestPaper;
import com.sundot.airport.exam.domain.TestPaperCat;
import com.sundot.airport.exam.domain.TestPaperQues;
import com.sundot.airport.exam.enums.CloudSchoolBizExceptionEnum;
import com.sundot.airport.exam.enums.QuesCompScoreTypeEnum;
import com.sundot.airport.exam.logic.manage.model.params.*;
import com.sundot.airport.exam.logic.manage.model.params.sub.TestPaperQuestionParam;
import com.sundot.airport.exam.logic.manage.model.result.TestPaperAddResult;
import com.sundot.airport.exam.logic.manage.model.result.TestPaperGetResult;
import com.sundot.airport.exam.logic.manage.model.result.TestPaperPageResult;
import com.sundot.airport.exam.logic.manage.model.result.sub.QuesOptionResult;
import com.sundot.airport.exam.logic.manage.model.result.sub.TestPaperQuestionResult;
import com.sundot.airport.exam.logic.manage.service.TestPaperBizService;
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.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;


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

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

    /**
     * 试卷名称复制时，用于匹配试卷名称的正则
     */
    private final Pattern FILE_NAME_PATTERN = Pattern.compile("^\\((\\d*)\\)$");

    @Resource
    private TestPaperService testPaperService;
    @Resource
    private TestPaperQuesService testPaperQuesService;
    @Resource
    private TestPaperCatService testPaperCatService;
    @Resource
    private QuesService quesService;
    @Resource
    private TestPaperManager testPaperManager;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public HttpResult<TestPaperAddResult> add(TestPaperAddParam param) {
        log.info(String.format("TestPaperBizServiceImpl.add:新增试卷:param=%s", param.toString()));

        // 检查试卷分类信息
        if (StrUtil.isNotBlank(param.getTpcId())) {
            Optional<TestPaperCat> testPaperCatOpt = testPaperCatService.get(param.getTpcId());
            CheckUtil.boolCheck(!testPaperCatOpt.isPresent(), CloudSchoolBizExceptionEnum.TEST_PAPER_CAT_NOT_FOUND);
        }

        // 试卷对象
        TestPaper entity = new TestPaper();
        BeanUtils.copyProperties(param, entity);

        // 根据名称查询记录
        Optional<TestPaper> nameCheck = testPaperService.byNameCheckExistsGet(param.getTpName());
        // 名称存在记录
        CheckUtil.boolCheck(nameCheck.isPresent(), CloudSchoolBizExceptionEnum.DUPLICATE_NAME);

        // 检查并根据题目id去重后的题目列表。key是quId,value是试卷题目记分记录。并统计题目和总分
        Map<String, List<TestPaperQues>> quesMap = checkQuestions(entity, param.getQuestionList());

        // 保存试卷数据
        Optional<TestPaper> insertOpt = testPaperService.insert(entity);
        // 新增检查
        CheckUtil.boolCheck(!insertOpt.isPresent(), BizExceptionEnum.OPERATION_INSERT_ERROR);
        // 保存试卷题目计分关系
        testPaperQuesService.insertList(quesMap
                .values()
                .stream()
                .map(i -> {
                    TestPaperQues tpq = i.get(0);
                    tpq.setTpId(insertOpt.get().getTpId());
                    return tpq;
                })
                .collect(Collectors.toList()));

        // 封装结果
        TestPaperAddResult result = new TestPaperAddResult();
        result.setTpId(entity.getTpId());
        return HttpResult.success(result);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public HttpResult<Boolean> delete(TestPaperDelParam param) {
        // 调用的批量删除接口
        boolean deleteList = testPaperService.deleteList(param.getTpIds());
        // 根据试卷去删除对应的题目计分记录
        boolean tpIdsDeleteList = testPaperQuesService.byTpIdsDeleteList(param.getTpIds());
        log.info(String.format("TestPaperBizServiceImpl.delete:删除试卷:param=%s,deleteList=%b,tpIdsDeleteList=%b",
                param.toString(), deleteList, tpIdsDeleteList));
        return HttpResult.success(deleteList && tpIdsDeleteList);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public HttpResult<Boolean> update(TestPaperUpdParam param) {
        log.info(String.format("TestPaperBizServiceImpl.update:修改题目:param=%s", param.toString()));

        // 检查试卷分类信息
        if (StrUtil.isNotBlank(param.getTpcId())) {
            Optional<TestPaperCat> testPaperCatOpt = testPaperCatService.get(param.getTpcId());
            CheckUtil.boolCheck(!testPaperCatOpt.isPresent(), CloudSchoolBizExceptionEnum.TEST_PAPER_CAT_NOT_FOUND);
        }

        // 根据名称查询记录
        Optional<TestPaper> nameCheck = testPaperService.byNameCheckExistsGet(param.getTpName());
        // 名称存在记录
        CheckUtil.boolCheck(nameCheck.map(i -> !i.getTpId().equals(param.getTpId())).orElse(false), CloudSchoolBizExceptionEnum.DUPLICATE_NAME);

        // 试卷对象
        TestPaper entity = new TestPaper();
        BeanUtils.copyProperties(param, entity);

        // 根据试卷业务id删除旧题目计分关系
        boolean byTpIdsDelRes = testPaperQuesService.byTpIdsDeleteList(Collections.singletonList(entity.getTpId()));
        log.info(String.format("TestPaperBizServiceImpl.update:修改题目删除旧结果:byTpIdsDelRes=%b,param=%s", byTpIdsDelRes, param.toString()));

        // 检查并根据题目id去重后的题目列表。key是quId,value是试卷题目记分记录。并统计题目和总分
        Map<String, List<TestPaperQues>> quesMap = checkQuestions(entity, param.getQuestionList());

        // 更新试卷数据
        testPaperService.update(entity);

        // 保存试卷题目计分关系
        testPaperQuesService.insertList(quesMap
                .values()
                .stream()
                .map(i -> {
                    TestPaperQues tpq = i.get(0);
                    tpq.setTpId(entity.getTpId());
                    return tpq;
                }).collect(Collectors.toList()));

        return HttpResult.success(true);
    }

    @Override
    public HttpResult<TestPaperGetResult> get(String tpId) {
        // 根据试卷业务id列表查询关联的题目信息并构建结果（包含题干、计分规则、选项）
        Map<String, List<TestPaperQuesResult>> map = testPaperManager.byTpIdsFindAndBuildResult(Collections.singletonList(tpId));

        // 查询试卷
        Optional<TestPaper> testPaper = testPaperService.get(tpId);
        CheckUtil.boolCheck(!testPaper.isPresent(), CloudSchoolBizExceptionEnum.TEST_PAPER_NOT_FOUND);
        // 查询分类
        Optional<TestPaperCat> testPaperCat = testPaperCatService.get(testPaper.get().getTpcId());

        // 封装结果
        TestPaperGetResult result = new TestPaperGetResult();
        BeanUtils.copyProperties(testPaper.get(), result);
        // 设置分类
        result.setTpcName(testPaperCat.map(TestPaperCat::getTpcName).orElse(null));
        result.setQuestionList(transformTpq(map.getOrDefault(tpId, Collections.emptyList())));
        return HttpResult.success(result);
    }

    @Override
    public HttpResult<PageData<TestPaperPageResult>> pageList(TestPaperPageParam param) {
        // 分页条件查询结果
        PageInfo<TestPaper> pageInfo = testPaperService.pageList(param);

        // 试卷业务与题目映射关系。key是tpId，value是对应的题目列表
        Map<String, List<TestPaperQuesResult>> quesMap;
        // 判断结果是否是简单模式
        if (param.getEasy()) {
            quesMap = new HashMap<>(1);
        } else {
            // tpIds
            List<String> tpIds = pageInfo
                    .getList()
                    .stream()
                    .map(TestPaper::getTpId)
                    .collect(Collectors.toList());

            // 根据试卷业务id列表查询关联的题目信息并构建结果（包含题干、计分规则、选项），然后用试卷业务id进行分组
            quesMap = testPaperManager.byTpIdsFindAndBuildResult(tpIds);
        }

        // 组装分页数据
        PageData<TestPaperPageResult> pageData = new PageData<>(pageInfo);
        pageData.setList(pageInfo
                .getList()
                .stream()
                .map(res -> new TestPaperPageResult() {
                    {
                        // 初始化结果
                        BeanUtils.copyProperties(res, this);
                        this.setQuestionList(transformTpq(quesMap.getOrDefault(res.getTpId(), Collections.emptyList())));
                    }
                })
                .collect(Collectors.toList()));
        return HttpResult.success(pageData);
    }

    @Override
    public HttpResult<Boolean> batchCatUpdate(TestPaperUpdCatParam param) {
        log.info(String.format("TestPaperBizServiceImpl.batchCatUpdate:更新试卷分类:param=%s", param.toString()));
        // 更新题干数据
        return HttpResult.success(testPaperService.batchCatUpdate(param.getTpIds(), param.getTpcId()));
    }

    @Override
    public HttpResult<Boolean> copy(TestPaperCopyParam param) {
        // 查询试卷
        Optional<TestPaper> testPaper = testPaperService.get(param.getTpId());
        CheckUtil.boolCheck(!testPaper.isPresent(), CloudSchoolBizExceptionEnum.TEST_PAPER_NOT_FOUND);

        // 根据名称查询记录
        List<TestPaper> nameCheck = testPaperService.byNameStartWithList(testPaper.get().getTpName());

        // 试卷名称重名版本
        int version = checkVersion(nameCheck.stream().map(TestPaper::getTpName).collect(Collectors.toList()),
                testPaper.get().getTpName());
        // 新的试卷名称
        String newTpName = String.format("%s(%d)", testPaper.get().getTpName(), version + 1);

        // 新的试卷数据
        TestPaper testPaperCopy = testPaper.get();
        testPaperCopy.setId(null);
        testPaperCopy.setTpId(null);
        testPaperCopy.setTpName(newTpName);
        testPaperCopy.setCreateTime(null);
        testPaperCopy.setCreateUid(null);
        testPaperCopy.setUpdateTime(null);
        testPaperCopy.setUpdateUid(null);
        // 保存试卷数据
        Optional<TestPaper> insertOpt = testPaperService.insert(testPaperCopy);
        TestPaper copyTestPaper = insertOpt.orElseGet(TestPaper::new);

        //获取试卷题目列表
        List<TestPaperQues> testPaperQues = testPaperQuesService.byTpIdFindList(Collections.singletonList(param.getTpId()));

        //批量设置新的试卷题目
        testPaperQues.forEach(tpq -> {
            tpq.setId(null);
            tpq.setTpId(copyTestPaper.getTpId());
            tpq.setTpqId(null);
            tpq.setCreateTime(null);
            tpq.setCreateUid(null);
            tpq.setUpdateTime(null);
            tpq.setUpdateUid(null);
        });

        //批量新增复制的试卷题目
        testPaperQuesService.insertList(testPaperQues);

        return HttpResult.success(insertOpt.isPresent());
    }

    /**
     * <b>方法名: </b> checkQuestions <br>
     * <b>说明: </b> 检查并根据题目id去重后的题目列表。key是quId,value是试卷题目记分记录。并且计算试卷的总分 <br>
     *
     * @param entity         com.sundot.airport.exam.domain.TestPaper 试卷试题类，用于计算总分
     * @param paramQuestions java.util.List<com.sundot.airport.logic.manage.model.params.TestPaperQuestionParam> 参数中的题目列表
     * @return java.util.Map<java.lang.String, java.util.List < com.sundot.airport.exam.domain.TestPaperQues>><br>
     * <b>修改履历: </b>
     * @author 2021/6/22 liangxiao
     */
    private Map<String, List<TestPaperQues>> checkQuestions(TestPaper entity, List<TestPaperQuestionParam> paramQuestions) {
        // 试卷题目sort排序信号量
        int[] sortSema = {0};

        // 根据题目id去重后的题目列表。key是quId,value是试卷题目记分记录
        Map<String, List<TestPaperQues>> quesMap = paramQuestions
                .stream()
                .map(i -> new TestPaperQues() {
                    {
                        BeanUtils.copyProperties(i, this);
                        // 试卷中题目的排序
                        this.setSort(sortSema[0]++);
                    }
                })
                .collect(Collectors.groupingBy(TestPaperQues::getQuId));
        log.info(String.format("TestPaperBizServiceImpl.checkQuestions:查题目重复:quesMap.size=%d,paramQuestions.size=%d",
                quesMap.size(), paramQuestions.size()));
        // 检查题目是否重复
        CheckUtil.boolCheck(quesMap.size() != paramQuestions.size(), CloudSchoolBizExceptionEnum.DUPLICATE_QUES);

        // 题目ids对应的有效题目个数
        List<Ques> quesList = quesService.byTpqIdList(new ArrayList<>(quesMap.keySet()));
        log.info(String.format("TestPaperBizServiceImpl.checkQuestions:检查题目有效:quesMap.size=%d,quesList.size=%d",
                quesMap.size(), quesList.size()));
        // 检查题目是否有效
        CheckUtil.boolCheck(quesMap.size() != quesList.size(), CloudSchoolBizExceptionEnum.INVALID_QUES);

        // 计算试卷分数等信息
        int tpTotalScore = 0;
        for (Ques ques : quesList) {
            // 获取题目的计分信息
            List<TestPaperQues> tpQuesList = quesMap.get(ques.getQuId());
            // 如果没有计分信息则是无效题目
            CheckUtil.boolCheck(tpQuesList.isEmpty(), CloudSchoolBizExceptionEnum.INVALID_QUES);
            // 题目计分规则
            TestPaperQues tpQues = tpQuesList.get(0);
            // 题目选项个数
            Integer quOptCount = ques.getQuOptCount();
            // 计算每题分数
            switch (ques.getQuType()) {
                case SINGLE:
                case JUDGMENT:
                case MULTIPLE:
                    // 单选、多选、判断直接加上题目总分即可
                    tpTotalScore += tpQues.getTpqTotalScore();
                    break;
                case FILL_BLANK:
                case CONNECTION:
                    int tScore = QuesCompScoreTypeEnum.RIGHT.equals(tpQues.getTpqCompType()) ?
                            tpQues.getTpqTotalScore() : tpQues.getTpqLeftScore() * quOptCount;
                    tpQues.setTpqTotalScore(tScore);
                    // 填空题、连线题需要先计算题目总分，再加上试卷总分
                    tpTotalScore += tScore;
                    break;
                default:
                    // 兜底后续添加未处理的枚举
                    CheckUtil.boolCheck(true, CloudSchoolBizExceptionEnum.INVALID_QUES);
            }
        }

        log.info(String.format("TestPaperBizServiceImpl.checkQuestions:计分结果:quesMap.size=%d,tpTotalScore=%d",
                quesMap.size(), tpTotalScore));

        // 统计试卷的题目数和总分
        entity.setTpQuesNum(quesMap.size());
        entity.setTpTotalScore(tpTotalScore);
        return quesMap;
    }

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


    /**
     * <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> checkVersion <br>
     * <b>说明: </b> 检查该试卷名称是否存在带有复制编号的名称 <br>
     *
     * @param nameList   java.util.List<java.lang.String> 试卷名称列表
     * @param sourceName java.lang.String 试卷待复制的名称
     * @return int 目前最大的编号<br>
     * <b>修改履历: </b>
     * @author 2021/7/14 liangxiao
     */
    private int checkVersion(List<String> nameList, String sourceName) {
        return nameList
                .stream()
                .map(i -> i.replace(sourceName, "").trim())
                .filter(i -> Pattern.matches("^\\(\\d*\\)$", i))
                .map(i -> {
                    Matcher matcher3 = FILE_NAME_PATTERN.matcher(i);
                    if (matcher3.find()) {
                        return Integer.parseInt(matcher3.group(1));
                    }
                    return 0;
                })
                .max(Comparator.comparingInt(a -> a))
                .orElse(0);
    }
}
