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

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

import javax.validation.constraints.NotNull;

import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.qmth.boot.core.concurrent.service.ConcurrentService;
import com.qmth.boot.core.exception.ParameterException;
import com.qmth.teachcloud.common.entity.SysUser;
import com.qmth.teachcloud.common.enums.ExceptionResultEnum;
import com.qmth.teachcloud.common.enums.ScanStatus;
import com.qmth.teachcloud.common.enums.mark.SubjectiveStatus;
import com.qmth.teachcloud.mark.bean.papermigrate.PaperMigrateDomain;
import com.qmth.teachcloud.mark.bean.papermigrate.PaperMigratePage;
import com.qmth.teachcloud.mark.bean.papermigrate.PaperMigratePaper;
import com.qmth.teachcloud.mark.bean.papermigrate.PaperMigrateVo;
import com.qmth.teachcloud.mark.bean.scananswer.StudentPaperVo;
import com.qmth.teachcloud.mark.bean.scanpaper.PaperVo;
import com.qmth.teachcloud.mark.dto.mark.score.StudentPaperDetailDto;
import com.qmth.teachcloud.mark.entity.*;
import com.qmth.teachcloud.mark.enums.LockType;
import com.qmth.teachcloud.mark.mapper.ScanPaperMapper;
import com.qmth.teachcloud.mark.service.*;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author xf
 * @since 2023-09-22
 */
@Service
public class ScanPaperServiceImpl extends ServiceImpl<ScanPaperMapper, ScanPaper> implements ScanPaperService {

    @Autowired
    private ScanAnswerCardService answerCardService;

    @Autowired
    private ScanPaperPageService paperPageService;

    @Autowired
    private MarkStudentService markStudentService;

    @Autowired
    private ConcurrentService concurrentService;

    @Autowired
    private ScanStudentPaperService scanStudentPaperService;

    @Autowired
    private MarkService markService;

    /**
     * 全量更新paper和所属的page集合，并刷新paper相关属性
     *
     * @param paper
     * @param pages
     */
    @Override
    @Transactional
    public void savePaperAndPages(@NotNull ScanPaper paper, @NotNull List<ScanPaperPage> pages) {
        // 与卡格式校验
        ScanAnswerCard answerCard = answerCardService.findByExamAndNumber(paper.getExamId(), paper.getCardNumber());
        if (answerCard == null) {
            throw ExceptionResultEnum.ERROR.exception("卡格式不存在, cardNumber=" + paper.getCardNumber());
        }
        if (pages.size() != (answerCard.getSinglePage() ? 1 : 2)) {
            throw ExceptionResultEnum.ERROR.exception("paper.page数量与卡格式不符, cardNumber=" + paper.getCardNumber());
        }
        // 更新paper属性
        paper.setPageCount(pages.size());
        paper.setQuestionFilled(false);
        // 判断是否客观题已填涂
        for (ScanPaperPage page : pages) {
            paper.setQuestionFilled(paper.getQuestionFilled() || page.isQuestionFilled());
        }
        // 保存paper数据
        saveOrUpdate(paper);
        for (ScanPaperPage page : pages) {
            // FIXME
            // 验证原图与裁切图已上传
            // if (!fileStore.exist(page.getSheetPath())) {
            // throw ExceptionResultEnum.ERROR.exception("原图不存在:" + page.getSheetPath());
            // }
            // if (page.getId() == null) {
            // page.setId(SystemConstant.getDbUuid());
            // }
            page.setPaperId(paper.getId());
        }
        // 保存page数据
        paperPageService.saveOrUpdateBatchByMultiId(pages);
    }

    @Override
    public List<StudentPaperDetailDto> listStudentPaperDetail(Long studentId) {
        return this.baseMapper.listStudentPaperDetail(studentId);
    }

    @Override
    public int getCountByExamAndCardNumber(Long examId, Integer number) {
        LambdaQueryWrapper<ScanPaper> lw = new LambdaQueryWrapper<>();
        lw.eq(ScanPaper::getExamId, examId);
        lw.eq(ScanPaper::getCardNumber, number);
        return this.count(lw);
    }

    @Override
    public List<PaperVo> findStudentPaper(Long studentId) {
        return baseMapper.findStudentPaper(studentId);
    }

    @Override
    public List<StudentPaperVo> listByStudentIds(List<Long> paramList) {
        return baseMapper.listByStudentIds(paramList);
    }

    private boolean isEmptyPage(PaperMigrateDomain domain) {
        for (PaperMigratePaper paper : domain.getPapers()) {
            if (CollectionUtils.isEmpty(paper.getPages())) {
                return true;
            }
        }
        return false;
    }

    @Transactional
    @Override
    public PaperMigrateVo paperMigrate(SysUser user, PaperMigrateDomain domain) {
        if (CollectionUtils.isEmpty(domain.getPapers())) {
            throw new ParameterException("papers信息不能为空");
        }
        List<Long> paperIds = domain.getPapers().stream().map(e -> e.getPaperId()).collect(Collectors.toList());
        // 查找目标考生
        MarkStudent student = markStudentService.findByExamIdAndCoursePaperIdAndStudentCode(domain.getExamId(),
                domain.getCoursePaperId(), domain.getStudentCode());
        if (student == null) {
            throw new ParameterException("考生信息未找到");
        }
        // 目标考生已扫描时，输入卡格式需要保持一致
        if (student.getCardNumber() != null && !student.getCardNumber().equals(domain.getCardNumber())) {
            throw new ParameterException("卡格式与目标考生不一致");
        }
        // 校验卡格式
        ScanAnswerCard answerCard = answerCardService.findByExamAndNumber(domain.getExamId(), domain.getCardNumber());
        if (answerCard == null) {
            throw new ParameterException("卡格式信息未找到");
        }
        // cardNumber有变化，需要重新识别
        if (student.getCardNumber() != null && !student.getCardNumber().equals(domain.getCardNumber())
                && isEmptyPage(domain)) {
            throw new ParameterException("卡格式有变化需要重新识别, pages信息不能为空");
        }

        // 指定paper现有绑定考生
        MarkStudent previousStudent = null;
        List<ScanStudentPaper> previousStudentPapers = scanStudentPaperService.findByPaperIds(paperIds);
        if (CollectionUtils.isEmpty(previousStudentPapers)) {
            throw new ParameterException("被绑定的题卡信息未关联考生");
        }
        checkStudent(previousStudentPapers, paperIds);
        Long previousStudentId = previousStudentPapers.get(0).getStudentId();
        // 考生ID变了才需要获取原考生
        if (!previousStudentId.equals(student.getId())) {
            previousStudent = markStudentService.getById(previousStudentId);
        }
        if (previousStudent != null) {
            concurrentService.getReadWriteLock(LockType.STUDENT + "-" + previousStudent.getId()).writeLock().lock();
        }
        concurrentService.getReadWriteLock(LockType.STUDENT + "-" + student.getId()).writeLock().lock();
        try {
            // 解绑paper和原有考生
            if (previousStudent != null) {
                // 重置考生状态
                resetStudent(previousStudent);
                markStudentService.updateStudentAndPaper(user, previousStudent.getId(), null);
            }
            for (PaperMigratePaper vp : domain.getPapers()) {
                ScanPaper paper = getById(vp.getPaperId());
                if (paper == null) {
                    throw new ParameterException("题卡信息未找到.paperId=" + vp.getPaperId());
                }
                // 更新paper属性
                paper.setCardNumber(domain.getCardNumber());
                paper.setNumber(vp.getPaperNumber());
                // 修改的paper强制置为人工绑定
                paper.setAssigned(true);
                // 原有mismatch标记需要清除
                paper.setMismatch(false);
                paper.setUpdaterId(user.getId());
                paper.setUpdateTime(System.currentTimeMillis());
                if (CollectionUtils.isNotEmpty(vp.getPages())) {
                    // 重新识别时更新所有page
                    List<ScanPaperPage> pages = new ArrayList<>();
                    for (PaperMigratePage page : vp.getPages()) {
                        pages.add(page.update(paperPageService.findPaperIdAndIndex(paper.getId(), page.getIndex())));
                    }
                    savePaperAndPages(paper, pages);
                } else {
                    saveOrUpdate(paper);
                }
            }
            // 查找目标考生现有绑定paper
            List<ScanStudentPaper> currentStudentPaperList = scanStudentPaperService.findByStudentId(student.getId());
            if (CollectionUtils.isNotEmpty(currentStudentPaperList)) {
                for (ScanStudentPaper current : currentStudentPaperList) {
                    updateMismatch(current.getPaperId(), true);
                }
            }
            // 更新目标考生状态
            currentStudentPaperList.clear();
            for (PaperMigratePaper vp : domain.getPapers()) {
                currentStudentPaperList
                        .add(new ScanStudentPaper(student.getId(), vp.getPaperNumber(), vp.getPaperId()));
            }
            markStudentService.updateStudentAndPaper(user, student.getId(), currentStudentPaperList);
            // 返回数据结果
            return PaperMigrateVo.create(student.getId());
        } finally {
            concurrentService.getReadWriteLock(LockType.STUDENT + "-" + student.getId()).writeLock().unlock();
            if (previousStudent != null) {
                concurrentService.getReadWriteLock(LockType.STUDENT + "-" + previousStudent.getId()).writeLock()
                        .unlock();
            }
        }
    }

    private void resetStudent(MarkStudent ms) {
        ms.setSheetCount(0);
        ms.setSheetPath(null);
        ms.setAnswers(null);
        ms.setUpload(false);
        ms.setAbsent(false);
        ms.setBreach(false);
        ms.setUploadTime(null);
        ms.setObjectiveScore(null);
        ms.setObjectiveScoreList(null);
        ms.setSubjectiveStatus(SubjectiveStatus.UNMARK);
        ms.setSubjectiveScore(null);
        ms.setSubjectiveScoreList(null);
        ms.setScanStatus(ScanStatus.UNEXIST);
        ms.setQuestionFilled(false);
        ms.setAssigned(false);
        ms.setInvalid(false);
        ms.setAssignConfirmed(false);
        ms.setAbsentSuspect(false);
        ms.setIncomplete(false);
        ms.setBreachCode(null);
        ms.setOmrAbsent(false);
        ms.setOmrAbsentChecked(false);
        ms.setCardNumber(null);
        markStudentService.saveOrUpdate(ms);
    }

    private void checkStudent(List<ScanStudentPaper> previousStudentPapers, List<Long> paperIds) {
        if (previousStudentPapers.size() != paperIds.size()) {
            throw new ParameterException("被绑定的题卡不属于同一个考生");
        }
        Long studentId = null;
        for (ScanStudentPaper sp : previousStudentPapers) {
            if (!paperIds.contains(sp.getPaperId())) {
                throw new ParameterException("被绑定的题卡不属于同一个考生");
            }
            if (studentId == null) {
                studentId = sp.getStudentId();
            } else {
                if (!sp.getStudentId().equals(studentId)) {
                    throw new ParameterException("被绑定的题卡不属于同一个考生");
                }
            }
        }
    }

    @Override
    @Transactional
    public boolean updateMismatch(Long id, boolean mismatch) {
        LambdaUpdateWrapper<ScanPaper> lw = new LambdaUpdateWrapper<>();
        lw.set(ScanPaper::getMismatch, mismatch);
        lw.eq(ScanPaper::getId, id);
        return this.update(lw);
    }

    @Override
    public boolean isScanByExamIdAndPaperNumber(Long examId, String paperNumber) {
        List<ScanAnswerCard> scanAnswerCardList = answerCardService.listByExamIdAndPaperNumber(examId, paperNumber);
        for (ScanAnswerCard scanAnswerCard : scanAnswerCardList) {
            int countByExamAndCardNumber = this.getCountByExamAndCardNumber(examId, scanAnswerCard.getNumber());
            if (countByExamAndCardNumber > 0) {
                return true;
            }
        }
        return false;
    }
}
