package cn.com.qmth.stmms.biz.mark.service.Impl;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import cn.com.qmth.stmms.biz.config.service.SystemConfigService;
import cn.com.qmth.stmms.biz.config.service.impl.SystemCache;
import cn.com.qmth.stmms.biz.exam.model.ExamStudent;
import cn.com.qmth.stmms.biz.exam.model.ExamSubject;
import cn.com.qmth.stmms.biz.exam.model.MarkGroup;
import cn.com.qmth.stmms.biz.exam.model.Marker;
import cn.com.qmth.stmms.biz.exam.service.ExamService;
import cn.com.qmth.stmms.biz.exam.service.ExamStudentService;
import cn.com.qmth.stmms.biz.exam.service.ExamSubjectService;
import cn.com.qmth.stmms.biz.exam.service.MarkGroupService;
import cn.com.qmth.stmms.biz.exam.service.MarkerService;
import cn.com.qmth.stmms.biz.lock.LockService;
import cn.com.qmth.stmms.biz.mark.model.MarkLibrary;
import cn.com.qmth.stmms.biz.mark.model.TrialLibrary;
import cn.com.qmth.stmms.biz.mark.service.MarkLibraryService;
import cn.com.qmth.stmms.biz.mark.service.MarkService;
import cn.com.qmth.stmms.biz.mark.service.TrialService;
import cn.com.qmth.stmms.biz.report.service.ReportService;
import cn.com.qmth.stmms.biz.utils.TaskLockUtil;
import cn.com.qmth.stmms.common.enums.ConfigType;
import cn.com.qmth.stmms.common.enums.ExamStatus;
import cn.com.qmth.stmms.common.enums.LibraryStatus;
import cn.com.qmth.stmms.common.enums.LockType;
import cn.com.qmth.stmms.common.enums.MarkStatus;

/**
 * 与评卷相关的所有定时任务
 *
 * @author luoshi
 */
@Component
public class MarkCronService {

    protected static final Logger log = LoggerFactory.getLogger(MarkCronService.class);

    @Autowired
    private ExamStudentService studentService;

    @Autowired
    private ExamSubjectService subjectService;

    @Autowired
    private MarkGroupService groupService;

    @Autowired
    private MarkLibraryService libraryService;

    @Autowired
    private TrialService trialService;

    @Autowired
    private MarkService markService;

    @Autowired
    private LockService lockService;

    @Autowired
    private MarkerService markerService;

    @Autowired
    private SystemCache systemCache;

    @Autowired
    private ExamService examService;

    @Value("${mark.activeExpireMinute}")
    private long markerActiveExpireMinute;

    @Autowired
    private ReportService reportService;

    @Autowired
    private SystemConfigService systemConfigService;

    /**
     * 自动释放可清除的锁
     */
    @Scheduled(cron = "${mark.cleanLockSchedule}")
    public void cronCleanLock() {
        try {
            lockService.clear();
        } catch (Exception e) {
            log.error("CronCleanTask error", e);
        }
    }

    /**
     * 自动释放超时未处理的评卷任务
     */
    @Scheduled(cron = "${mark.cleanTaskSchedule}")
    public void cronCleanTask() {
        try {
            long timeoutMinute = systemCache.getMarkTime();
            TaskLockUtil.clearTimeoutTask(timeoutMinute * 60 * 1000);
        } catch (Exception e) {
            log.error("CronCleanTask error", e);
        }
    }

    /**
     * 定时生成评卷任务
     */
    @Scheduled(fixedDelay = 60 * 1000, initialDelay = 60 * 1000)
    public void buildLibrary() {
        log.info("start auto-create library");
        try {
            List<Integer> examIds = groupService.findExamIdByStatus(ExamStatus.START, MarkStatus.TRIAL,
                    MarkStatus.FORMAL, MarkStatus.FINISH);
            for (Integer examId : examIds) {
                buildLibraryByExam(examId);
            }
        } catch (Exception e) {
            log.error("auto-create library error", e);
        } finally {
            log.info("finish auto-create library");
        }
    }

    /**
     * 定时更新评卷员质量监控指标
     */
    @Scheduled(fixedDelay = 60 * 1000, initialDelay = 60 * 1000)
    public void updateMarkerQuality() {
        log.info("start auto-update marker");
        try {
            List<Integer> examIds = groupService.findExamIdByStatus(ExamStatus.START, MarkStatus.FORMAL);
            for (Integer examId : examIds) {
                updateMarkerQualityByExam(examId);
            }
        } catch (Exception e) {
            log.error("auto-update marker error", e);
        } finally {
            log.info("finish auto-update marker");
        }
    }

    private void updateMarkerQualityByExam(Integer examId) {
        // 获取主观题总分大于0的科目
        List<ExamSubject> subjects = subjectService.list(examId, 0);
        for (ExamSubject subject : subjects) {
            List<MarkGroup> groups = groupService.findByExamAndSubjectAndStatus(examId, subject.getCode(),
                    MarkStatus.FORMAL);
            for (MarkGroup markGroup : groups) {
                List<Marker> markers = markerService.findByExamAndSubjectAndGroup(examId, subject.getCode(),
                        markGroup.getNumber());
                for (Marker marker : markers) {
                    try {
                        if (markService.needUpdateQuality(marker, markerActiveExpireMinute)) {
                            log.info("start auto-update markerId: " + marker.getId());
                            markService.updateQuality(marker);
                        }
                    } catch (Exception e) {
                        log.error("update marker quality error for markerId=" + marker.getId(), e);
                    }
                }
            }
        }
    }

    private void buildLibraryByExam(Integer examId) {
        // 获取主观题总分大于0的科目
        List<ExamSubject> subjects = subjectService.listSubjectiveScore(examId, 0d);
        for (ExamSubject subject : subjects) {
            try {
                lockService.watch(LockType.EXAM_SUBJECT, subject.getExamId(), subject.getCode());
                log.info("start create library for examId=" + subject.getExamId() + ", subjectCode="
                        + subject.getCode());
                // 清除缺考考生和违纪考生
                List<ExamStudent> list = studentService.findAbsentOrBreachLibraryStudent(subject.getExamId(),
                        subject.getCode());
                if (list != null) {
                    for (ExamStudent student : list) {
                        try {
                            lockService.waitlock(LockType.STUDENT, student.getId());
                            log.info("delete library for studentId=" + student.getId());
                            markService.deleteByStudent(student);
                        } catch (Exception e) {
                            log.error("delete student library error", e);
                        } finally {
                            lockService.unlock(LockType.STUDENT, student.getId());
                        }
                    }
                }
                // 处理正常考生
                // 生成试评考生
                List<MarkGroup> groups = groupService.findByExamAndSubjectAndStatus(subject.getExamId(),
                        subject.getCode(), MarkStatus.TRIAL);
                long trialCount = studentService.countByExamIdAndSubjectCodeAndTrial(examId, subject.getCode(), true);
                if (!groups.isEmpty() && trialCount < subject.getTrialCount()) {
                    while (trialCount < subject.getTrialCount() && trialCount < subject.getUploadCount()) {
                        // 取一个的考生
                        ExamStudent student = studentService.randomStudent(examId, subject.getCode());
                        if (student == null) {
                            break;
                        }
                        studentService.updateTrialById(student.getId(), true);
                        trialCount++;
                    }
                }
                groups = groupService.findByExamAndSubjectAndStatus(subject.getExamId(), subject.getCode(),
                        MarkStatus.FORMAL, MarkStatus.TRIAL);
                for (MarkGroup group : groups) {
                    // 试评状态的分组也提前生成
                    buildTrialLibrary(group);
                    // 生成正评任务
                    buildFormalLibrary(group);
                }
            } finally {
                lockService.unwatch(LockType.EXAM_SUBJECT, subject.getExamId(), subject.getCode());
            }
        }
    }

    private void buildFormalLibrary(MarkGroup group) {
        int pageSize = 100;
        try {
            // log.info("start create library for examId=" + group.getExamId() +
            // ", subjectCode=" + group.getSubjectCode()
            // + ", groupNumber=" + group.getNumber());
            lockService.watch(LockType.GROUP, group.getExamId(), group.getSubjectCode(), group.getNumber());
            // 上锁后重复验证分组状态
            if (!groupService.validateStatus(group.getExamId(), group.getSubjectCode(), group.getNumber(),
                    MarkStatus.FORMAL, MarkStatus.TRIAL)) {
                return;
            }
            int count = 0;
            List<ExamStudent> studentList = studentService.findUnLibraryStudent(group.getExamId(),
                    group.getSubjectCode(), group.getNumber(), pageSize);
            while (!studentList.isEmpty()) {
                List<MarkLibrary> libraryList = new ArrayList<>();
                int doubleCount = 0;
                int studentCount = studentList.size();
                for (ExamStudent student : studentList) {
                    MarkLibrary library = new MarkLibrary();
                    library.setExamId(student.getExamId());
                    library.setSubjectCode(student.getSubjectCode());
                    library.setGroupNumber(group.getNumber());
                    library.setStudentId(student.getId());
                    library.setExamNumber(student.getExamNumber());
                    library.setSecretNumber(student.getSecretNumber());
                    library.setTaskNumber(1);
                    library.setStatus(LibraryStatus.WAITING);
                    libraryList.add(library);
                    // 开启双评时需要判断是否生成第二份评卷任务
                    if (group.getDoubleRate() != null && group.getDoubleRate() > 0) {
                        boolean needDouble = false;
                        if (group.getDoubleRate() == 1) {
                            needDouble = true;
                        } else {
                            double libraryCount = libraryList.size();
                            int expectCount = (int) (studentCount * group.getDoubleRate());
                            // 随机数判断加入当前已经生成双评任务的比例加权
                            // 实际双评任务数小于理论生成数 &&（剩余未生成双评的考生数量小于剩余应生成的数量||随机比例）
                            needDouble = doubleCount < expectCount
                                    && ((studentCount - libraryCount + doubleCount) <= (expectCount - doubleCount) || Math
                                            .random() < group.getDoubleRate() + 0.1);
                        }
                        if (needDouble) {
                            library = new MarkLibrary();
                            library.setExamId(student.getExamId());
                            library.setSubjectCode(student.getSubjectCode());
                            library.setGroupNumber(group.getNumber());
                            library.setStudentId(student.getId());
                            library.setExamNumber(student.getExamNumber());
                            library.setSecretNumber(student.getSecretNumber());
                            library.setTaskNumber(2);
                            library.setStatus(LibraryStatus.WAITING);
                            libraryList.add(library);
                            doubleCount++;
                        }
                    }
                }
                // 有新任务创建
                if (!libraryList.isEmpty()) {
                    count += libraryList.size();
                    // 任务乱序
                    Collections.shuffle(libraryList);
                    // 批量保存
                    libraryService.save(libraryList);
                    // 正评状态才需要更新任务数量
                    if (group.getStatus() == MarkStatus.FORMAL) {
                        markService.updateLibraryCount(group);
                        markService.updateMarkedCount(group);
                    }
                }
                studentList = studentService.findUnLibraryStudent(group.getExamId(), group.getSubjectCode(),
                        group.getNumber(), pageSize);
            }
            if (count != 0) {
                log.info("finish create " + count + " library for examId=" + group.getExamId() + ", subjectCode="
                        + group.getSubjectCode() + ", groupNumber=" + group.getNumber());
            }
        } catch (Exception e) {
            log.error(
                    "build formal library error for examId=" + group.getExamId() + ", subjectCode="
                            + group.getSubjectCode() + ", groupNumber=" + group.getNumber(), e);
        } finally {
            lockService.unwatch(LockType.GROUP, group.getExamId(), group.getSubjectCode(), group.getNumber());
        }
    }

    private void buildTrialLibrary(MarkGroup group) {
        List<ExamStudent> studentList = studentService.findUnTrialStudent(group.getExamId(), group.getSubjectCode(),
                group.getNumber());
        if (studentList.isEmpty()) {
            return;
        }
        // 尝试构造试评任务
        try {
            lockService.watch(LockType.GROUP, group.getExamId(), group.getSubjectCode(), group.getNumber());
            // 上锁后重新验证分组状态
            if (!groupService.validateStatus(group.getExamId(), group.getSubjectCode(), group.getNumber(),
                    MarkStatus.TRIAL)) {
                return;
            }
            List<TrialLibrary> trialList = new ArrayList<TrialLibrary>();
            for (ExamStudent student : studentList) {
                if (studentService.validateStatus(student.getId())) {
                    TrialLibrary library = new TrialLibrary();
                    library.setExamId(student.getExamId());
                    library.setSubjectCode(student.getSubjectCode());
                    library.setGroupNumber(group.getNumber());
                    library.setStudentId(student.getId());
                    library.setExamNumber(student.getExamNumber());
                    library.setSecretNumber(student.getSecretNumber());
                    trialList.add(library);
                }
            }
            if (group.getStatus() == MarkStatus.TRIAL && !trialList.isEmpty()) {
                trialService.save(trialList);
                markService.updateLibraryCount(group);
                markService.updateMarkedCount(group);
            }
        } catch (Exception e) {
            log.error(
                    "build trial library error for examId=" + group.getExamId() + ", subjectCode="
                            + group.getSubjectCode() + ", groupNumber=" + group.getNumber(), e);
        } finally {
            lockService.unwatch(LockType.GROUP, group.getExamId(), group.getSubjectCode(), group.getNumber());
        }
    }

    @Scheduled(fixedDelay = 30 * 60 * 1000, initialDelay = 2 * 60 * 1000)
    public void updateReport() {
        String systemConfig = systemConfigService.findByType(ConfigType.AUTO_REPORT);
        if (systemConfig == null || !systemConfig.equalsIgnoreCase("on")) {
            return;
        }
        log.info("start auto-update report");
        try {
            Map<Integer, Set<String>> map = new HashMap<Integer, Set<String>>();
            List<Integer> examIds = groupService.findExamIdByStatus(ExamStatus.START, MarkStatus.FORMAL);
            for (Integer examId : examIds) {
                List<ExamSubject> list = subjectService.list(examId);
                Set<String> set = new HashSet<>();
                for (ExamSubject subject : list) {
                    if (!lockService.isLocked(LockType.SCORE_CALCULATE, subject.getExamId(), subject.getCode())) {
                        set.add(subject.getCode());
                    }
                }
                if (!set.isEmpty()) {
                    map.put(examId, set);
                }
            }
            for (Integer examId : map.keySet()) {
                reportService.reportExam(examId, map.get(examId));
            }
        } catch (Exception e) {
            log.error("auto-update report error", e);
        } finally {
            log.info("finish auto-update report");
        }
    }

}
