package com.qmth.exam.reserve.service.impl;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.redisson.api.RLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.qmth.boot.core.collection.PageResult;
import com.qmth.boot.core.concurrent.service.ConcurrentService;
import com.qmth.boot.core.exception.StatusException;
import com.qmth.boot.tools.excel.ExcelReader;
import com.qmth.boot.tools.excel.enums.ExcelType;
import com.qmth.boot.tools.excel.model.DataMap;
import com.qmth.boot.tools.io.ZipWriter;
import com.qmth.exam.reserve.bean.Constants;
import com.qmth.exam.reserve.bean.apply.ApplyRecordCacheBean;
import com.qmth.exam.reserve.bean.login.LoginUser;
import com.qmth.exam.reserve.bean.stdapply.AgentAndTimeVO;
import com.qmth.exam.reserve.bean.stdapply.CategoryVO;
import com.qmth.exam.reserve.bean.stdapply.MaterialTitleInfo;
import com.qmth.exam.reserve.bean.stdapply.SignInVO;
import com.qmth.exam.reserve.bean.stdapply.StudentApplyReq;
import com.qmth.exam.reserve.bean.stdapply.StudentApplyVO;
import com.qmth.exam.reserve.bean.stdapply.StudentImportVO;
import com.qmth.exam.reserve.cache.CacheConstants;
import com.qmth.exam.reserve.cache.impl.ApplyTaskCacheService;
import com.qmth.exam.reserve.dao.StudentApplyDao;
import com.qmth.exam.reserve.entity.ApplyTaskEntity;
import com.qmth.exam.reserve.entity.CategoryEntity;
import com.qmth.exam.reserve.entity.ExamRoomEntity;
import com.qmth.exam.reserve.entity.ExamSiteEntity;
import com.qmth.exam.reserve.entity.StudentApplyEntity;
import com.qmth.exam.reserve.entity.StudentEntity;
import com.qmth.exam.reserve.entity.TimePeriodEntity;
import com.qmth.exam.reserve.enums.CategoryLevel;
import com.qmth.exam.reserve.enums.EventType;
import com.qmth.exam.reserve.service.ApplyTaskService;
import com.qmth.exam.reserve.service.CategoryService;
import com.qmth.exam.reserve.service.ExamRoomService;
import com.qmth.exam.reserve.service.ExamSiteService;
import com.qmth.exam.reserve.service.MaterialGenerateService;
import com.qmth.exam.reserve.service.OperateLogService;
import com.qmth.exam.reserve.service.StudentApplyService;
import com.qmth.exam.reserve.service.StudentService;
import com.qmth.exam.reserve.service.TimePeriodService;
import com.qmth.exam.reserve.util.DateUtil;
import com.qmth.exam.reserve.util.JsonHelper;
import com.qmth.exam.reserve.util.PageUtil;

@Service
public class StudentApplyServiceImpl extends ServiceImpl<StudentApplyDao, StudentApplyEntity>
        implements StudentApplyService {

    private final static Logger log = LoggerFactory.getLogger(StudentApplyServiceImpl.class);

    private static final String[] EXCEL_HEADER = new String[] { "学号", "姓名", "证件号", "所属教学点", "预约考点1", "预约时段1", "预约考点2",
            "预约时段2", "预约考点3", "预约时段3", "预约考点4", "预约时段4" };

    @Autowired
    private TimePeriodService timePeriodService;

    @Autowired
    private ApplyTaskService applyTaskService;

    @Autowired
    private CategoryService categoryService;

    @Autowired
    private StudentService studentService;

    @Autowired
    private ExamSiteService examSiteService;

    @Autowired
    private OperateLogService operateLogService;

    @Autowired
    private ExamRoomService examRoomService;

    @Autowired
    private MaterialGenerateService materialService;

    @Autowired
    private ConcurrentService concurrentService;

    @Autowired
    private ApplyTaskCacheService cacheService;

    @Override
    public PageResult<StudentApplyVO> page(StudentApplyReq req) {
        if (req.getTaskId() == null) {
            ApplyTaskEntity task = getApplyTask();
            req.setTaskId(task.getId());
        }
        if (req.getTeachingId() != null) {
            List<CategoryVO> listExamSite = examSiteService.listExamSite(req.getTeachingId());
            List<Long> examSiteIds = listExamSite.stream().map(CategoryVO::getId).collect(Collectors.toList());
            if (!examSiteIds.isEmpty()) {
                req.setExamSiteIds(examSiteIds);
            }
        }
        IPage<StudentApplyVO> iPage = this.baseMapper
                .page(new Page<StudentApplyVO>(req.getPageNumber(), req.getPageSize()), req);
        return PageUtil.of(iPage);
    }

    @Transactional
    @Override
    public void cancel(LoginUser user, Long id) {
        // 时间判断
        StudentApplyEntity studentApply = this.baseMapper.selectById(id);
        if (studentApply == null || studentApply.getTimePeriodId() == null) {
            throw new StatusException("考生没有预约，无法取消！");
        }
        TimePeriodEntity timePeriod = timePeriodService.getById(studentApply.getTimePeriodId());
        if (timePeriod == null) {
            throw new StatusException("考试时段不存在，请检查考试时段数据！");
        }
        ApplyTaskEntity task = getApplyTask();
        Date applyDate = DateUtils.truncate(new Date(timePeriod.getStartTime()), Calendar.DATE);
        Date canCancelDay = DateUtil.addValues(applyDate, Calendar.DAY_OF_MONTH, -task.getAllowApplyCancelDays());
        if (new Date().after(canCancelDay)) {
            throw new StatusException("可取消时间已过，无法取消！");
        }

        String studentApplyLockKey = String.format(CacheConstants.LOCK_STUDENT_APPLY, studentApply.getStudentId());
        RLock studentApplyLock = (RLock) concurrentService.getLock(studentApplyLockKey);
        try {
            if (!studentApplyLock.tryLock()) {
                log.warn("获取锁失败，同一个考生不允许同时操作预约！lockKey:{}", studentApplyLockKey);
                throw new StatusException(Constants.SYSTEM_BUSY);
            } else {
                log.warn("获取锁成功！lockKey:{}", studentApplyLockKey);

                studentApply.setCancel(Boolean.TRUE);
                this.baseMapper.updateById(studentApply);

                ApplyRecordCacheBean bean = new ApplyRecordCacheBean();
                bean.setStudentId(studentApply.getStudentId());
                bean.setExamSiteId(studentApply.getExamSiteId());
                bean.setTimePeriodId(studentApply.getTimePeriodId());
                bean.setCancel(Boolean.TRUE);
                bean.setOperateId(user.getId());
                bean.setOperateTime(System.currentTimeMillis());

                // 先推送至预约队列
                cacheService.pushStudentApplyRecordQueue(bean);
                cacheService.saveStudentApplyRecord(bean);
                cacheService.decreaseApplyFinishCount(studentApply.getExamSiteId(), studentApply.getTimePeriodId());
            }
        } catch (Exception e) {
            throw new StatusException("取消预约失败，请稍后再试！", e);
        } finally {
            try {
                // 解锁前检查当前线程是否持有该锁
                if (studentApplyLock.isLocked() && studentApplyLock.isHeldByCurrentThread()) {
                    studentApplyLock.unlock();
                    log.info("解锁成功！lockKey:{}", studentApplyLockKey);
                }
            } catch (Exception e) {
                log.warn(e.getMessage());
            }
        }

        operateLogService.insertOperateLog(user.getId(), EventType.CANCEL_APPLY, JsonHelper.toJson(studentApply));
    }

    @Transactional
    @Override
    public List<Map<String, Object>> importPreExam(Long userId, Long teachingId, Integer level,
            InputStream inputStream) {
        List<DataMap> lineList;
        ExcelReader reader = ExcelReader.create(ExcelType.XLSX, inputStream, 0);
        try {
            lineList = reader.getDataMapList();
        } catch (Exception e) {
            throw new StatusException("Excel 解析失败");
        }
        if (!Arrays.equals(EXCEL_HEADER, reader.getColumnNames())) {
            throw new StatusException("Excel表头错误");
        }
        if (CollectionUtils.isEmpty(lineList)) {
            throw new StatusException("Excel无内容");
        }
        ApplyTaskEntity task = getApplyTask();
        Date openStartTime = new Date(task.getOpenApplyStartTime());
        Date now = new Date();
        List<Map<String, Object>> failRecords = new ArrayList<Map<String, Object>>();
        Map<String, Long> teachingCache = getTeachingCache(level);
        Map<String, Long> agentCache = getAgentCache();
        Map<String, TimePeriodEntity> timeCache = getTimePeriodCache();
        Map<Long, Long> examSiteCategoryCache = getExamSiteCategoryCache();
        List<StudentImportVO> applyList = new ArrayList<>();
        AgentAndTimeVO agentTime;
        for (int i = 0; i < lineList.size(); i++) {
            List<AgentAndTimeVO> agentTimeList = new ArrayList<>();
            DataMap line = lineList.get(i);
            StudentImportVO apply = new StudentImportVO();
            StringBuilder msg = new StringBuilder();
            String studentCode = trimAndNullIfBlank(line.get(EXCEL_HEADER[0]));
            if (StringUtils.isBlank(studentCode)) {
                msg.append(" 学号不能为空");
            }

            String name = trimAndNullIfBlank(line.get(EXCEL_HEADER[1]));
            if (StringUtils.isBlank(name)) {
                msg.append(" 姓名不能为空");
            }

            String identityNumber = trimAndNullIfBlank(line.get(EXCEL_HEADER[2]));
            if (StringUtils.isBlank(identityNumber)) {
                msg.append(" 证件号不能为空");
            }
            StudentEntity student = null;
            try {
                student = checkStd(studentCode, name, identityNumber, task);
                apply.setStudentId(student.getId());
                apply.setApplyNumber(student.getApplyNumber());
            } catch (StatusException e) {
                msg.append(" " + e.getMessage());
                failRecords.add(newError(i + 1, msg.toString()));
                continue;
            }

            String teachingName = trimAndNullIfBlank(line.get(EXCEL_HEADER[3]));
            if (StringUtils.isBlank(teachingName)) {
                msg.append(" 所属教学点不能为空");
            }
            Long categoryId = teachingCache.get(teachingName);
            if (categoryId == null) {
                msg.append(" 所属教学点不存在");
            }
            if (categoryId != null && !student.getCategoryId().equals(categoryId)) {
                msg.append(" 导入的考生所属教学点和系统中的考生教学点不匹配");
            }
            String agentName1 = trimAndNullIfBlank(line.get(EXCEL_HEADER[4]));
            if (StringUtils.isBlank(agentName1)) {
                msg.append(" 预约考点1不能为空");
            }
            agentTime = new AgentAndTimeVO();
            Long agentId = agentCache.get(agentName1);
            Long applyCategoryId = null;
            if (agentId == null) {
                msg.append(" 预约考点1不存在");
            } else {
                applyCategoryId = examSiteCategoryCache.get(agentId);
                if (now.before(openStartTime) && categoryId != null && !applyCategoryId.equals(categoryId)) {
                    msg.append(" 未到自由预约时间，不允许预约其他教学点的考点");
                }
            }

            String timePeriod1 = trimAndNullIfBlank(line.get(EXCEL_HEADER[5]));
            if (StringUtils.isBlank(timePeriod1)) {
                msg.append(" 预约时段1不能为空");
            }
            TimePeriodEntity timePeriod = null;
            try {
                timePeriod = checkTimePeriod(timePeriod1, timeCache);
                agentTime.setAgentId(agentId);
                agentTime.setTimePeriodId(timePeriod.getId());
                agentTime.setStartTime(timePeriod.getStartTime());
                agentTimeList.add(agentTime);
            } catch (StatusException e) {
                msg.append(" " + e.getMessage());
            }

            String agentName2 = trimAndNullIfBlank(line.get(EXCEL_HEADER[6]));
            String timePeriod2 = trimAndNullIfBlank(line.get(EXCEL_HEADER[7]));
            if (StringUtils.isBlank(agentName2) && StringUtils.isBlank(timePeriod2)) {
                apply.setAgentTimeList(agentTimeList);
                applyList.add(apply);
                if (msg.length() > 0)
                    failRecords.add(newError(i + 1, msg.toString()));
                continue;
            } else {
                agentId = agentCache.get(agentName2);
                if (agentId == null) {
                    msg.append(" 预约考点2不存在");
                } else {
                    applyCategoryId = examSiteCategoryCache.get(agentId);
                    if (now.before(openStartTime) && categoryId != null && !applyCategoryId.equals(categoryId)) {
                        msg.append(" 未到自由预约时间，不允许预约其他教学点的考点");
                    }
                }
                try {
                    timePeriod = checkTimePeriod(timePeriod2, timeCache);
                    agentTime = new AgentAndTimeVO();
                    agentTime.setAgentId(agentId);
                    agentTime.setTimePeriodId(timePeriod.getId());
                    agentTime.setStartTime(timePeriod.getStartTime());
                    agentTimeList.add(agentTime);
                } catch (StatusException e) {
                    msg.append(" " + e.getMessage());
                }
            }

            String agentName3 = trimAndNullIfBlank(line.get(EXCEL_HEADER[8]));
            String timePeriod3 = trimAndNullIfBlank(line.get(EXCEL_HEADER[9]));
            if (StringUtils.isBlank(agentName3) && StringUtils.isBlank(timePeriod3)) {
                apply.setAgentTimeList(agentTimeList);
                applyList.add(apply);
                if (msg.length() > 0)
                    failRecords.add(newError(i + 1, msg.toString()));
                continue;
            } else {
                agentId = agentCache.get(agentName3);
                if (agentId == null) {
                    msg.append(" 预约考点3不存在");
                } else {
                    applyCategoryId = examSiteCategoryCache.get(agentId);
                    if (now.before(openStartTime) && categoryId != null && !applyCategoryId.equals(categoryId)) {
                        msg.append(" 未到自由预约时间，不允许预约其他教学点的考点");
                    }
                }
                try {
                    timePeriod = checkTimePeriod(timePeriod3, timeCache);
                    agentTime = new AgentAndTimeVO();
                    agentTime.setAgentId(agentId);
                    agentTime.setTimePeriodId(timePeriod.getId());
                    agentTime.setStartTime(timePeriod.getStartTime());
                    agentTimeList.add(agentTime);
                } catch (StatusException e) {
                    msg.append(" " + e.getMessage());
                }
            }

            String agentName4 = trimAndNullIfBlank(line.get(EXCEL_HEADER[10]));
            String timePeriod4 = trimAndNullIfBlank(line.get(EXCEL_HEADER[11]));
            if (StringUtils.isBlank(agentName4) && StringUtils.isBlank(timePeriod4)) {
                apply.setAgentTimeList(agentTimeList);
                applyList.add(apply);
                if (msg.length() > 0)
                    failRecords.add(newError(i + 1, msg.toString()));
                continue;
            } else {
                agentId = agentCache.get(agentName4);
                if (agentId == null) {
                    msg.append(" 预约考点4不存在");
                } else {
                    applyCategoryId = examSiteCategoryCache.get(agentId);
                    if (now.before(openStartTime) && categoryId != null && !applyCategoryId.equals(categoryId)) {
                        msg.append(" 未到自由预约时间，不允许预约其他教学点的考点");
                    }
                }
                try {
                    timePeriod = checkTimePeriod(timePeriod4, timeCache);
                    agentTime = new AgentAndTimeVO();
                    agentTime.setAgentId(agentId);
                    agentTime.setTimePeriodId(timePeriod.getId());
                    agentTime.setStartTime(timePeriod.getStartTime());
                    agentTimeList.add(agentTime);
                    apply.setAgentTimeList(agentTimeList);
                    applyList.add(apply);
                } catch (StatusException e) {
                    msg.append(" " + e.getMessage());
                }
            }

        }

        checkStudentApplyTime(applyList, getApplyTask().getAllowApplyCancelDays(), failRecords);
        if (CollectionUtils.isNotEmpty(failRecords)) {
            // TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            return failRecords;
        }
        checkAvailableTimePeriod(applyList);
        for (int i = 0; i < applyList.size(); i++) {
            StudentImportVO vo = applyList.get(i);
            try {
                saveStdApply(i, vo, userId, failRecords);
            } catch (StatusException e) {
                failRecords.add(newError(i + 1, " 系统异常"));
                log.error("导入异常", e);
            }
        }

        if (CollectionUtils.isNotEmpty(failRecords)) {
            // TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            return failRecords;
        }

        return failRecords;

    }

    private Map<Long, Long> getExamSiteCategoryCache() {
        Map<Long, Long> cache = new HashMap<>();
        LambdaQueryWrapper<ExamSiteEntity> lm = new LambdaQueryWrapper<>();
        lm.eq(ExamSiteEntity::getEnable, Boolean.TRUE);
        List<ExamSiteEntity> examSiteList = examSiteService.list(lm);
        examSiteList.forEach(site -> {
            cache.put(site.getId(), site.getCategoryId());
        });
        return cache;
    }

    private void checkAvailableTimePeriod(List<StudentImportVO> applyList) {
        for (StudentImportVO vo : applyList) {
            List<AgentAndTimeVO> agentTimeList = vo.getAgentTimeList();
            List<StudentApplyEntity> haveApplyList = listStudentApply(vo.getStudentId(), Boolean.FALSE);
            int studentApplyFinishCount = cacheService.getStudentApplyFinishCount(vo.getStudentId());
            if (studentApplyFinishCount == 0) {
                studentApplyFinishCount = haveApplyList.size();
            }
            agentTimeList.sort(Comparator.comparing(AgentAndTimeVO::getStartTime));
            List<AgentAndTimeVO> tobeInsertTimeList = new ArrayList<>();

            // 考生已经完成预约-不做处理
            if (studentApplyFinishCount >= vo.getApplyNumber()) {
                vo.setAgentTimeList(tobeInsertTimeList);
                continue;
            }

            // 只预约了部分
            if (studentApplyFinishCount > 0 && studentApplyFinishCount < vo.getApplyNumber()) {
                List<AgentAndTimeVO> availableList = listAvailableTime(haveApplyList, agentTimeList);
                // 需要填充的次数
                tobeInsertTimeList.addAll(availableList);
            }

            // 未预约
            if (studentApplyFinishCount == 0) {
                tobeInsertTimeList = agentTimeList;
            }
            vo.setAgentTimeList(tobeInsertTimeList);
        }
    }

    private List<AgentAndTimeVO> listAvailableTime(List<StudentApplyEntity> haveApplyList,
            List<AgentAndTimeVO> agentTimeList) {
        List<AgentAndTimeVO> availableList = new ArrayList<>();
        for (AgentAndTimeVO time : agentTimeList) {
            boolean flag = false;
            for (StudentApplyEntity apply : haveApplyList) {
                if (time.getTimePeriodId().equals(apply.getTimePeriodId())) {
                    flag = true;
                    break;
                }
            }
            if (!flag) {
                availableList.add(time);
            }
        }
        return availableList;
    }

    private void checkStudentApplyTime(List<StudentImportVO> applyList, Integer days,
            List<Map<String, Object>> failRecords) {
        for (int i = 0; i < applyList.size(); i++) {
            StudentImportVO vo = applyList.get(i);
            List<AgentAndTimeVO> agentTimeList = vo.getAgentTimeList();
            if (agentTimeList.size() > vo.getApplyNumber()) {
                failRecords.add(newError(i + 1, " 导入考生预约数量大于考生可预约的数量"));
            }
            boolean isDuplicate = agentTimeList.stream()
                    .collect(Collectors.groupingBy(AgentAndTimeVO::getTimePeriodId, Collectors.counting())).entrySet()
                    .stream().anyMatch(entry -> entry.getValue() > 1);
            if (isDuplicate) {
                failRecords.add(newError(i + 1, " 预约的时间不能相同"));
            }
            LocalDate day = LocalDate.now().plusDays(days);
            long epochMilli = day.plusDays(1).atStartOfDay(ZoneId.systemDefault()).minusSeconds(1).toInstant()
                    .toEpochMilli();
            for (AgentAndTimeVO time : agentTimeList) {
                if (time.getStartTime() <= epochMilli) {
                    failRecords.add(newError(i + 1, " 预约的考试时间，必须在可取消的时间之后"));
                }
            }
        }
    }

    private ApplyTaskEntity getApplyTask() {
        LambdaQueryWrapper<ApplyTaskEntity> wrapper = new LambdaQueryWrapper<ApplyTaskEntity>()
                .eq(ApplyTaskEntity::getEnable, Boolean.TRUE);
        ApplyTaskEntity task = applyTaskService.getOne(wrapper);
        if (task == null)
            throw new StatusException("未开启预约任务");
        return task;
    }

    private List<StudentApplyEntity> saveStdApply(int row, StudentImportVO vo, Long userId,
            List<Map<String, Object>> failRecords) {
        List<StudentApplyEntity> ApplyList = new ArrayList<>();
        List<AgentAndTimeVO> agentTimeList = vo.getAgentTimeList();
        String studentApplyLockKey = String.format(CacheConstants.LOCK_STUDENT_APPLY, vo.getStudentId());
        RLock studentApplyLock = (RLock) concurrentService.getLock(studentApplyLockKey);
        try {
            if (!studentApplyLock.tryLock()) {
                log.warn("获取锁失败，考生在同时操作预约！lockKey:{}", studentApplyLockKey);
            } else {
                log.warn("获取锁成功！lockKey:{}", studentApplyLockKey);

                for (AgentAndTimeVO agentTime : agentTimeList) {
                    // 已经预约的容量和考点的容量从redis中获取
                    int haveApplyCount = cacheService.getApplyFinishCount(agentTime.getAgentId(),
                            agentTime.getTimePeriodId());
                    int examSiteCount = cacheService.getApplyTotalCount(agentTime.getAgentId());
                    if (haveApplyCount >= examSiteCount) {
                        ExamSiteEntity examSite = examSiteService.getById(agentTime.getAgentId());
                        failRecords.add(newError(row + 1, " 考点【" + examSite.getName() + "】的容量已满"));
                    } else {
                        StudentApplyEntity entity = new StudentApplyEntity();
                        entity.setStudentId(vo.getStudentId());
                        entity.setExamSiteId(agentTime.getAgentId());
                        entity.setTimePeriodId(agentTime.getTimePeriodId());
                        entity.setCancel(Boolean.FALSE);
                        entity.setOperateId(userId);

                        StudentApplyEntity existStudentApply = findStudentApply(entity);
                        if (existStudentApply != null) {
                            existStudentApply.setCancel(Boolean.FALSE);
                            baseMapper.updateById(existStudentApply);
                        } else {
                            baseMapper.insert(entity);
                        }

                        ApplyRecordCacheBean bean = new ApplyRecordCacheBean();
                        bean.setStudentId(vo.getStudentId());
                        bean.setExamSiteId(agentTime.getAgentId());
                        bean.setTimePeriodId(agentTime.getTimePeriodId());
                        bean.setCancel(Boolean.FALSE);
                        bean.setOperateId(userId);
                        bean.setOperateTime(System.currentTimeMillis());

                        // 先推送至预约队列
                        cacheService.pushStudentApplyRecordQueue(bean);
                        cacheService.saveStudentApplyRecord(bean);
                        cacheService.increaseApplyFinishCount(agentTime.getAgentId(), agentTime.getTimePeriodId());
                    }
                }
            }
        } catch (Exception e) {
            log.error("导入预考失败,错误原因：", e.getMessage());
            throw new StatusException("导入预考失败，请稍后再试！", e);
        } finally {
            try {
                if (studentApplyLock.isLocked() && studentApplyLock.isHeldByCurrentThread()) {
                    studentApplyLock.unlock();
                    log.info("解锁成功！lockKey:{}", studentApplyLockKey);
                }
            } catch (Exception e) {
                log.warn(e.getMessage());
            }
        }
        return ApplyList;
    }

    private TimePeriodEntity checkTimePeriod(String timePeriod, Map<String, TimePeriodEntity> timeCache) {
        if (timePeriod == null || timePeriod.split(" ").length != 2) {
            throw new StatusException(" 预约时段格式不正确");
        }
        String[] arr = timePeriod.split("-");
        String startTime = arr[0] + ":00";
        String endTime = startTime.substring(0, startTime.indexOf("日") + 1) + " " + arr[1] + ":00";
        Long startTimeLong = DateUtil.getLongTimeByZHDate(startTime);
        Long endTimeLong = DateUtil.getLongTimeByZHDate(endTime);
        if (timeCache.get(startTimeLong + "-" + endTimeLong) == null) {
            throw new StatusException(" 预约时段不存在");
        }
        return timeCache.get(startTimeLong + "-" + endTimeLong);
    }

    private Map<String, TimePeriodEntity> getTimePeriodCache() {
        Map<String, TimePeriodEntity> map = new HashMap<>();
        LambdaQueryWrapper<TimePeriodEntity> lm = new LambdaQueryWrapper<>();
        lm.eq(TimePeriodEntity::getApplyTaskId, getApplyTask().getId());
        List<TimePeriodEntity> timeList = timePeriodService.list(lm);
        for (TimePeriodEntity time : timeList) {
            map.put(time.getStartTime() + "-" + time.getEndTime(), time);
        }
        return map;
    }

    private Map<String, Long> getTeachingCache(Integer level) {
        LambdaQueryWrapper<CategoryEntity> lm = new LambdaQueryWrapper<>();
        lm.eq(CategoryEntity::getEnable, Boolean.TRUE);
        lm.eq(CategoryEntity::getLevel, level == null ? CategoryLevel.TEACHING.getValue() : level);
        List<CategoryEntity> categoryList = categoryService.list(lm);
        return categoryList.stream().collect(Collectors.toMap(CategoryEntity::getName, CategoryEntity::getId));
    }

    private Map<String, Long> getAgentCache() {
        LambdaQueryWrapper<ExamSiteEntity> lm = new LambdaQueryWrapper<>();
        lm.eq(ExamSiteEntity::getEnable, Boolean.TRUE);
        List<ExamSiteEntity> categoryList = examSiteService.list(lm);
        return categoryList.stream().collect(Collectors.toMap(ExamSiteEntity::getName, ExamSiteEntity::getId));
    }

    private StudentEntity checkStd(String studentCode, String name, String identityNumber, ApplyTaskEntity task) {
        LambdaQueryWrapper<StudentEntity> lm = new LambdaQueryWrapper<>();
        lm.eq(StudentEntity::getStudentCode, studentCode);
        lm.eq(StudentEntity::getName, name);
        lm.eq(StudentEntity::getIdentityNumber, identityNumber);
        lm.eq(StudentEntity::getApplyTaskId, task.getId());
        StudentEntity student = studentService.getOne(lm);
        if (student == null) {
            throw new StatusException(" 考生信息填写错误");
        }
        return student;
    }

    private Map<String, Object> newError(int lineNum, String msg) {
        Map<String, Object> map = new HashMap<>();
        map.put("lineNum", lineNum);
        map.put("msg", msg);
        return map;
    }

    private String trimAndNullIfBlank(String s) {
        if (StringUtils.isBlank(s)) {
            return null;
        }
        return s.trim();
    }

    @Override
    public void autoAssign(Long taskId, Long userId) {
        checkAfterOpenTime();
        RLock lock = (RLock) concurrentService.getLock(CacheConstants.LOCK_AUTO_APPLY);
        try {
            if (!lock.tryLock()) {
                log.warn("获取锁失败，不允许同时执行自动分配！lockKey:{}", CacheConstants.LOCK_AUTO_APPLY);
                throw new StatusException("其他老师正在执行自动分配，请不要重复执行！");
            }

            log.warn("获取锁成功！lockKey:{}", CacheConstants.LOCK_AUTO_APPLY);

            // 1、未完成预约的考生
            List<StudentEntity> studentList = studentService.listNoFinishStudent(taskId, Boolean.FALSE);
            Map<Long, List<StudentEntity>> noFinishApplyMap = studentList.stream()
                    .collect(Collectors.groupingBy(StudentEntity::getCategoryId));
            // 2、考位是否充足
            List<TimePeriodEntity> timeList = listTimePeriod(taskId);
            timeList = listNoCancelExamTimePeriod(timeList, taskId);
            checkTeachingCapacity(noFinishApplyMap, timeList, taskId);
            // 3、按照教学点安排考位。规则：不能和已预约的时间上有冲突
            for (Long key : noFinishApplyMap.keySet()) {
                List<ExamSiteEntity> siteList = listExamSite(key, null);
                List<StudentEntity> teachingStudentList = noFinishApplyMap.get(key);
                for (ExamSiteEntity site : siteList) {
                    for (TimePeriodEntity time : timeList) {
                        // 该时段已预约的考生
                        Integer haveApplyNum = cacheService.getApplyFinishCount(site.getId(), time.getId());
                        if (haveApplyNum == 0) {
                            haveApplyNum = getHaveApplyNum(site.getId(), time.getId());
                        }
                        // 剩余的考位
                        Integer remainNum = site.getCapacity() - haveApplyNum;

                        assignStudentApply(userId, site.getId(), time.getId(), teachingStudentList, remainNum);
                    }
                }
                // 4、判断是否还有剩余考生未完成预约，提醒考位不够
                if (!teachingStudentList.isEmpty())
                    throw new StatusException("【" + categoryService.getById(key).getName() + "】教学点考位不足");
            }
        } catch (Exception e) {
            log.error(e.getMessage());
            throw new StatusException(e.getMessage());
        } finally {
            try {
                // 解锁前检查当前线程是否持有该锁
                if (lock.isLocked() && lock.isHeldByCurrentThread()) {
                    lock.unlock();
                    log.info("解锁成功！lockKey:{}", CacheConstants.LOCK_AUTO_APPLY);
                }
            } catch (Exception e) {
                log.warn(e.getMessage());
            }
        }

    }

    private List<TimePeriodEntity> listNoCancelExamTimePeriod(List<TimePeriodEntity> timeList, Long taskId) {
        ApplyTaskEntity task = applyTaskService.getById(taskId);
        Long longToday = DateUtil.getLongTimeByDate(DateUtil.formatShortSplitDateString(new Date()) + " 00:00:00");
        Date today = new Date(longToday);
        Date otherDay = DateUtil.addValues(today, Calendar.DAY_OF_MONTH, task.getAllowApplyCancelDays());
        Long longOtherDay = DateUtil.getLongTimeByDate(DateUtil.formatShortSplitDateString(otherDay) + " 23:59:59");
        return timeList.stream().filter(time -> time.getStartTime() > longOtherDay).collect(Collectors.toList());
    }

    private List<StudentApplyEntity> assignStudentApply(Long userId, Long siteId, Long timeId,
            List<StudentEntity> teachingStudentList, Integer remainNum) {
        List<StudentApplyEntity> insertApplyList = new ArrayList<>();
        int num = 0;
        String studentApplyLockKey = null;
        RLock studentApplyLock = null;
        for (Iterator<StudentEntity> iterator = teachingStudentList.iterator(); iterator.hasNext();) {
            StudentEntity student = iterator.next();
            if (num >= remainNum)
                break;
            studentApplyLockKey = String.format(CacheConstants.LOCK_STUDENT_APPLY, student.getId());
            studentApplyLock = (RLock) concurrentService.getLock(studentApplyLockKey);
            try {
                if (!studentApplyLock.tryLock()) {
                    log.warn("获取锁失败，考生在同时操作预约！lockKey:{}", studentApplyLockKey);
                    iterator.remove();
                } else {
                    log.warn("获取锁成功！lockKey:{}", studentApplyLockKey);

                    List<StudentApplyEntity> studentApplyList = listStudentApply(student.getId(), Boolean.FALSE);
                    int toApplyNum = student.getApplyNumber() - studentApplyList.size();
                    if (toApplyNum > 0 && !haveApplySameTimePeriod(siteId, timeId, student.getId())) {
                        StudentApplyEntity studentApply = new StudentApplyEntity();
                        studentApply.setStudentId(student.getId());
                        studentApply.setExamSiteId(siteId);
                        studentApply.setCancel(Boolean.FALSE);
                        studentApply.setTimePeriodId(timeId);
                        studentApply.setOperateId(userId);

                        StudentApplyEntity existStudentApply = findStudentApply(studentApply);
                        if (existStudentApply != null) {
                            existStudentApply.setCancel(Boolean.FALSE);
                            baseMapper.updateById(existStudentApply);
                        } else {
                            baseMapper.insert(studentApply);
                        }
                        insertApplyList.add(studentApply);

                        num++;
                        if (student.getApplyNumber() - (studentApplyList.size() + 1) == 0) {
                            iterator.remove();
                        }

                        ApplyRecordCacheBean bean = new ApplyRecordCacheBean();
                        bean.setStudentId(studentApply.getStudentId());
                        bean.setExamSiteId(studentApply.getExamSiteId());
                        bean.setTimePeriodId(studentApply.getTimePeriodId());
                        bean.setCancel(Boolean.FALSE);
                        bean.setOperateId(userId);
                        bean.setOperateTime(System.currentTimeMillis());

                        // 先推送至预约队列
                        cacheService.pushStudentApplyRecordQueue(bean);
                        cacheService.saveStudentApplyRecord(bean);
                        cacheService.increaseApplyFinishCount(studentApply.getExamSiteId(),
                                studentApply.getTimePeriodId());
                    }
                }
            } catch (Exception e) {
                log.error("自动安排预约失败,错误信息：{}", e.getMessage());
                throw new StatusException("自动安排预约失败，请稍后再试！");
            } finally {
                try {
                    // 解锁前检查当前线程是否持有该锁
                    if (studentApplyLock.isLocked() && studentApplyLock.isHeldByCurrentThread()) {
                        studentApplyLock.unlock();
                        log.info("解锁成功！lockKey:{}", studentApplyLockKey);
                    }
                } catch (Exception e) {
                    log.warn(e.getMessage());
                }
            }

        }
        return insertApplyList;
    }

    private StudentApplyEntity findStudentApply(StudentApplyEntity studentApply) {
        LambdaQueryWrapper<StudentApplyEntity> lm = new LambdaQueryWrapper<>();
        lm.eq(StudentApplyEntity::getExamSiteId, studentApply.getExamSiteId());
        lm.eq(StudentApplyEntity::getTimePeriodId, studentApply.getTimePeriodId());
        lm.eq(StudentApplyEntity::getStudentId, studentApply.getStudentId());
        return baseMapper.selectOne(lm);
    }

    private boolean haveApplySameTimePeriod(Long siteId, Long timeId, Long studentId) {
        LambdaQueryWrapper<StudentApplyEntity> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(StudentApplyEntity::getExamSiteId, siteId);
        wrapper.eq(StudentApplyEntity::getTimePeriodId, timeId);
        wrapper.eq(StudentApplyEntity::getStudentId, studentId);
        wrapper.eq(StudentApplyEntity::getCancel, Boolean.FALSE);
        StudentApplyEntity studentApply = baseMapper.selectOne(wrapper);
        return studentApply != null;
    }

    private Integer getHaveApplyNum(Long siteId, Long timeId) {
        LambdaQueryWrapper<StudentApplyEntity> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(StudentApplyEntity::getExamSiteId, siteId);
        wrapper.eq(StudentApplyEntity::getTimePeriodId, timeId);
        wrapper.eq(StudentApplyEntity::getCancel, Boolean.FALSE);
        return baseMapper.selectCount(wrapper);
    }

    private void checkAfterOpenTime() {
        ApplyTaskEntity task = getApplyTask();
        Date selfEndTime = new Date(task.getSelfApplyEndTime());
        Date openStartTime = new Date(task.getOpenApplyStartTime());
        if (!DateUtil.isBetwwen(selfEndTime, openStartTime)) {
            throw new StatusException("自动分配，时间必须要在第一阶段结束之后，第三阶段开始之前");
        }
    }

    private void checkTeachingCapacity(Map<Long, List<StudentEntity>> map, List<TimePeriodEntity> timeList,
            Long taskId) {
        for (Long key : map.keySet()) {
            List<ExamSiteEntity> siteList = listExamSite(key, null);
            // 总考位数量
            Integer total = siteList.stream().collect(Collectors.summingInt(ExamSiteEntity::getCapacity))
                    * timeList.size();
            // 已经预约的数量
            Integer haveApplyNum = 0;
            for (ExamSiteEntity site : siteList) {
                haveApplyNum += cacheService.getApplyTotalCount(site.getId());
            }
            if (haveApplyNum == 0) {
                haveApplyNum = getBaseMapper().getHaveApplyCount(
                        siteList.stream().map(site -> site.getId()).collect(Collectors.toList()), Boolean.FALSE);
            }
            // 未预约的数量
            Integer noApplyNum = getNoApplyNum(map.get(key));
            if (noApplyNum > total - haveApplyNum) {
                CategoryEntity category = categoryService.getById(key);
                throw new StatusException("【" + category.getName() + "】教学点考位不足！剩余的考位数量：【" + (total - haveApplyNum)
                        + "】，实际需要的考位数量：【" + noApplyNum + "】");
            }
        }
    }

    private Integer getNoApplyNum(List<StudentEntity> list) {
        int noApplyNum = 0;
        for (StudentEntity student : list) {
            if (student.getApplyNumber() == 1) {
                noApplyNum++;
            } else if (student.getApplyNumber() > 1) {
                // listStudentApply(student.getId(), Boolean.FALSE).size()
                int haveApplyNum = cacheService.getStudentApplyFinishCount(student.getId());
                noApplyNum = noApplyNum + (student.getApplyNumber() - haveApplyNum);
            }
        }
        return noApplyNum;
    }

    private List<StudentApplyEntity> listStudentApply(Long stdId, Boolean cancel) {
        LambdaQueryWrapper<StudentApplyEntity> lm = new LambdaQueryWrapper<>();
        lm.eq(StudentApplyEntity::getStudentId, stdId);
        lm.eq(StudentApplyEntity::getCancel, cancel);
        return this.baseMapper.selectList(lm);
    }

    private List<TimePeriodEntity> listTimePeriod(Long taskId) {
        LambdaQueryWrapper<TimePeriodEntity> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(TimePeriodEntity::getApplyTaskId, taskId);
        wrapper.orderByAsc(TimePeriodEntity::getStartTime);
        List<TimePeriodEntity> timeList = timePeriodService.list(wrapper);
        if (timeList.isEmpty()) {
            throw new StatusException("考试时段未设置");
        }
        return timeList;
    }

    private List<ExamSiteEntity> listExamSite(Long categoryId, Long examSiteId) {
        LambdaQueryWrapper<ExamSiteEntity> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(ExamSiteEntity::getCategoryId, categoryId);
        wrapper.eq(examSiteId != null, ExamSiteEntity::getId, examSiteId);
        wrapper.eq(ExamSiteEntity::getEnable, Boolean.TRUE);
        return examSiteService.list(wrapper);
    }

    @Transactional
    @Override
    public void autoLayout(Long teachingId) {
        ApplyTaskEntity applyTask = getApplyTask();
        String autoLayoutLockKey = String.format(CacheConstants.LOCK_ARRANGE_EXAM,
                DateUtil.formatShortDateString(new Date()));
        RLock autoLayoutLock = (RLock) concurrentService.getLock(autoLayoutLockKey);
        try {
            if (!autoLayoutLock.tryLock()) {
                log.warn("获取锁失败，已有线程在执行排考！lockKey:{}", autoLayoutLock);
                return;
            }

            log.warn("获取锁成功！lockKey:{}", autoLayoutLockKey);

            // 1.根据当前日期，查询不能取消的时段
            List<TimePeriodEntity> timePeriodList = listTimePeriod(applyTask.getId());
            List<TimePeriodEntity> noCancelTimePeroidList = listNoCancelApplyTimePeriod(timePeriodList,
                    applyTask.getAllowApplyCancelDays());
            if (noCancelTimePeroidList.isEmpty()) {
                log.warn("当前时间不在取消预约范围内");
                return;
            }
            // 2.查询考试日期的待排考的考生
            List<StudentApplyEntity> toBeLayoutStudentList = this.baseMapper.listTimePeriod(
                    noCancelTimePeroidList.stream().map(item -> item.getId()).collect(Collectors.toList()),
                    Boolean.FALSE);
            if (toBeLayoutStudentList == null || toBeLayoutStudentList.isEmpty()) {
                log.warn("没有待排考的考生");
                return;
            }
            // 3.开始排考
            Map<Long, List<StudentApplyEntity>> toBeLayoutStudentMap = toBeLayoutStudentList.stream()
                    .collect(Collectors.groupingBy(StudentApplyEntity::getExamSiteId));
            for (Long examSiteId : toBeLayoutStudentMap.keySet()) {
                Map<Long, List<StudentApplyEntity>> timeLayoutStudentMap = toBeLayoutStudentMap.get(examSiteId).stream()
                        .collect(Collectors.groupingBy(StudentApplyEntity::getTimePeriodId));
                List<ExamRoomEntity> roomList = listExamRoom(examSiteId);
                if (roomList.isEmpty()) {
                    log.warn("{}:未设置考场", examSiteId);
                    return;
                }
                ExamSiteEntity examSite = examSiteService.getById(examSiteId);
                layoutStudentByTimePeriod(applyTask.getId(), examSite, roomList, timeLayoutStudentMap);
            }
        } catch (StatusException e) {
            log.error(e.getMessage());
            e.printStackTrace();
        } finally {
            try {
                // 解锁前检查当前线程是否持有该锁
                if (autoLayoutLock.isLocked() && autoLayoutLock.isHeldByCurrentThread()) {
                    autoLayoutLock.unlock();
                    log.info("解锁成功！lockKey:{}", autoLayoutLockKey);
                }
            } catch (Exception e) {
                log.warn(e.getMessage());
            }
        }
    }

    private void layoutStudentByTimePeriod(Long taskId, ExamSiteEntity examSite, List<ExamRoomEntity> roomList,
            Map<Long, List<StudentApplyEntity>> timeLayoutStudentMap) {
        for (Long timePeriodId : timeLayoutStudentMap.keySet()) {
            List<StudentApplyEntity> studentApplyList = timeLayoutStudentMap.get(timePeriodId);
            layoutStudentToRoom(taskId, examSite, roomList, studentApplyList, timePeriodService.getById(timePeriodId));
        }
    }

    private void layoutStudentToRoom(Long taskId, ExamSiteEntity examSite, List<ExamRoomEntity> roomList,
            List<StudentApplyEntity> studentApplyList, TimePeriodEntity timePeriod) {
        Integer timePeriodOrder = getTimePeriodOrder(taskId, timePeriod);
        for (ExamRoomEntity room : roomList) {
            int num = 0;
            for (Iterator<StudentApplyEntity> iterator = studentApplyList.iterator(); iterator.hasNext();) {
                StudentApplyEntity student = iterator.next();
                if (num >= room.getCapacity())
                    break;
                String seatNumber = StringUtils.leftPad(String.valueOf(++num), 3, '0');
                student.setExamRoomId(room.getId());
                student.setSeatNumber(seatNumber);
                student.setTicketNumber(
                        generateTicketNumber(timePeriodOrder, examSite.getCode(), room.getCode(), seatNumber));
                this.baseMapper.updateById(student);
                iterator.remove();
            }
        }
    }

    private Integer getTimePeriodOrder(Long taskId, TimePeriodEntity timePeriod) {
        List<TimePeriodEntity> timeList = listTimePeriod(taskId);
        List<TimePeriodEntity> sameDayTimeList = listSameDayTimePeriod(timeList, timePeriod.getStartTime());
        for (int i = 0; i < sameDayTimeList.size(); i++) {
            TimePeriodEntity time = sameDayTimeList.get(i);
            if (time.getStartTime().equals(timePeriod.getStartTime()))
                return i + 1;
        }
        return 0;
    }

    private List<TimePeriodEntity> listSameDayTimePeriod(List<TimePeriodEntity> timeList, Long startTime) {
        Date day = new Date(startTime);
        List<TimePeriodEntity> resultList = new ArrayList<>();
        for (TimePeriodEntity time : timeList) {
            if (DateUtils.isSameDay(day, new Date(time.getStartTime())))
                resultList.add(time);
        }
        return resultList.stream().sorted(Comparator.comparing(TimePeriodEntity::getStartTime))
                .collect(Collectors.toList());
    }

    private String generateTicketNumber(Integer timePeriodOrder, String examSiteCode, String roomCode,
            String seatNumber) {
        return DateUtil.formatShortDateString(new Date()) + timePeriodOrder + examSiteCode + roomCode + seatNumber;
    }

    public List<ExamRoomEntity> listExamRoom(Long examSiteId) {
        LambdaQueryWrapper<ExamRoomEntity> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(ExamRoomEntity::getExamSiteId, examSiteId);
        wrapper.orderByAsc(ExamRoomEntity::getCode);
        return examRoomService.list(wrapper);
    }

    private List<TimePeriodEntity> listNoCancelApplyTimePeriod(List<TimePeriodEntity> list,
                                                               Integer allowApplyCancelDays) {
        Date noCancelDate = getNoCancelApplyDate(allowApplyCancelDays);
        List<TimePeriodEntity> noCancelTimePeroidList = new ArrayList<>();
        for (TimePeriodEntity time : list) {
            if (DateUtils.isSameDay(noCancelDate, new Date(time.getStartTime())))
                noCancelTimePeroidList.add(time);
        }
        return noCancelTimePeroidList;

    }

    private Date getNoCancelApplyDate(Integer allowApplyCancelDays) {
        return DateUtil.addValues(Calendar.DAY_OF_MONTH, allowApplyCancelDays);
    }

    @Override
    public File downloadSignIn(Long teachingId, Long agentId, Long examDate) {
        ApplyTaskEntity applyTask = getApplyTask();
        List<TimePeriodEntity> timePeriodList = listTimePeriod(applyTask.getId());
        List<TimePeriodEntity> noCancelTimePeroidList = listSameDayTimePeriod(timePeriodList, examDate);
        if (noCancelTimePeroidList.isEmpty()) {
            throw new StatusException("当前时间没有可下载的签到表");
        }
        List<ExamSiteEntity> siteList = listExamSite(teachingId, agentId);
        if (siteList.isEmpty())
            throw new StatusException("当前教学点下没有可用的考点");
        CategoryEntity category = categoryService.getById(teachingId);
        File tempFolder = new File("temp");
        if (!tempFolder.exists()) {
            tempFolder.mkdirs();
        }
        ZipWriter writer = null;
        File zipFile = new File(tempFolder, category.getName() + "签到表.zip");
        try {
            writer = ZipWriter.create(zipFile);
            List<File> fileList = new ArrayList<>();
            for (ExamSiteEntity site : siteList) {
                fileList.addAll(downloadByExamSite(writer, applyTask.getName(), site, noCancelTimePeroidList));
            }
            if (fileList.isEmpty()) {
                throw new StatusException("暂未排考，请等待排考后下载");
            }
        } catch (IOException e) {
            e.printStackTrace();
            throw new StatusException("文件写入异常");
        }
        writer.close();
        return zipFile;
    }

    public List<File> downloadByExamSite(ZipWriter writer, String taskName, ExamSiteEntity site,
            List<TimePeriodEntity> timeList) throws IOException {
        List<File> fileList = new ArrayList<>();
        List<ExamRoomEntity> roomList = listExamRoom(site.getId());
        MaterialTitleInfo title = new MaterialTitleInfo();
        title.setTaskName(taskName);
        title.setSiteName(site.getName());
        for (TimePeriodEntity time : timeList) {
            title.setTimePeriod(DateUtil.getStartAndEndTime(time.getStartTime(), time.getEndTime()));
            for (ExamRoomEntity room : roomList) {
                title.setAddress(room.getAddress());
                title.setRoomCode(room.getCode());
                List<StudentApplyVO> studentList = baseMapper.listStudentApply(time.getId(), room.getId());
                if (!studentList.isEmpty()) {
                    File file = materialService.generateSignInForm(title, studentList);
                    fileList.add(file);
                    writer.write(file, title.getSiteName(),
                            title.getTimePeriod() + " 第【" + title.getRoomCode() + "】考场" + ".pdf");
                }
            }
        }
        return fileList;
    }

    /**
     * 获取某考点某时段的“已预约数量”
     */
    @Override
    public int countApplyFinishForExamSiteAndTimePeriod(Long examSiteId, Long timePeriodId) {
        Integer value = baseMapper.countApplyFinishForExamSiteAndTimePeriod(examSiteId, timePeriodId);
        return value != null ? value : 0;
    }

    @Override
    public List<SignInVO> listSignInDate(Long taskId) {
        List<SignInVO> signInList = new ArrayList<>();
        ApplyTaskEntity task = null;
        if (taskId != null) {
            task = applyTaskService.getById(taskId);
        } else {
            LambdaQueryWrapper<ApplyTaskEntity> wrapper = new LambdaQueryWrapper<ApplyTaskEntity>()
                    .eq(ApplyTaskEntity::getEnable, Boolean.TRUE);
            task = applyTaskService.getOne(wrapper);
        }
        if (task == null) {
            log.warn("当前没有开启的任务");
            return signInList;
        }

        List<TimePeriodEntity> timePeriodList = listTimePeriod(task.getId());
        if (timePeriodList.isEmpty()) {
            log.warn("未配置考试时段");
            return signInList;
        }
        Long startTime = timePeriodList.get(0).getStartTime();
        Long endTime = timePeriodList.get(timePeriodList.size() - 1).getEndTime();

        String todayStr = DateUtil.formatShortSplitDateString(new Date());
        Long longToday = DateUtil.getLongTimeByDate(todayStr + " 00:00:00");
        Date today = new Date(longToday);
        if (longToday >= startTime && longToday <= endTime) {
            SignInVO vo = new SignInVO();
            vo.setExamDate(longToday);
            signInList.add(vo);
        }

        for (int i = 1; i <= task.getAllowApplyCancelDays(); i++) {
            Date otherDay = DateUtil.addValues(today, Calendar.DAY_OF_MONTH, i);
            Long longOtherDay = DateUtil.getLongTimeByDate(DateUtil.formatShortSplitDateString(otherDay) + " 00:00:00");
            if (longOtherDay >= startTime && longOtherDay <= endTime) {
                SignInVO vo = new SignInVO();
                vo.setExamDate(longOtherDay);
                signInList.add(vo);
            }
        }
        signInList.sort(Comparator.comparing(SignInVO::getExamDate));
        return signInList;
    }

}
