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

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
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.mark.MarkUser;
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.service.SysUserService;
import com.qmth.teachcloud.common.util.ServletUtil;
import com.qmth.teachcloud.mark.dto.mark.entrance.MarkEntranceDto;
import com.qmth.teachcloud.mark.dto.mark.manage.MarkQualityChartDto;
import com.qmth.teachcloud.mark.dto.mark.manage.MarkQualityDto;
import com.qmth.teachcloud.mark.dto.mark.manage.MarkUserGroupProgressDto;
import com.qmth.teachcloud.mark.entity.*;
import com.qmth.teachcloud.mark.enums.LockType;
import com.qmth.teachcloud.mark.enums.MarkTaskStatus;
import com.qmth.teachcloud.mark.lock.LockService;
import com.qmth.teachcloud.mark.mapper.MarkUserGroupMapper;
import com.qmth.teachcloud.mark.service.*;
import com.qmth.teachcloud.mark.utils.Calculator;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * <p>
 * 评卷员表 服务实现类
 * </p>
 *
 * @author xf
 * @since 2023-09-22
 */
@Service
public class MarkUserGroupServiceImpl extends ServiceImpl<MarkUserGroupMapper, MarkUserGroup> implements MarkUserGroupService {

    @Resource
    private MarkQuestionService markQuestionService;
    @Resource
    private MarkGroupService markGroupService;
    @Resource
    private MarkTaskService markTaskService;
    @Resource
    private MarkPaperService markPaperService;
    @Resource
    private MarkUserClassService markUserClassService;
    @Resource
    private MarkSyncService markSyncService;
    @Resource
    private SysUserService sysUserService;
    @Resource
    private MarkService markService;
    @Resource
    private LockService lockService;

    @Override
    public IPage<MarkEntranceDto> listEntranceGroup(Long examId, String courseCode, String paperNumber, Integer pageNumber, Integer pageSize) {
        SysUser sysUser = (SysUser) ServletUtil.getRequestUser();
        Page<MarkUserGroup> page = new Page<>(pageNumber, pageSize);
        IPage<MarkEntranceDto> markEntranceDtoIPage = this.baseMapper.listEntranceGroup(page, examId, courseCode, paperNumber, sysUser.getId(), MarkPaperStatus.FORMAL.name());
        for (MarkEntranceDto record : markEntranceDtoIPage.getRecords()) {
            int taskCount = record.getTaskCount();
            int markedCount = record.getMarkedCount();
            MarkPaper markPaper = markPaperService.getByExamIdAndPaperNumber(examId, record.getPaperNumber());
            if (markPaper != null && markPaper.getOpenMarkClass() != null && markPaper.getOpenMarkClass().booleanValue()) {
                List<MarkUserClass> markUserClassList = markUserClassService.listByExamIdAndPaperNumberAndUserId(examId, record.getPaperNumber(), sysUser.getId());
                List<String> classNames = null;
                if (CollectionUtils.isNotEmpty(markUserClassList)) {
                    classNames = markUserClassList.stream().map(MarkUserClass::getClassName).collect(Collectors.toList());
                }
                taskCount = markTaskService.countByExamIdAndPaperNumberAndGroupNumberAndUserIdAndAndClassNameStatusIn(examId, record.getPaperNumber(), record.getGroupNumber(), null, classNames);
                record.setTaskCount(taskCount);
                markedCount = markTaskService.countByExamIdAndPaperNumberAndGroupNumberAndUserIdAndAndClassNameStatusIn(examId, record.getPaperNumber(), record.getGroupNumber(), null, classNames, MarkTaskStatus.MARKED, MarkTaskStatus.ARBITRATED);
                record.setMarkedCount(markedCount);
            }
            record.setLeftCount(taskCount == 0 ? 0 : taskCount - markedCount);
            // 评卷进度
            if (taskCount == 0) {
                record.setPercent(0D);
            } else {
                BigDecimal bigDecimal = new BigDecimal(markedCount * 100).divide(new BigDecimal(taskCount), 2, RoundingMode.HALF_UP);
                record.setPercent(bigDecimal.doubleValue());
            }
            // 分组题目
            record.setGroupQuestions(markQuestionService.assembleGroupQuestionsByExamIdAndPaperNumberAndNumber(record.getExamId(), record.getPaperNumber(), record.getGroupNumber()));
        }

        return markEntranceDtoIPage;
    }

    @Override
    public List<MarkUser> listGroupUserByExamIdAndPaperNumberAndGroupNumberAndClassName(Long examId, String paperNumber, Integer groupNumber, String className) {
        return this.baseMapper.listGroupUserByExamIdAndPaperNumberAndGroupNumber(examId, paperNumber, groupNumber, className);
    }

    @Override
    public IPage<MarkUserGroupProgressDto> pageGroupUserByExamIdAndPaperNumberAndGroupNumber(Long examId, String paperNumber, Integer groupNumber, String loginName, String className, Integer pageNumber, Integer pageSize) {
        Page<MarkUserGroupProgressDto> page = new Page<>(pageNumber, pageSize);
        IPage<MarkUserGroupProgressDto> markUserGroupProgressDtoIPage = this.baseMapper.pageGroupUserByExamIdAndPaperNumberAndGroupNumber(page, examId, paperNumber, groupNumber, loginName, className);
        for (MarkUserGroupProgressDto record : markUserGroupProgressDtoIPage.getRecords()) {
            record.setGroupQuestions(markQuestionService.assembleGroupQuestionsByExamIdAndPaperNumberAndNumber(examId, paperNumber, record.getGroupNumber()));
            List<MarkTask> markTaskList = markTaskService.listByExamIdAndPaperNumberAndGroupNumberAndUserIdAndClassName(examId, paperNumber, record.getGroupNumber(), record.getUserId(), null);
            record.setResetting(lockService.isLocked(LockType.MARKER_RESET, record.getMarkUserGroupId()));
            record.setMarkedCount(markTaskService.markedCount(markTaskList));
            MarkGroup markGroup = markGroupService.getByExamIdAndPaperNumberAndGroupNumber(examId, paperNumber, record.getGroupNumber());
            record.setCurrentCount(markService.applyCurrentCount(markGroup, record.getMarkUserGroupId()));
        }
        return markUserGroupProgressDtoIPage;
    }

    @Override
    public void addMarkUserGroup(Long examId, String paperNumber, Integer groupNumber, List<Long> userIds) {
        if (CollectionUtils.isNotEmpty(userIds)) {
            List<MarkUserGroup> markUserGroups = new ArrayList<>();
            for (Long userId : userIds) {
                MarkUserGroup markUserGroup = this.getByExamIdAndPaperNumberAndGroupNumberAndUserId(examId, paperNumber, groupNumber, userId);
                if (markUserGroup == null) {
                    markUserGroups.add(new MarkUserGroup(examId, paperNumber, groupNumber, userId));
                }
            }
            if (CollectionUtils.isNotEmpty(markUserGroups)) {
                this.saveBatch(markUserGroups);
            }
        }
    }

    @Override
    public void setTopCount(List<Long> markUserGroupIds, Integer topCount) {
        for (Long markUserGroupId : markUserGroupIds) {
            MarkUserGroup markUserGroup = this.getById(markUserGroupId);
            if (markUserGroup != null) {
                markUserGroup.setTopCount(topCount);
                this.updateById(markUserGroup);
            }
        }
    }

    @Override
    public MarkUserGroup getByExamIdAndPaperNumberAndGroupNumberAndUserId(Long examId, String paperNumber, Integer groupNumber, Long userId) {
        QueryWrapper<MarkUserGroup> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda().eq(MarkUserGroup::getExamId, examId)
                .eq(MarkUserGroup::getPaperNumber, paperNumber)
                .eq(MarkUserGroup::getGroupNumber, groupNumber)
                .eq(MarkUserGroup::getUserId, userId);
        return this.getOne(queryWrapper);
    }

    @Override
    public void deleteMarker(Long markUserGroupId) {
        MarkUserGroup markUserGroup = this.getById(markUserGroupId);
        if (markUserGroup == null) {
            throw ExceptionResultEnum.ERROR.exception("评卷员不存在");
        }
        int count = markTaskService.countByExamIdAndPaperNumberAndGroupNumberAndUserId(markUserGroup.getExamId(), markUserGroup.getPaperNumber(), markUserGroup.getGroupNumber(), markUserGroup.getUserId());
        if (count > 0) {
            throw ExceptionResultEnum.ERROR.exception("删除评卷员失败，该评卷员已开始评卷");
        } else {
            // 先回收，再删除
            releaseMarker(Arrays.asList(markUserGroupId));
            this.removeById(markUserGroupId);
        }
    }

    @Override
    public void releaseMarker(List<Long> markUserGroupIds) {
        for (Long markUserGroupId : markUserGroupIds) {
            MarkUserGroup markUserGroup = this.getById(markUserGroupId);
            if (markUserGroup != null) {
                try {
                    lockService.waitlock(LockType.MARK_USER_GROUP, markUserGroupId);
                    markService.releaseByMarkUserGroup(markUserGroup);
                } catch (Exception e) {
                    log.error("release marker error", e);
                } finally {
                    lockService.unlock(LockType.MARK_USER_GROUP, markUserGroupId);
                }
            }
        }
    }

    @Override
    public boolean resetMarker(MarkUserGroup markUserGroup) {
        if (markUserGroup != null) {
            if (lockService.trylock(LockType.MARKER_RESET, markUserGroup.getId())) {
                markSyncService.markerResetSync(markUserGroup);
                return true;
            } else {
                throw ExceptionResultEnum.ERROR.exception("评卷员正在重置");
            }
        }
        return true;
    }

    @Override
    public void resetById(Long id) {
        UpdateWrapper<MarkUserGroup> updateWrapper = new UpdateWrapper<>();
        updateWrapper.lambda().set(MarkUserGroup::getFinishCount, null)
                .set(MarkUserGroup::getHeaderFinishCount, null)
                .set(MarkUserGroup::getValidCount, null)
                .set(MarkUserGroup::getAvgScore, null)
                .set(MarkUserGroup::getAvgSpeed, null)
                .set(MarkUserGroup::getStdevScore, null)
                .set(MarkUserGroup::getMaxScore, null)
                .set(MarkUserGroup::getMinScore, null)
                .eq(MarkUserGroup::getId, id);
        this.update(updateWrapper);
    }

    @Override
    public IPage<MarkQualityDto> pageQuality(Long examId, String paperNumber, Integer groupNumber, String loginName, Integer pageNumber, Integer pageSize) {
        Page<MarkQualityDto> page = new Page<>(pageNumber, pageSize);
        IPage<MarkQualityDto> markQualityDtoIPage = this.baseMapper.pageQuality(page, examId, paperNumber, groupNumber, loginName);
        for (MarkQualityDto qualityDto : markQualityDtoIPage.getRecords()) {
            MarkGroup markGroup = markGroupService.getByExamIdAndPaperNumberAndGroupNumber(examId, paperNumber, qualityDto.getGroupNumber());
            markGroup.setQuestionList(markQuestionService.listQuestionByExamIdAndPaperNumberAndGroupNumber(examId, paperNumber, qualityDto.getGroupNumber(), false));
            qualityDto.setGroupQuestions(markGroup.getGroupQuestions());
            if (qualityDto.getUserId() != null) {
                SysUser sysUser = sysUserService.getByUserId(qualityDto.getUserId());
                qualityDto.setLoginName(sysUser.getLoginName());
                qualityDto.setName(sysUser.getRealName());
                qualityDto.setOrgName(sysUser.getOrgName());
                qualityDto.setAdoptionRate(Calculator.divide2String(Calculator.multiply(qualityDto.getValidCount(), 100), Double.valueOf(qualityDto.getFinishCount()), 2));
                qualityDto.setAvgScore(Calculator.round(qualityDto.getAvgScore(), 2));
            }
        }
        return markQualityDtoIPage;
    }

    @Override
    public void updateQuality(Long examId, String paperNumber, Integer groupNumber) {
        if (groupNumber == null) {
            throw ExceptionResultEnum.ERROR.exception("请选择评阅题目");
        }
        MarkGroup markGroup = markGroupService.getByExamIdAndPaperNumberAndGroupNumber(examId, paperNumber, groupNumber);
        if (markGroup != null) {
            String lockKey = markService.getGroupKey(markGroup);
            if (lockService.trylock(LockType.BATCH_QUALITY, lockKey)) {
                markSyncService.updateQuality(this.listByExamIdAndPaperNumberAndGroupNumber(examId, paperNumber, groupNumber), lockKey);
            }
        }
    }

    @Override
    public List<MarkUserGroup> listByExamIdAndPaperNumberAndGroupNumber(Long examId, String paperNumber, Integer groupNumber) {
        QueryWrapper<MarkUserGroup> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda().eq(MarkUserGroup::getExamId, examId)
                .eq(MarkUserGroup::getPaperNumber, paperNumber)
                .eq(MarkUserGroup::getGroupNumber, groupNumber)
                .orderByAsc(MarkUserGroup::getId);
        return this.list(queryWrapper);
    }

    @Override
    public void updateQualityById(Long markUserGroupId, int finishCount, int headerFinishCount, int validCount, double avgSpeed, double avgScore, double stdevScore, double maxScore, double minScore) {
        UpdateWrapper<MarkUserGroup> updateWrapper = new UpdateWrapper<>();
        updateWrapper.lambda().set(MarkUserGroup::getFinishCount, finishCount)
                .set(MarkUserGroup::getHeaderFinishCount, headerFinishCount)
                .set(MarkUserGroup::getValidCount, validCount)
                .set(MarkUserGroup::getAvgSpeed, avgSpeed)
                .set(MarkUserGroup::getAvgScore, avgScore)
                .set(MarkUserGroup::getStdevScore, stdevScore)
                .set(MarkUserGroup::getMaxScore, maxScore)
                .set(MarkUserGroup::getMinScore, minScore)
                .eq(MarkUserGroup::getId, markUserGroupId);
        this.update(updateWrapper);
    }

    @Override
    public List<MarkQualityChartDto> listQualityChart(Long examId, String paperNumber, Integer groupNumber) {
        if (groupNumber == null) {
            throw ExceptionResultEnum.ERROR.exception("请选择对应的科目及分组，再查看给分曲线");
        }
        List<MarkQualityChartDto> markQualityChartDtoList = new ArrayList<>();
        List<MarkUserGroup> markUserGroupList = this.listByExamIdAndPaperNumberAndGroupNumber(examId, paperNumber, groupNumber);
        Double totalAvgScore = markUserGroupList.stream().filter(m -> m.getAvgScore() != null).collect(Collectors.averagingDouble(MarkUserGroup::getAvgScore));
        for (MarkUserGroup markUserGroup : markUserGroupList) {
            MarkQualityChartDto markQualityChartDto = new MarkQualityChartDto();
            markQualityChartDto.setUserId(markUserGroup.getUserId());
            SysUser sysUser = sysUserService.getById(markUserGroup.getUserId());
            markQualityChartDto.setLoginName(sysUser.getLoginName());
            markQualityChartDto.setName(sysUser.getRealName());
            markQualityChartDto.setTotalAvgScore(Calculator.round(totalAvgScore, 2));
            markQualityChartDto.setAvgScore(markUserGroup.getAvgScore() == null ? null : Calculator.round(markUserGroup.getAvgScore(), 2));
            markQualityChartDto.setMaxScore(markUserGroup.getMaxScore() == null ? null : Calculator.round(markUserGroup.getMaxScore(), 2));
            markQualityChartDto.setMinScore(markUserGroup.getMinScore() == null ? null : Calculator.round(markUserGroup.getMinScore(), 2));
            markQualityChartDtoList.add(markQualityChartDto);
        }
        return markQualityChartDtoList;
    }

    @Override
    public List<MarkUserGroup> listByExamIdAndPaperNumberAndUserId(Long examId, String paperNumber, Long userId) {
        QueryWrapper<MarkUserGroup> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda().eq(MarkUserGroup::getExamId, examId)
                .eq(MarkUserGroup::getPaperNumber, paperNumber)
                .eq(MarkUserGroup::getUserId, userId)
                .orderByAsc(MarkUserGroup::getId);
        return this.list(queryWrapper);
    }

    @Override
    public void deleteByExamIdAndPaperNumberAndGroupNumber(Long examId, String paperNumber, Integer groupNumber) {
        UpdateWrapper<MarkUserGroup> updateWrapper = new UpdateWrapper<>();
        updateWrapper.lambda().eq(MarkUserGroup::getExamId, examId)
                .eq(MarkUserGroup::getPaperNumber, paperNumber)
                .eq(MarkUserGroup::getGroupNumber, groupNumber);
        this.remove(updateWrapper);
    }

    @Override
    public void updateMode(Long examId, String paperNumber, Integer groupNumber, Long userId, MarkMode mode) {
        UpdateWrapper<MarkUserGroup> updateWrapper = new UpdateWrapper<>();
        LambdaUpdateWrapper<MarkUserGroup> lambdaUpdateWrapper = updateWrapper.lambda();
        lambdaUpdateWrapper.set(MarkUserGroup::getMode, mode)
                .eq(MarkUserGroup::getExamId, examId)
                .eq(MarkUserGroup::getPaperNumber, paperNumber);
        if (groupNumber != null) {
            lambdaUpdateWrapper.eq(MarkUserGroup::getGroupNumber, groupNumber);
        }
        if (userId != null) {
            lambdaUpdateWrapper.eq(MarkUserGroup::getUserId, userId);
        }
        this.update(updateWrapper);
    }

    @Override
    public List<String> listBindMarkers(Long examId, String paperNumber, Integer groupNumber) {
        List<MarkUserGroup> markUserGroups = listByExamIdAndPaperNumberAndGroupNumber(examId, paperNumber, groupNumber);
        List<String> userIds = new ArrayList<>();
        if (CollectionUtils.isNotEmpty(markUserGroups)) {
            userIds = markUserGroups.stream().map(m -> String.valueOf(m.getUserId())).collect(Collectors.toList());
        }
        return userIds;
    }
}
