package com.qmth.teachcloud.mark.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.qmth.teachcloud.common.bean.dto.DataPermissionRule;
import com.qmth.teachcloud.common.bean.dto.mark.MarkSettingDto;
import com.qmth.teachcloud.common.entity.MarkQuestion;
import com.qmth.teachcloud.common.entity.SysUser;
import com.qmth.teachcloud.common.enums.ExceptionResultEnum;
import com.qmth.teachcloud.common.enums.mark.MarkMode;
import com.qmth.teachcloud.common.enums.mark.MarkPaperStatus;
import com.qmth.teachcloud.common.enums.mark.SubjectiveStatus;
import com.qmth.teachcloud.common.service.BasicRoleDataPermissionService;
import com.qmth.teachcloud.common.service.TeachcloudCommonService;
import com.qmth.teachcloud.common.util.ServletUtil;
import com.qmth.teachcloud.mark.bean.archivescore.ArchiveScoreQuery;
import com.qmth.teachcloud.mark.bean.archivescore.ArchiveScoreVo;
import com.qmth.teachcloud.mark.bean.document.ArchivePaperQuery;
import com.qmth.teachcloud.mark.bean.document.ArchivePaperVo;
import com.qmth.teachcloud.mark.dto.mark.score.CheckScoreListDto;
import com.qmth.teachcloud.mark.dto.mark.score.MarkPaperPackageDto;
import com.qmth.teachcloud.mark.dto.mark.score.SettingDto;
import com.qmth.teachcloud.mark.entity.MarkPaper;
import com.qmth.teachcloud.mark.entity.MarkStudent;
import com.qmth.teachcloud.mark.entity.ScanPackage;
import com.qmth.teachcloud.mark.enums.OmrTaskStatus;
import com.qmth.teachcloud.mark.mapper.MarkPaperMapper;
import com.qmth.teachcloud.mark.service.*;
import com.qmth.teachcloud.mark.utils.Calculator;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * <p>
 * 考试科目表 服务实现类
 * </p>
 *
 * @author xf
 * @since 2023-09-22
 */
@Service
public class MarkPaperServiceImpl extends ServiceImpl<MarkPaperMapper, MarkPaper> implements MarkPaperService {

    @Resource
    private ScanPackageService scanPackageService;
    @Resource
    private ScanOmrTaskService scanOmrTaskService;
    @Resource
    private TeachcloudCommonService teachcloudCommonService;
    @Resource
    private BasicRoleDataPermissionService basicRoleDataPermissionService;
    @Resource
    private MarkQuestionService markQuestionService;
    @Resource
    private MarkStudentService markStudentService;
    @Resource
    private MarkUserGroupService markUserGroupService;
    @Resource
    private MarkDocumentService markDocumentService;


    @Override
    public IPage<MarkSettingDto> listPaperSetting(Long examId, String courseCode, String paperNumber, Boolean groupStatus, Integer pageNumber, Integer pageSize) {
        Page<MarkSettingDto> page = new Page<>(pageNumber, pageSize);
        SysUser sysUser = (SysUser) ServletUtil.getRequestUser();
        DataPermissionRule dpr = basicRoleDataPermissionService.findDataPermission(sysUser.getSchoolId(), sysUser.getId(), "/api/admin/mark/setting/list");
        IPage<MarkSettingDto> markSettingDtoIPage = this.baseMapper.listPaperSetting(page, sysUser.getSchoolId(), examId, courseCode, paperNumber, groupStatus, dpr);
        for (MarkSettingDto record : markSettingDtoIPage.getRecords()) {
            if (Objects.nonNull(record.getMarkMode())) {
                record.setMarkModeDisplay(record.getMarkMode().getName());
            }
        }
        return markSettingDtoIPage;
    }

    @Override
    public MarkPaper getByExamIdAndPaperNumber(Long examId, String paperNumber) {
        QueryWrapper<MarkPaper> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda().eq(MarkPaper::getExamId, examId)
                .eq(MarkPaper::getPaperNumber, paperNumber);
        return this.getOne(queryWrapper);
    }

    @Override
    public MarkPaper getByExamIdAndPaperNumberAndPaperType(Long examId, String paperNumber, String paperType) {
        QueryWrapper<MarkPaper> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda().eq(MarkPaper::getExamId, examId)
                .eq(MarkPaper::getPaperNumber, paperNumber)
                .eq(MarkPaper::getPaperType, paperType);
        return this.getOne(queryWrapper);
    }

    @Transactional
    @Override
    public void savePaperSetting(MarkPaper markPaper) {
        MarkPaper markPaperOld = this.getByExamIdAndPaperNumber(markPaper.getExamId(), markPaper.getPaperNumber());
        if (markPaperOld == null) {
            throw ExceptionResultEnum.ERROR.exception("评卷设置数据不存在");
        }
        // 评卷是否结束
        if (MarkPaperStatus.FINISH.equals(markPaperOld.getStatus())) {
            throw ExceptionResultEnum.ERROR.exception("科目已结束评卷，无法修改参数");
        }
        UpdateWrapper<MarkPaper> updateWrapper = new UpdateWrapper<>();
        updateWrapper.lambda().set(MarkPaper::getMarkMode, markPaper.getMarkMode())
                .set(MarkPaper::getForceMode, !MarkMode.UNLIMITED.equals(markPaper.getMarkMode()))
                .set(MarkPaper::getMarkStartTime, markPaper.getMarkStartTime())
                .set(MarkPaper::getMarkEndTime, markPaper.getMarkEndTime())
                .set(MarkPaper::getSheetView, markPaper.getSheetView())
                .set(MarkPaper::getShowObjectScore, markPaper.getShowObjectScore())
                .set(MarkPaper::getAutoScroll, markPaper.getAutoScroll())
                .set(MarkPaper::getOpenDoubleMarking, markPaper.getOpenDoubleMarking())
                .set(MarkPaper::getPassScore, markPaper.getPassScore())
                .set(MarkPaper::getExcellentScore, markPaper.getExcellentScore())
                .set(MarkPaper::getSheetConfig, markPaper.getSheetConfig())
                .eq(MarkPaper::getExamId, markPaper.getExamId())
                .eq(MarkPaper::getPaperNumber, markPaper.getPaperNumber());

        // 更新评卷员模式
        markUserGroupService.updateMode(markPaper.getExamId(), markPaper.getPaperNumber(), null, null, MarkMode.UNLIMITED.equals(markPaper.getMarkMode()) ? MarkMode.TRACK : markPaper.getMarkMode());
        this.update(updateWrapper);
    }

    @Override
    public Boolean finishPaper(Long examId, List<String> paperNumbers, MarkPaperStatus status) {
        for (String paperNumber : paperNumbers) {
            MarkPaper markPaper = this.getByExamIdAndPaperNumber(examId, paperNumber);
            if (MarkPaperStatus.FINISH.equals(status)) {
                String courseInfo = String.format("%s[%s]，试卷编号%s，", markPaper.getCourseName(), markPaper.getCourseCode(), markPaper.getPaperNumber());
                // 主观题是否全部分组
                List<MarkQuestion> markQuestionObjectiveList = markQuestionService.listQuestionByExamIdAndPaperNumberAndGroupNumber(examId, paperNumber, null, true);
                if (CollectionUtils.isNotEmpty(markQuestionObjectiveList)) {
                    if (markQuestionObjectiveList.stream().filter(m -> StringUtils.isBlank(m.getAnswer())).count() > 0) {
                        throw ExceptionResultEnum.ERROR.exception(courseInfo + "客观题标答未设置，无法结束评卷");
                    }
                }

                // 主观题是否全部分组
                List<MarkQuestion> markQuestionSubjectiveList = markQuestionService.listQuestionByExamIdAndPaperNumberAndGroupNumber(examId, paperNumber, null, false);
                // 没有主观题，不校验考生评卷
                if (CollectionUtils.isNotEmpty(markQuestionSubjectiveList)) {
                    if (markQuestionSubjectiveList.stream().filter(m -> m.getGroupNumber() == null).count() > 0) {
                        throw ExceptionResultEnum.ERROR.exception(courseInfo + "主观题未全部分组，无法结束评卷");
                    }
                    // 未缺考、未违纪且已上传图片的考生全部评完
                    if (markStudentService.countByExamIdAndPaperNumberAndMarkStatus(examId, paperNumber, SubjectiveStatus.UNMARK) > 0) {
                        throw ExceptionResultEnum.ERROR.exception(courseInfo + "考生未全部评完，无法结束评卷");
                    }
                    // 未全部扫描，不能结束
                    if (markStudentService.countUnexistByExamIdAndPaperNumberAndPaperType(examId, paperNumber, markPaper.getPaperType()) > 0) {
                        throw ExceptionResultEnum.ERROR.exception(courseInfo + "有考生未扫描未核对，请在扫描客户端进行确认操作后再结束阅卷");
                    }
                    // 识别缺考未确认完，不能结束
                    if (markStudentService.countOmrAbsentStudent(examId, paperNumber, markPaper.getPaperType(), false) > 0) {
                        throw ExceptionResultEnum.ERROR.exception(courseInfo + "识别缺考记录未核对，请在扫描客户端进行确认操作后再结束阅卷");
                    }
                    // 人工绑定未做完，不能结束
                    if (markStudentService.getAssignedCount(examId, false, markPaper.getCourseCode(), markPaper.getCoursePaperId(), MarkPaperStatus.FORMAL, null) > 0) {
                        throw ExceptionResultEnum.ERROR.exception(courseInfo + "人工绑定记录未核对，请在扫描客户端进行确认操作后再结束阅卷");
                    }
                    // 客观题检查未做完，不能结束
                    if (scanOmrTaskService.getFinishStudentCountByExamAndUserId(examId, markPaper.getCourseCode(), markPaper.getCoursePaperId(), OmrTaskStatus.WAITING.name(), null) > 0) {
                        throw ExceptionResultEnum.ERROR.exception(courseInfo + "客观题存在识别嫌疑需要人工确认，请在扫描客户端进行确认操作后再结束阅卷");
                    }
                }

                // 结束评卷时，客观题统分
                List<MarkStudent> markStudentList = markStudentService.listByExamIdAndCoursePaperId(examId, markPaper.getCoursePaperId());
                for (MarkStudent markStudent : markStudentList) {
                    markStudentService.calculateObjectiveScore(markStudent);
                }

                // 结束评卷时，文档进行归档
                markDocumentService.initMarkDocument(markPaper);
            }

            UpdateWrapper<MarkPaper> updateWrapper = new UpdateWrapper<>();
            updateWrapper.lambda().set(MarkPaper::getStatus, status)
                    .eq(MarkPaper::getExamId, examId)
                    .eq(MarkPaper::getPaperNumber, paperNumber);
            this.update(updateWrapper);
        }

        return true;
    }

    @Override
    public List<MarkPaper> listQualityMarkPaperByStatus(MarkPaperStatus status, int uploadCount) {
        return this.baseMapper.listQualityMarkPaperByStatus(status.name(), uploadCount);
    }

    @Override
    public int getCourseCount(Long examId, String courseCode, String coursePaperId, MarkPaperStatus status, DataPermissionRule dpr) {
        return baseMapper.getCourseCount(examId, courseCode, coursePaperId, status.name(), dpr);
    }

    @Override
    public int getPaperNumberCount(Long examId, String courseCode, String coursePaperId, MarkPaperStatus status, DataPermissionRule dpr) {
        return baseMapper.getPaperNumberCount(examId, courseCode, coursePaperId, status.name(), dpr);
    }

    @Override
    public IPage<CheckScoreListDto> listStudentScoreList(Long examId, String courseCode, String paperNumber, Integer pageNumber, Integer pageSize) {
        SysUser sysUser = (SysUser) ServletUtil.getRequestUser();
        Page<CheckScoreListDto> page = new Page<>(pageNumber, pageSize);
        DataPermissionRule dpr = basicRoleDataPermissionService.findDataPermission(sysUser.getSchoolId(), sysUser.getId(), ServletUtil.getRequest().getServletPath());
        return this.baseMapper.listStudentScoreList(page, examId, courseCode, paperNumber, dpr);
    }

    @Override
    public void updateStatus(Long examId, String paperNumber, MarkPaperStatus newStatus, MarkPaperStatus currentStatus) {
        UpdateWrapper<MarkPaper> updateWrapper = new UpdateWrapper<>();
        updateWrapper.lambda().set(MarkPaper::getStatus, newStatus)
                .eq(MarkPaper::getExamId, examId)
                .eq(MarkPaper::getPaperNumber, paperNumber)
                .eq(MarkPaper::getStatus, currentStatus);
        this.update(updateWrapper);
    }

    @Override
    public void updateUploadCount(Long examId, String paperNumber, int countUploaded) {
        UpdateWrapper<MarkPaper> updateWrapper = new UpdateWrapper<>();
        updateWrapper.lambda().set(MarkPaper::getUploadCount, countUploaded)
                .eq(MarkPaper::getExamId, examId)
                .eq(MarkPaper::getPaperNumber, paperNumber);
        this.update(updateWrapper);
    }

    @Override
    public IPage<MarkPaperPackageDto> listPackage(Long examId, String paperNumber, String packageCode, Integer pageNumber, Integer pageSize) {
        MarkPaper markPaper = this.getByExamIdAndPaperNumber(examId, paperNumber);
        Page<MarkPaperPackageDto> page = new Page<>(pageNumber, pageSize);
        IPage<MarkPaperPackageDto> markPaperPackageDtoIPage = this.baseMapper.listPackage(page, examId, markPaper.getCoursePaperId(), packageCode);
        for (MarkPaperPackageDto packageDto : markPaperPackageDtoIPage.getRecords()) {
            List<ScanPackage> scanPackageList = scanPackageService.listByExamIdAndCoursePaperIdAndPackageCode(examId, markPaper.getCoursePaperId(), packageDto.getPackageCode());
            List<String> urls = scanPackageList.stream().map(m -> teachcloudCommonService.filePreview(m.getPath())).collect(Collectors.toList());
            packageDto.setCount(urls.size());
            packageDto.setUrls(urls);
        }
        return markPaperPackageDtoIPage;
    }

    @Override
    public MarkPaper getByExamIdAndCoursePaperId(Long examId, String coursePaperId) {
        QueryWrapper<MarkPaper> wrapper = new QueryWrapper<>();
        wrapper.lambda().eq(MarkPaper::getExamId, examId)
                .eq(MarkPaper::getCoursePaperId, coursePaperId);
        wrapper.last("LIMIT 1");
        return this.getOne(wrapper);
    }

    @Override
    public List<MarkPaper> listByExamId(Long examId, MarkPaperStatus status, DataPermissionRule dpr) {
        return this.baseMapper.listByExamId(examId, status.name(), dpr);
    }

    @Override
    public void updateGroupStatusByExamIdAndPaperNumber(boolean groupStatus, Long examId, String paperNumber) {
        UpdateWrapper<MarkPaper> updateWrapper = new UpdateWrapper<>();
        updateWrapper.lambda().set(MarkPaper::getGroupStatus, groupStatus)
                .eq(MarkPaper::getExamId, examId)
                .eq(MarkPaper::getPaperNumber, paperNumber);
        this.update(updateWrapper);
    }

    @Override
    public int countByPropositionTeacherId(boolean status) {
        Long schoolId = Long.valueOf(ServletUtil.getRequestHeaderSchoolId().toString());
        SysUser sysUser = (SysUser) ServletUtil.getRequestUser();
        DataPermissionRule dpr = basicRoleDataPermissionService.findDataPermission(schoolId, sysUser.getId(), "/api/admin/mark/setting/list");
        List<MarkSettingDto> markSettingDtoList = this.baseMapper.listPaperSetting(schoolId, null, null, null, status, dpr);
        return CollectionUtils.isEmpty(markSettingDtoList) ? 0 : markSettingDtoList.size();
    }

    @Override
    public void updateStudentCountByExamIdAndPaperNumberAndPaperType(Long examId, String paperNumber, String paperType) {
        long count = markStudentService.countByExamIdAndPaperNumber(examId, paperNumber, paperType);
        UpdateWrapper<MarkPaper> updateWrapper = new UpdateWrapper<>();
        updateWrapper.lambda().set(MarkPaper::getStudentCount, count)
                .eq(MarkPaper::getExamId, examId)
                .eq(MarkPaper::getPaperNumber, paperNumber)
                .eq(MarkPaper::getPaperType, paperType);
        this.update(updateWrapper);
    }

    @Override
    public SettingDto getSetting(Long examId, String paperNumber) {
        SettingDto settingDto = new SettingDto();
        SysUser sysUser = (SysUser) ServletUtil.getRequestUser();
        settingDto.setExamId(examId);
        settingDto.setPaperNumber(paperNumber);
        settingDto.setUserName(sysUser.getRealName());
        MarkPaper markPaper = this.getByExamIdAndPaperNumber(examId, paperNumber);
        if (markPaper != null) {
            settingDto.setCourseCode(markPaper.getCourseCode());
            settingDto.setCourseName(markPaper.getCourseName());
            if (StringUtils.isNotBlank(markPaper.getPaperFilePath())) {
                settingDto.setPaperUrl(teachcloudCommonService.filePreview(markPaper.getPaperFilePath()));
            }
            if (StringUtils.isNotBlank(markPaper.getAnswerFilePath())) {
                settingDto.setAnswerUrl(teachcloudCommonService.filePreview(markPaper.getAnswerFilePath()));
            }
        }
        return settingDto;
    }

    @Override
    public IPage<ArchiveScoreVo> scoreList(ArchiveScoreQuery query) {
        SysUser sysUser = (SysUser) ServletUtil.getRequestUser();
        Page<ArchiveScoreVo> page = new Page<>(query.getPageNumber(), query.getPageSize());
        DataPermissionRule dpr = basicRoleDataPermissionService.findDataPermission(sysUser.getSchoolId(), sysUser.getId(), ServletUtil.getRequest().getServletPath());
        IPage<ArchiveScoreVo> ret = this.baseMapper.scoreList(page, sysUser.getSchoolId(), query, dpr);
        if (CollectionUtils.isNotEmpty(ret.getRecords())) {
            for (ArchiveScoreVo vo : ret.getRecords()) {
                double total = vo.getStudentCount() - vo.getAbsentCount();
                vo.setPassRate(Calculator.divide2String(Calculator.multiply(vo.getPassCount(), 100), total, 2));
                vo.setExcellentRate(Calculator.divide2String(Calculator.multiply(vo.getExcellentCount(), 100), total, 2));
                vo.setAvgScore(Calculator.round(vo.getAvgScore(), 2));
                vo.setMinScore(Calculator.round(vo.getMinScore(), 2));
                vo.setMaxScore(Calculator.round(vo.getMaxScore(), 2));
            }
        }
        return ret;
    }

    @Override
    public void deleteByExamIdAndPaperNumber(Long examId, String paperNumber, Boolean checkMarkStudent) {
        if (checkMarkStudent) {
            long count = markStudentService.countByExamIdAndPaperNumber(examId, paperNumber, null);
            if (count > 0) {
                return;
            }
        }
        UpdateWrapper<MarkPaper> updateWrapper = new UpdateWrapper<>();
        updateWrapper.lambda().eq(MarkPaper::getExamId, examId)
                .eq(MarkPaper::getPaperNumber, paperNumber);
        this.remove(updateWrapper);
    }

    @Override
    public void updateAbsentCount(Long examId, String paperNumber, int absentCount) {
        UpdateWrapper<MarkPaper> updateWrapper = new UpdateWrapper<>();
        updateWrapper.lambda().set(MarkPaper::getAbsentCount, absentCount)
                .eq(MarkPaper::getExamId, examId)
                .eq(MarkPaper::getPaperNumber, paperNumber);
        this.update(updateWrapper);
    }

    @Override
    public IPage<ArchivePaperVo> documentList(ArchivePaperQuery query) {
        SysUser sysUser = (SysUser) ServletUtil.getRequestUser();
        Page<ArchiveScoreVo> page = new Page<>(query.getPageNumber(), query.getPageSize());
        DataPermissionRule dpr = basicRoleDataPermissionService.findDataPermission(sysUser.getSchoolId(), sysUser.getId(), ServletUtil.getRequest().getServletPath());
        IPage<ArchivePaperVo> ret = this.baseMapper.documentList(page, query, dpr);
        return ret;
    }
}
