package com.qmth.themis.business.service.impl;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.aliyun.oss.common.utils.BinaryUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.qmth.themis.business.cache.ObjectiveAnswerCacheUtil;
import com.qmth.themis.business.cache.RedisKeyHelper;
import com.qmth.themis.business.cache.bean.ExamPaperCacheBean;
import com.qmth.themis.business.cache.bean.ObjectiveAnswerCacheBean;
import com.qmth.themis.business.constant.SpringContextHolder;
import com.qmth.themis.business.constant.SystemConstant;
import com.qmth.themis.business.dao.TEExamActivityMapper;
import com.qmth.themis.business.dao.TEExamPaperMapper;
import com.qmth.themis.business.entity.TEExamPaper;
import com.qmth.themis.business.service.TEExamPaperService;
import com.qmth.themis.business.util.OssUtil;
import com.qmth.themis.common.exception.BusinessException;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * @Description: 考试试卷 服务实现类
 * @Param:
 * @return:
 * @Author: wangliang
 * @Date: 2020/6/25
 */
@Service
public class TEExamPaperServiceImpl extends ServiceImpl<TEExamPaperMapper, TEExamPaper> implements TEExamPaperService {

    private final static long objectiveAnswerCacheTimeOutHours = 48L;

    @Resource
    OssUtil ossUtil;

    @Resource
    TEExamPaperMapper teExamPaperMapper;

    @Resource
    TEExamActivityMapper examActivityMapper;

    @Override
    public TEExamPaper findByExamIdAndCourseCodeAndPaperCode(Long examId, String courseCode, String paperCode) {
        if (examId == null) {
            throw new BusinessException("examId is null");
        }
        if (StringUtils.isBlank(courseCode)) {
            throw new BusinessException("courseCode is null");
        }
        if (StringUtils.isBlank(paperCode)) {
            throw new BusinessException("paperCode is null");
        }
        return teExamPaperMapper.findByExamIdAndCourseCodeAndPaperCode(examId, courseCode, paperCode);
    }

    @Cacheable(value = "exam_paper", key = "#paperId", unless = "#result == null")
    @Override
    public ExamPaperCacheBean getExamPaperCacheBean(Long paperId) {
        return cacheOperation(paperId);
    }

    /**
     * 更新试卷缓存
     *
     * @param paperId
     * @return
     */
    @Override
    @CachePut(value = "exam_paper", key = "#paperId", condition = "#result != null")
    public ExamPaperCacheBean updateExamPaperCacheBean(Long paperId) {
        return cacheOperation(paperId);
    }

    /**
     * 删除试卷缓存
     *
     * @param paperId
     */
    @Override
    @CacheEvict(value = "exam_paper", key = "#paperId")
    public void deleteExamPaperCacheBean(Long paperId) {

    }

    /**
     * 缓存操作
     *
     * @param paperId
     * @return
     */
    public ExamPaperCacheBean cacheOperation(Long paperId) {
        ExamPaperCacheBean ret = null;
        TEExamPaper ep = getById(paperId);
        if (ep == null) {
            return ret;
        }
        ret = new ExamPaperCacheBean();
        ret.setId(ep.getId());
        ret.setExamId(ep.getExamId());
        ret.setCourseCode(ep.getCourseCode());
        ret.setCode(ep.getCode());
        ret.setName(ep.getName());
        ret.setTotalScore(ep.getTotalScore());
        ret.setTotalObjectiveScore(ep.getTotalObjectiveScore());
        ret.setTotalSubjectiveScore(ep.getTotalSubjectiveScore());
        ret.setPaperPath(ep.getPaperPath());
        if (Objects.isNull(ep.getPaperMd5())) {
            try {
                if (Objects.isNull(ep.getPaperPath())) {
                    throw new BusinessException("未上传试卷结构");
                }
                ep.setPaperMd5(BinaryUtil.encodeMD5(ossUtil.download(false, ep.getPaperPath())));
                if (Objects.isNull(ep.getStructMd5())) {
                    ep.setStructMd5(BinaryUtil.encodeMD5(ossUtil.download(false, ep.getStructPath())));
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        ret.setPaperMd5(ep.getPaperMd5());
        ret.setStructPath(ep.getStructPath());
        ret.setStructMd5(ep.getStructMd5());
        ret.setAnswerPath(ep.getAnswerPath());
        ret.setDecryptSecret(ep.getDecryptSecret());
        ret.setDecryptVector(ep.getDecryptVector());
        ret.setEncryptMode(ep.getEncryptMode());
        ret.setNeedVoiceAnswer(ep.getNeedVoiceAnswer());
        ret.setHasAudio(ep.getHasAudio());
        ret.setWeight(ep.getWeight());
        ret.setAudioPlayCount(ep.getAudioPlayCount());
        ret.setPaperViewPath(ep.getPaperViewPath());
        return ret;
    }

    @Override
    public void reloadObjectiveAnswerCacheBean(Long paperId, Long timeOutHours) {
        ExamPaperCacheBean ep = getExamPaperCacheBean(paperId);
        if (ep == null) {
            return;
        }
        Long examId = ep.getExamId();
        if (StringUtils.isBlank(ep.getAnswerPath())) {//没有答案文件
            return;
        }
        try {
            String answerjson = new String(ossUtil.download(false, ep.getAnswerPath()), StandardCharsets.UTF_8);
            Map<String, ObjectiveAnswerCacheBean> map = buildObjectiveAnswerCache(answerjson);
            ObjectiveAnswerCacheUtil.saveObjectiveAnswerCache(examId, paperId, map);
            ObjectiveAnswerCacheUtil.expireObjectiveAnswerCache(examId, timeOutHours);
        } catch (Exception e) {
            log.error("get struct/answer json error", e);
            throw new RuntimeException(e);
        }
    }

    @Override
    public Map<String, ObjectiveAnswerCacheBean> getObjectiveAnswerCacheBean(Long paperId) {
        ExamPaperCacheBean ep = getExamPaperCacheBean(paperId);
        if (ep == null) {
            return null;
        }
        Long examId = ep.getExamId();
        if (StringUtils.isBlank(ep.getAnswerPath())) {//没有答案文件
            return null;
        }
        Map<String, ObjectiveAnswerCacheBean> map = ObjectiveAnswerCacheUtil.getObjectiveAnswerCache(examId, paperId);
        if (map != null) {
            return map;
        }
        try {
            String answerjson = new String(ossUtil.download(false, ep.getAnswerPath()), StandardCharsets.UTF_8);
            map = buildObjectiveAnswerCache(answerjson);
            ObjectiveAnswerCacheUtil.saveObjectiveAnswerCache(examId, paperId, map);
            ObjectiveAnswerCacheUtil.expireObjectiveAnswerCache(examId, objectiveAnswerCacheTimeOutHours);
            return map;
        } catch (Exception e) {
            log.error("get struct/answer json error", e);
            throw new RuntimeException(e);
        }
    }

    private Map<String, ObjectiveAnswerCacheBean> buildObjectiveAnswerCache(String answerStr) {
        Map<String, ObjectiveAnswerCacheBean> map = new HashMap<String, ObjectiveAnswerCacheBean>();
        JSONObject answerJson = JSONObject.parseObject(answerStr);
        JSONArray answerdetails = answerJson.getJSONArray(SystemConstant.DETAILS);
        for (int i = 0; i < answerdetails.size(); i++) {
            Integer mainNum = answerdetails.getJSONObject(i).getInteger(SystemConstant.NUMBER);
            JSONArray answerdetailquestions = answerdetails.getJSONObject(i).getJSONArray(SystemConstant.QUESTIONS);
            for (int j = 0; j < answerdetailquestions.size(); j++) {
                JSONObject answerquestion = answerdetailquestions.getJSONObject(j);
                Integer subNum = answerquestion.getInteger(SystemConstant.NUMBER);
                Double score = answerquestion.getDouble(SystemConstant.SCORE);
                Integer structType = answerquestion.getInteger(SystemConstant.STRUCT_TYPE);
                if (structType.intValue() == 1 || structType.intValue() == 2 || structType.intValue() == 3) {
                    ObjectiveAnswerCacheBean bean = new ObjectiveAnswerCacheBean();
                    bean.setScore(score);
                    bean.setStructType(structType);
                    if (structType.intValue() == 1 || structType.intValue() == 2) {
                        JSONArray answer = answerquestion.getJSONArray(SystemConstant.ANSWER);
                        bean.setChoiceAnswer(answer);
                    }
                    if (structType.intValue() == 3) {
                        Boolean answer = answerquestion.getBoolean(SystemConstant.ANSWER);
                        bean.setBoolAnswer(answer);
                    }
                    map.put(RedisKeyHelper.examAnswerHashKey(mainNum, subNum, null), bean);
                }
                if (structType.intValue() == 6 || structType.intValue() == 8) {
                    JSONArray answersubQuestions = answerquestion.getJSONArray(SystemConstant.SUB_QUESTIONS);
                    for (int k = 0; k < answersubQuestions.size(); k++) {
                        JSONObject answersubquestion = answersubQuestions.getJSONObject(k);
                        Integer subIndex = answersubquestion.getInteger(SystemConstant.NUMBER);
                        Double subScore = answersubquestion.getDouble(SystemConstant.SCORE);
                        Integer subStructType = answersubquestion.getInteger(SystemConstant.STRUCT_TYPE);

                        if (subStructType.intValue() == 1 || subStructType.intValue() == 2
                                || subStructType.intValue() == 3) {
                            ObjectiveAnswerCacheBean bean = new ObjectiveAnswerCacheBean();
                            bean.setScore(subScore);
                            bean.setStructType(subStructType);
                            if (subStructType.intValue() == 1 || subStructType.intValue() == 2) {
                                JSONArray answer = answersubquestion.getJSONArray(SystemConstant.ANSWER);
                                bean.setChoiceAnswer(answer);
                            }
                            if (subStructType.intValue() == 3) {
                                Boolean answer = answersubquestion.getBoolean(SystemConstant.ANSWER);
                                bean.setBoolAnswer(answer);
                            }
                            map.put(RedisKeyHelper.examAnswerHashKey(mainNum, subNum, subIndex), bean);
                        }
                    }
                }
            }
        }
        return map;
    }

    @Override
    public List<TEExamPaper> findByExamIdAndCourseCode(Long examId, String courseCode) {
        return teExamPaperMapper.findListByExamIdAndCourseCode(examId, courseCode);
    }

    @Transactional
    @Override
    public void savePaperWeight(Map<Long, Double> map) {
        for (Long id : map.keySet()) {
            teExamPaperMapper.updateWeight(id, map.get(id));
        }
    }

    /**
     * 获取缓存过期时间，不满足条件的返回-1.条件：场次最小startTime在当天的，或者当前时间在场次最小startTime和最大finishTime之间的。
     *
     * @param examId
     * @return
     */
    private Long getObjectiveAnswerCacheTimeOut(Long examId) {
        Long objectiveAnswerCacheTimeOut = -1L;
        List<Map<String, Long>> list = examActivityMapper.findMinStartTimeAndMaxFinshTimeByExamId(examId);
        if (list != null && list.size() > 0 && Objects.nonNull(list.get(0))) {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
            Date now = new Date();
            Long startTime = list.get(0).get("startTime");
            Long finishTime = list.get(0).get("finishTime");
            Date start = new Date(startTime);
            if (sdf.format(now).equals(sdf.format(start)) || (now.getTime() >= startTime && now.getTime() <= finishTime)) {
                objectiveAnswerCacheTimeOut = (finishTime - now.getTime()) / (1000 * 60 * 60) + 24;
            }
        }
        return objectiveAnswerCacheTimeOut;
    }

    /**
     * 处理考试批次下的客观题答案缓存
     *
     * @param examId
     */
    @Override
    public void disposeObjectiveAnswer(Long examId) {
        Long objectiveAnswerCacheTimeOut = getObjectiveAnswerCacheTimeOut(examId);
        if (objectiveAnswerCacheTimeOut > 0) {
            List<TEExamPaper> list = teExamPaperMapper.findByExamId(examId);
            if (list != null && list.size() > 0) {
                for (TEExamPaper paper : list) {
                    if (ObjectiveAnswerCacheUtil.getObjectiveAnswerCache(examId, paper.getId()) == null) {
                        reloadObjectiveAnswerCacheBean(paper.getId(), objectiveAnswerCacheTimeOut);
                    }
                }
            }
            Long count = ObjectiveAnswerCacheUtil.getHashCacheSize(examId);
            if (count != null && count > 0) {
                ObjectiveAnswerCacheUtil.expireObjectiveAnswerCache(examId, objectiveAnswerCacheTimeOut);
            }
        }
    }

    /**
     * 导入试卷时处理缓存
     */
    @Override
    public void disposePaperCacheOnPaperImport(Long examId, List<Long> paperIds) {
        TEExamPaperService examPaperService = SpringContextHolder.getBean(TEExamPaperService.class);
        for (Long id : paperIds) {
            examPaperService.deleteExamPaperCacheBean(id);
            Long objectiveAnswerCacheTimeOut = getObjectiveAnswerCacheTimeOut(examId);
            if (objectiveAnswerCacheTimeOut > 0) {
                examPaperService.reloadObjectiveAnswerCacheBean(id, objectiveAnswerCacheTimeOut);
            }
            examPaperService.deletePaperStructCacheBean(id);
        }
    }

    /**
     * 删除试卷结构缓存
     *
     * @param paperId
     */
    @Override
    @CacheEvict(value = "paper_struct", key = "#paperId")
    public void deletePaperStructCacheBean(Long paperId) {

    }

    @Cacheable(value = "paper_struct", key = "#paperId", unless = "#result == null")
    @Override
    public Map<String, Integer> getPaperStructCacheBean(Long paperId) {
        ExamPaperCacheBean ep = getExamPaperCacheBean(paperId);
        if (ep == null) {
            return null;
        }
        if (StringUtils.isBlank(ep.getStructPath())) {//没有试卷结构文件
            return null;
        }
        try {
            String structJson = new String(ossUtil.download(false, ep.getStructPath()), StandardCharsets.UTF_8);
            return buildPaperStructCache(structJson);
        } catch (Exception e) {
            log.error("get struct json error", e);
            throw new RuntimeException(e);
        }
    }

    private Map<String, Integer> buildPaperStructCache(String structStr) {
        Map<String, Integer> map = new LinkedHashMap<String, Integer>();
        JSONObject structJson = JSONObject.parseObject(structStr);
        JSONArray structdetails = structJson.getJSONArray(SystemConstant.DETAILS);
        for (int i = 0; i < structdetails.size(); i++) {
            Integer mainNum = structdetails.getJSONObject(i).getInteger(SystemConstant.NUMBER);
            JSONArray structdetailquestions = structdetails.getJSONObject(i).getJSONArray(SystemConstant.QUESTIONS);
            for (int j = 0; j < structdetailquestions.size(); j++) {
                JSONObject structquestion = structdetailquestions.getJSONObject(j);
                Integer subNum = structquestion.getInteger(SystemConstant.NUMBER);
                Integer structType = structquestion.getInteger(SystemConstant.STRUCT_TYPE);
                map.put(RedisKeyHelper.examAnswerHashKey(mainNum, subNum, null), structType);
                if (structType.intValue() == 6 || structType.intValue() == 8) {
                    JSONArray structsubQuestions = structquestion.getJSONArray(SystemConstant.SUB_QUESTIONS);
                    for (int k = 0; k < structsubQuestions.size(); k++) {
                        JSONObject structsubquestion = structsubQuestions.getJSONObject(k);
                        Integer subIndex = structsubquestion.getInteger(SystemConstant.NUMBER);
                        Integer subStructType = structsubquestion.getInteger(SystemConstant.STRUCT_TYPE);
                        map.put(RedisKeyHelper.examAnswerHashKey(mainNum, subNum, subIndex), subStructType);
                    }
                }
            }
        }
        return map;
    }
}
