package com.ruoyi.school.paper.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.ruoyi.common.core.domain.PageQuery;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.redis.RedisUtils;
import com.ruoyi.school.paper.domain.DbTestPaperRecord;
import com.ruoyi.school.paper.domain.DbTestPaperRecordDetail;
import com.ruoyi.school.paper.domain.bo.DbQuestionBankTestPaperBo;
import com.ruoyi.school.paper.domain.bo.DbTestPaperRecordBo;
import com.ruoyi.school.paper.domain.bo.TestPaperAnswerBo;
import com.ruoyi.school.paper.domain.bo.TestPaperAnswerItemBo;
import com.ruoyi.school.paper.domain.vo.DbQuestionBankTestPaperVo;
import com.ruoyi.school.paper.domain.vo.DbTestPaperVo;
import com.ruoyi.school.paper.domain.vo.TestPaperVo;
import com.ruoyi.school.paper.domain.vo.TestQuestionItemVo;
import com.ruoyi.school.paper.service.*;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

/**
 * <p>created in  2023/7/21 17:03
 *
 * @author WangMin
 * @version 1.0
 */
@Service
@RequiredArgsConstructor
public class TestPaperServiceImpl implements ITestPaperService {

    private final IDbTestPaperService testPaperService;

    private final IDbTestPaperRecordService paperRecordService;

    private final IDbQuestionBankTestPaperService bankTestPaperService;

    private final IDbTestPaperRecordDetailService paperRecordDetailService;

    private final String TEST_PAPER_ANSWER_KEY = "test_paper_record_detail";
    private final String TEST_PAPER_ANSWER_KEY_FORMAT = "%s:record:%d:question:%d";

    @Override
    @Transactional(rollbackFor = Exception.class)
    public TestPaperVo generateTestPaper(Long userId, Long paperId) {
        TestPaperVo result = new TestPaperVo();
        // 获取到对应试卷信息
        DbTestPaperVo dbTestPaperVo = testPaperService.queryById(paperId);
        Objects.requireNonNull(dbTestPaperVo, "未检查到目标试卷");
        result.setTestName(dbTestPaperVo.getTestName());
        // 判断是否有该生的未交卷答题记录
        LambdaQueryWrapper<DbTestPaperRecord> paperRecordWrapper = new LambdaQueryWrapper<>();
        paperRecordWrapper.eq(DbTestPaperRecord::getTestPaperId, paperId)
            .eq(DbTestPaperRecord::getUserId, userId)
            .eq(DbTestPaperRecord::getStatus, 0);
        DbTestPaperRecord dbTestPaperRecord;
        if ((dbTestPaperRecord = paperRecordService.getOne(paperRecordWrapper)) == null) {
            // 插入学生-试卷考试记录信息
            DbTestPaperRecordBo insertTestPaperRecord = new DbTestPaperRecordBo();
            insertTestPaperRecord.setTestPaperId(paperId);
            insertTestPaperRecord.setUserId(userId);
            // 状态设置为考试中
            insertTestPaperRecord.setStatus(0);
            boolean recordInsertFlag = paperRecordService.insertByBo(insertTestPaperRecord);
            if (!recordInsertFlag) {
                throw new ServiceException("考试记录生成失败");
            }
            dbTestPaperRecord = BeanUtil.copyProperties(insertTestPaperRecord, DbTestPaperRecord.class);
        }
        result.setRecordId(dbTestPaperRecord.getId());
        return result;
    }

    /**
     * 下一题
     *
     * @param request   请求参数 【注】考试记录ID必传
     * @param pageQuery 分页
     * @return 考试题目
     */
    @Override
    public TableDataInfo<TestQuestionItemVo> nextQuestion(TestPaperAnswerBo request, PageQuery pageQuery) {
        // 若传入了本题的结果，则先将结果存入Redis中
        if (CollUtil.isNotEmpty(request.getAnswers())) {
            request.getAnswers().forEach(answer -> {
                Objects.requireNonNull(answer.getId(), "题目ID非法");
                String key = String.format(TEST_PAPER_ANSWER_KEY_FORMAT, TEST_PAPER_ANSWER_KEY, request.getRecordId(), answer.getId());
                RedisUtils.setCacheObject(key, answer);
            });
        }
        // 返回下一题
        TableDataInfo<TestQuestionItemVo> result = bankTestPaperService.getTestPaperQuestion(request, pageQuery);
        List<TestQuestionItemVo> rows = result.getRows();
        // 查询Redis缓存中是否存在该生已作答过的答案
        rows.forEach(question -> {
            String key = String.format(TEST_PAPER_ANSWER_KEY_FORMAT, TEST_PAPER_ANSWER_KEY, request.getRecordId(), question.getId());
            TestPaperAnswerItemBo cacheObject = RedisUtils.getCacheObject(key);
            Optional.ofNullable(cacheObject).ifPresent(cache -> {
                question.setAnswer(cache.getAnswer());
                question.setAnswerPic(cache.getAnswerPic());
            });
        });
        return result;
    }

    /**
     * 交卷
     *
     * @param bo 试题类容
     * @return 操作结果
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean paperTestEnd(TestPaperAnswerBo bo) {
        // 查询试卷对应所有的题目
        DbTestPaperRecord testPaperRecord = paperRecordService.getOne(Wrappers.<DbTestPaperRecord>lambdaQuery().eq(DbTestPaperRecord::getId, bo.getRecordId()));
        Objects.requireNonNull(testPaperRecord, "未检查到合法考试记录");
        DbQuestionBankTestPaperBo queryCondition = new DbQuestionBankTestPaperBo();
        queryCondition.setTestPaperId(testPaperRecord.getTestPaperId());
        List<DbQuestionBankTestPaperVo> questionList = bankTestPaperService.queryList(queryCondition);
        List<DbTestPaperRecordDetail> insertPaperRecords = new ArrayList<>(questionList.size());
        // 装载学生考试答题卡
        questionList.forEach(question -> {
            DbTestPaperRecordDetail buffer = new DbTestPaperRecordDetail();
            buffer.setRecordId(bo.getRecordId());
            buffer.setQuestionBankId(question.getId());
            // 查询Redis中是否有该题的提交记录
            String key = String.format(TEST_PAPER_ANSWER_KEY_FORMAT, TEST_PAPER_ANSWER_KEY, bo.getRecordId(), question.getId());
            TestPaperAnswerItemBo cacheObject = RedisUtils.getCacheObject(key);
            Optional.ofNullable(cacheObject).ifPresent(cache -> {
                buffer.setAnswer(cache.getAnswer());
                buffer.setAnswersPic(cache.getAnswerPic());
            });
            // 计算选择题分数
            DbQuestionBankTestPaperVo bankTestPaperVo = bankTestPaperService.queryById(question.getId());
            buffer.setCorrectAnswer(StringUtils.isNotEmpty(bankTestPaperVo.getRightAnswers()) ? bankTestPaperVo.getRightAnswers() : bankTestPaperVo.getRightAnswersPic());
            if (bankTestPaperVo.getSpecies().equals(2)) {
                buffer.setScore(bankTestPaperVo.getRightAnswers().equals(buffer.getAnswer()) ? BigDecimal.valueOf(bankTestPaperVo.getScore()) : BigDecimal.ZERO);
            }
            insertPaperRecords.add(buffer);
        });
        if (CollUtil.isNotEmpty(insertPaperRecords)) {
            if (!paperRecordDetailService.saveBatch(insertPaperRecords)) {
                throw new ServiceException("答题记录保存失败");
            }
            // 修改考试记录状态为待批阅
            testPaperRecord.setStatus(1);
            if (!paperRecordService.updateByBo(BeanUtil.toBean(testPaperRecord, DbTestPaperRecordBo.class))) {
                throw new ServiceException("考试记录状态修改失败");
            }
            // 清除Redis缓存
            RedisUtils.deleteKeys(String.format("%s:record:%d*", TEST_PAPER_ANSWER_KEY, bo.getRecordId()));
            return true;
        }
        return true;
    }

    /**
     * 检查答题卡完整性
     *
     * @param recordId 答题记录ID
     * @return 答题完整性
     */
    @Override
    public boolean checkTestPaperIntegrality(Long recordId) {
        // 查询试卷对应所有的题目
        DbTestPaperRecord testPaperRecord = paperRecordService.getOne(Wrappers.<DbTestPaperRecord>lambdaQuery().eq(DbTestPaperRecord::getId, recordId));
        Objects.requireNonNull(testPaperRecord, "未检查到合法考试记录");
        DbQuestionBankTestPaperBo queryCondition = new DbQuestionBankTestPaperBo();
        queryCondition.setTestPaperId(testPaperRecord.getTestPaperId());
        List<DbQuestionBankTestPaperVo> questionList = bankTestPaperService.queryList(queryCondition);
        for (DbQuestionBankTestPaperVo question : questionList) {
            // 查询Redis中是否有该题的提交记录
            String key = String.format(TEST_PAPER_ANSWER_KEY_FORMAT, TEST_PAPER_ANSWER_KEY, recordId, question.getId());
            TestPaperAnswerItemBo cacheObject = RedisUtils.getCacheObject(key);
            if (cacheObject == null  ||
                (StringUtils.isEmpty(cacheObject.getAnswer()) && StringUtils.isEmpty(cacheObject.getAnswerPic()))) {
                return false;
            }
        }
        return true;
    }
}
