package com.qmth.paper.library.business.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.qmth.boot.tools.excel.ExcelWriter;
import com.qmth.boot.tools.excel.enums.ExcelType;
import com.qmth.paper.library.business.bean.vo.FilePathVo;
import com.qmth.paper.library.business.entity.PaperScanTask;
import com.qmth.paper.library.business.mapper.ExamStudentMapper;
import com.qmth.paper.library.business.service.*;
import com.qmth.paper.library.business.templete.execute.AsyncExamStudentImportService;
import com.qmth.paper.library.common.bean.dto.syssetting.SimpleObject;
import com.qmth.paper.library.common.contant.SysSettingConstant;
import com.qmth.paper.library.common.contant.SystemConstant;
import com.qmth.paper.library.common.entity.BasicField;
import com.qmth.paper.library.common.entity.ExamStudent;
import com.qmth.paper.library.common.entity.SysUser;
import com.qmth.paper.library.common.enums.*;
import com.qmth.paper.library.common.lock.LockService;
import com.qmth.paper.library.common.lock.LockType;
import com.qmth.paper.library.common.service.BasicFieldService;
import com.qmth.paper.library.common.service.CommonCacheService;
import com.qmth.paper.library.common.util.ServletUtil;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.FillPatternType;
import org.apache.poi.ss.usermodel.Font;
import org.apache.poi.ss.usermodel.IndexedColors;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.net.URLEncoder;
import java.util.*;
import java.util.stream.Collectors;

/**
 * <p>
 * 学校id 服务实现类
 * </p>
 *
 * @author wangliang
 * @since 2021-08-02
 */
@Service
public class ExamStudentServiceImpl extends ServiceImpl<ExamStudentMapper, ExamStudent> implements ExamStudentService {

    @Resource
    private BasicFieldService basicFieldService;
    @Resource
    private ExamCourseService examCourseService;
    @Resource
    private PaperLibraryService paperLibraryService;
    @Resource
    private PaperScanTaskService paperScanTaskService;
    @Resource
    private CommonCacheService commonCacheService;
    @Resource
    private PaperLibraryCommonService paperLibraryCommonService;
    @Resource
    private AsyncExamStudentImportService asyncExamStudentImportService;
    @Resource
    private FileUploadService fileUploadService;
    @Resource
    private LockService lockService;

    @Override
    public IPage<ExamStudent> findStudentPage(Long examId, String courseCode, String collegeName, String majorName, String clazzName, String queryParams, int pageNumber, int pageSize) {
        queryParams = SystemConstant.translateSpecificSign(queryParams);
        return this.baseMapper.findStudentPage(new Page<>(pageNumber, pageSize), examId, courseCode, collegeName, majorName, clazzName, queryParams);
    }

    @Transactional
    @Override
    public void saveBasicStudent(ExamStudent examStudent) {
        SysUser sysUser = (SysUser) ServletUtil.getRequestUser();
        if (StringUtils.isBlank(examStudent.getStudentCode())) {
            throw ExceptionResultEnum.ERROR.exception("学号必填");
        }
        if (StringUtils.isBlank(examStudent.getCourseCode())) {
            throw ExceptionResultEnum.ERROR.exception("课程代码必填");
        }

        //文件存储方式校验
        SimpleObject storeTypeSimpleObject = commonCacheService.getSysSetting(sysUser.getSchoolId()).get(SysSettingConstant.STORE_TYPE);
        if (StringUtils.isBlank(storeTypeSimpleObject.getValue())) {
            throw ExceptionResultEnum.ERROR.exception("文件存储方式未设置，请联系管理员设置");
        }
        StoreTypeEnum storeType = StoreTypeEnum.valueOf(storeTypeSimpleObject.getValue());
        if (StoreTypeEnum.ROOM.equals(storeType) && StringUtils.isBlank(examStudent.getExamRoom())) {
            throw ExceptionResultEnum.ERROR.exception("考场必填");
        }
        if (StoreTypeEnum.CLASS.equals(storeType) && StringUtils.isBlank(examStudent.getClassName())) {
            throw ExceptionResultEnum.ERROR.exception("班级必填");
        }

        // 验证
        ExamStudent byStudentCode = this.getByExamIdAndCourseCodeAndStudentCode(examStudent.getExamId(), examStudent.getCourseCode(), examStudent.getStudentCode());
        if (examStudent.getId() == null) {
            if (StringUtils.isBlank(examStudent.getCourseName())) {
                throw ExceptionResultEnum.ERROR.exception("课程名称必填");
            }
            if (byStudentCode != null) {
                throw ExceptionResultEnum.ERROR.exception(String.format("学号[%s]在课程%s（%s）下已存在", examStudent.getStudentCode(), examStudent.getCourseName(), examStudent.getCourseCode()));
            }
            // 学号去掉空格
            examStudent.setStudentCode(StringUtils.deleteWhitespace(examStudent.getStudentCode()));
            if (StringUtils.isBlank(examStudent.getStudentName())) {
                throw ExceptionResultEnum.ERROR.exception("姓名必填");
            }
            // 课程代码去掉空格
            examStudent.setCourseCode(StringUtils.deleteWhitespace(examStudent.getCourseCode()));
            // 课程名称去掉空格
            examStudent.setCourseName(StringUtils.deleteWhitespace(examStudent.getCourseName()));

            // 校验是否设置了存储方式
            SimpleObject recognitionTypeSimpleObject = commonCacheService.getSysSetting(sysUser.getSchoolId()).get(SysSettingConstant.RECOGNITION_TYPE);
            if (StringUtils.isBlank(recognitionTypeSimpleObject.getValue())) {
                throw ExceptionResultEnum.ERROR.exception("识别方式未设置，请联系管理员设置");
            }

            RecognitionTypeEnum recognitionType = RecognitionTypeEnum.valueOf(recognitionTypeSimpleObject.getValue());

            examStudent.setId(SystemConstant.getDbUuid());
            examStudent.setSchoolId(sysUser.getSchoolId());
            examStudent.setCreateId(sysUser.getId());
            examStudent.setCreateTime(System.currentTimeMillis());

            // 考场去掉空格
            examStudent.setExamRoom(StringUtils.isNotBlank(examStudent.getExamRoom()) ? StringUtils.deleteWhitespace(examStudent.getExamRoom()) : null);
            // 班级去掉空格
            examStudent.setClassName(StringUtils.isNotBlank(examStudent.getClassName()) ? StringUtils.deleteWhitespace(examStudent.getClassName()) : null);
            boolean save = this.save(examStudent);

            if (save) {
                // 保存课程
                examCourseService.saveStudentCourse(examStudent.getExamId(), examStudent);
                // 保存任务（存在则更新考生数量）
                paperScanTaskService.addScanTask(examStudent.getExamId(), recognitionType, storeType, examStudent);
            }
        } else {
            if (byStudentCode != null && !examStudent.getId().equals(byStudentCode.getId())) {
                throw ExceptionResultEnum.ERROR.exception(String.format("学号[%s]在课程%s（%s）下已存在", examStudent.getStudentCode(), examStudent.getCourseName(), examStudent.getCourseCode()));
            }
            //按照存储方式，不能更改相应的考场、班级
            ExamStudent existStudent = this.getById(examStudent.getId());
            if (Objects.isNull(existStudent)) {
                throw ExceptionResultEnum.ERROR.exception(String.format("找不到考生,id:%s", examStudent.getId()));
            }
            if (StoreTypeEnum.ROOM.equals(storeType) && !StringUtils.deleteWhitespace(examStudent.getExamRoom()).equals(existStudent.getExamRoom())) {
                throw ExceptionResultEnum.ERROR.exception("不能更改考场");
            }
            if (StoreTypeEnum.CLASS.equals(storeType) && !StringUtils.deleteWhitespace(examStudent.getClassName()).equals(existStudent.getClassName())) {
                throw ExceptionResultEnum.ERROR.exception("不能更改班级");
            }

            // 考场去掉空格
            examStudent.setExamRoom(StringUtils.isNotBlank(examStudent.getExamRoom()) ? StringUtils.deleteWhitespace(examStudent.getExamRoom()) : null);
            // 班级去掉空格
            examStudent.setClassName(StringUtils.isNotBlank(examStudent.getClassName()) ? StringUtils.deleteWhitespace(examStudent.getClassName()) : null);

            examStudent.setId(examStudent.getId());
            examStudent.setEnable(true);
            examStudent.setUpdateId(sysUser.getId());
            examStudent.setUpdateTime(System.currentTimeMillis());
            this.updateById(examStudent);
        }
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public Boolean removeBasicStudentBatch(List<Long> ids) {
        if (CollectionUtils.isEmpty(ids)) {
            throw ExceptionResultEnum.ERROR.exception("请选择删除的数据");
        }
        List<ExamStudent> examStudentList = this.listByIds(ids);
        if (CollectionUtils.isNotEmpty(examStudentList)) {
            List<String> studentCodes = examStudentList.stream().filter(m -> m.getBindCount() > 0).map(ExamStudent::getStudentCode).collect(Collectors.toList());
            if (CollectionUtils.isNotEmpty(studentCodes)) {
                throw ExceptionResultEnum.ERROR.exception("有考生已绑定图片，学号：" + String.join(",", studentCodes));
            }
        }
        // 删除基础学生
        this.removeByIds(ids);
        Map<String, String> stringMap = examStudentList.stream().collect(Collectors.toMap(ExamStudent::getCourseCode, ExamStudent::getCourseName, (oldValue, newValue) -> oldValue));
        for (Map.Entry<String, String> entry : stringMap.entrySet()) {
            Long examId = examStudentList.get(0).getExamId();
            int count = this.countByExamIdAndCourseCode(examId, entry.getKey());
            PaperScanTask paperScanTask = paperScanTaskService.getByExamIdAndCourseCode(examId, entry.getKey());
            if (count == 0) {
                // 删除课程
                examCourseService.removeByExamIdAndCourseCode(examId, entry.getKey());
                // 删除任务
                if (paperScanTask != null) {
                    paperScanTaskService.removeById(paperScanTask.getId());
                }
            } else {
                if (paperScanTask != null) {
                    paperScanTaskService.updateStudentCount(paperScanTask);
                }
            }
        }
        return true;
    }

    @Override
    public ExamStudent getByExamIdAndCourseCodeAndStudentCode(Long examId, String courseCode, String studentCode) {
        QueryWrapper<ExamStudent> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda().eq(ExamStudent::getExamId, examId)
                .eq(ExamStudent::getCourseCode, courseCode)
                .eq(ExamStudent::getStudentCode, studentCode);
        return this.getOne(queryWrapper);
    }

    @Transactional
    @Override
    public void dataImport(Long semesterId, Long examId, MultipartFile file) {
        SysUser sysUser = (SysUser) ServletUtil.getRequestUser();
        // 校验是否设置了存储方式
        SimpleObject recognitionTypeSimpleObject = commonCacheService.getSysSetting(sysUser.getSchoolId()).get(SysSettingConstant.RECOGNITION_TYPE);
        if (StringUtils.isBlank(recognitionTypeSimpleObject.getValue())) {
            throw ExceptionResultEnum.ERROR.exception("识别方式未设置");
        }
        SimpleObject storeTypeSimpleObject = commonCacheService.getSysSetting(sysUser.getSchoolId()).get(SysSettingConstant.STORE_TYPE);
        if (StringUtils.isBlank(storeTypeSimpleObject.getValue())) {
            throw ExceptionResultEnum.ERROR.exception("文件存储方式未设置");
        }
        List<BasicField> basicFieldList = basicFieldService.listBySchoolId(sysUser.getSchoolId());
        if (CollectionUtils.isEmpty(basicFieldList)) {
            throw ExceptionResultEnum.ERROR.exception("字段管理未配置");
        }

        RecognitionTypeEnum recognitionType = RecognitionTypeEnum.valueOf(recognitionTypeSimpleObject.getValue());
        StoreTypeEnum storeType = StoreTypeEnum.valueOf(storeTypeSimpleObject.getValue());

        try {
            Map<String, Object> map = paperLibraryCommonService.saveTask(file, examId, TaskTypeEnum.EXAM_STUDENT_IMPORT);
            map.put("examId", examId);
            map.put("recognitionType", recognitionType);
            map.put("storeType", storeType);
            map.put("inputStream", file.getInputStream());
            asyncExamStudentImportService.importTask(map);
        } catch (Exception e) {
            throw ExceptionResultEnum.ERROR.exception(e.getMessage());
        }
    }

    @Override
    public void updateBindCount(Long studentId) {
        try {
            lockService.waitlock(LockType.UPDATE_STUDENT_PICTURE_COUNT, studentId);
            UpdateWrapper<ExamStudent> updateWrapper = new UpdateWrapper<>();
            updateWrapper.lambda().set(ExamStudent::getBindCount, paperLibraryService.countByStudentId(studentId))
                    .eq(ExamStudent::getId, studentId);
            this.update(updateWrapper);
        } finally {
            lockService.unlock(LockType.UPDATE_STUDENT_PICTURE_COUNT, studentId);
        }
    }

    @Override
    public int countByExamIdAndCourseCode(Long examId, String courseCode) {
        QueryWrapper<ExamStudent> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda().eq(ExamStudent::getExamId, examId)
                .eq(ExamStudent::getCourseCode, courseCode);
        return this.count(queryWrapper);
    }

    @Override
    public void downloadTemplate(HttpServletResponse response) {
        Long schoolId = Long.valueOf(ServletUtil.getRequestHeaderSchoolId().toString());
        List<BasicField> basicFieldList = basicFieldService.listBySchoolId(schoolId);
        if (CollectionUtils.isEmpty(basicFieldList)) {
            throw ExceptionResultEnum.ERROR.exception("字段管理未设置");
        }

        List<String> columnNameList = basicFieldList.stream().filter(m -> m.getEnable()).map(BasicField::getName).collect(Collectors.toList());
        try {
            String sheetName = "考生导入模板";
            log.debug("导出Excel开始...");
            response.setHeader("Content-Disposition", "inline;filename=" + URLEncoder.encode("考生导入模板", SystemConstant.CHARSET_NAME) + ".xlsx");
            response.setContentType("application/vnd.ms-excel");
            ServletOutputStream outputStream = response.getOutputStream();
            ExcelWriter writer = ExcelWriter.create(ExcelType.XLSX);
            List<String[]> columnValues = new ArrayList<>();
            writer.writeDataArrays(sheetName, null, columnNameList.toArray(new String[columnNameList.size()]), columnValues.listIterator());

            // 必填字段
            List<String> requireFields = Arrays.asList(new String[]{BasicFieldEnum.STUDENT_CODE.getName(), BasicFieldEnum.STUDENT_NAME.getName(), BasicFieldEnum.COURSE_CODE.getName(), BasicFieldEnum.COURSE_NAME.getName()});

            List<Integer> columnNameIndexList = new ArrayList<>();
            List<Integer> columnNameRequireIndexList = new ArrayList<>();
            for (int i = 0; i < columnNameList.size(); i++) {
                if (requireFields.contains(columnNameList.get(i))) {
                    columnNameRequireIndexList.add(i);
                } else {
                    columnNameIndexList.add(i);
                }
            }

            // 表头背景灰色
            if (CollectionUtils.isNotEmpty(columnNameIndexList)) {
                CellStyle cellStyle1 = writer.createCellStyle();
                Font font1 = writer.createFont();
                font1.setBold(true);
                font1.setFontHeightInPoints((short) 12);
                cellStyle1.setFont(font1);
                cellStyle1.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
                cellStyle1.setFillPattern(FillPatternType.SOLID_FOREGROUND);
                writer.setCellStyle(sheetName, cellStyle1, 0, ArrayUtils.toPrimitive(columnNameIndexList.toArray(new Integer[columnNameIndexList.size()])));
            }

            // 必填字段背景灰色、字段红色
            if (CollectionUtils.isNotEmpty(columnNameRequireIndexList)) {
                CellStyle cellStyle2 = writer.createCellStyle();
                Font font2 = writer.createFont();
                font2.setBold(true);
                font2.setFontHeightInPoints((short) 12);
                font2.setColor(IndexedColors.RED.getIndex());
                cellStyle2.setFont(font2);
                cellStyle2.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
                cellStyle2.setFillPattern(FillPatternType.SOLID_FOREGROUND);
                writer.setCellStyle(sheetName, cellStyle2, 0, ArrayUtils.toPrimitive(columnNameRequireIndexList.toArray(new Integer[columnNameRequireIndexList.size()])));
            }
            writer.output(outputStream);
            outputStream.flush();
            outputStream.close();
            log.debug("导出Excel结束");
        } catch (Exception e) {
            throw ExceptionResultEnum.ERROR.exception("考生导入模板导出失败：" + e.getMessage());
        }

    }

    @Override
    public List<String> listByStoreType(Long examId, String courseCode) {
        PaperScanTask paperScanTask = paperScanTaskService.getByExamIdAndCourseCode(examId, courseCode);
        if (paperScanTask == null) {
            throw ExceptionResultEnum.ERROR.exception("扫描任务不存在");
        }
        List<String> list = new ArrayList<>();
        List<ExamStudent> examStudentList = this.listByExamIdAndCourseCode(examId, courseCode);
        if (CollectionUtils.isNotEmpty(examStudentList)) {
            // 按班级存储
            if (StoreTypeEnum.CLASS.equals(paperScanTask.getStoreType())) {
                list = examStudentList.stream().filter(m -> StringUtils.isNotBlank(m.getClassName())).map(ExamStudent::getClassName).distinct().collect(Collectors.toList());
            }
            // 按考场存储
            else if (StoreTypeEnum.ROOM.equals(paperScanTask.getStoreType())) {
                list = examStudentList.stream().filter(m -> StringUtils.isNotBlank(m.getExamRoom())).map(ExamStudent::getExamRoom).distinct().collect(Collectors.toList());
            }
        }
        return list;
    }

    @Override
    public List<ExamStudent> listByExamIdAndCourseCode(Long examId, String courseCode) {
        QueryWrapper<ExamStudent> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda().eq(ExamStudent::getExamId, examId)
                .eq(ExamStudent::getCourseCode, courseCode);
        return this.list(queryWrapper);
    }

    @Override
    public ExamStudent saveExamStudentFromClient(ExamStudent examStudent) {
        SysUser sysUser = (SysUser) ServletUtil.getRequestUser();
        if (StringUtils.isBlank(examStudent.getStudentCode())) {
            throw ExceptionResultEnum.ERROR.exception("学号必填");
        }
        if (StringUtils.isBlank(examStudent.getCourseCode())) {
            throw ExceptionResultEnum.ERROR.exception("课程代码必填");
        }
        // 验证
        ExamStudent byStudentCode = this.getByExamIdAndCourseCodeAndStudentCode(examStudent.getExamId(), examStudent.getCourseCode(), examStudent.getStudentCode());
        if (byStudentCode != null) {
            return byStudentCode;
        } else {
            if (StringUtils.isBlank(examStudent.getCourseName())) {
                throw ExceptionResultEnum.ERROR.exception("课程名称必填");
            }
            // 学号去掉空格
            examStudent.setStudentCode(StringUtils.deleteWhitespace(examStudent.getStudentCode()));
            if (StringUtils.isBlank(examStudent.getStudentName())) {
                throw ExceptionResultEnum.ERROR.exception("姓名必填");
            }
            // 课程代码去掉空格
            examStudent.setCourseCode(StringUtils.deleteWhitespace(examStudent.getCourseCode()));
            // 课程名称去掉空格
            examStudent.setCourseName(StringUtils.deleteWhitespace(examStudent.getCourseName()));

            // 校验是否设置了存储方式
            SimpleObject recognitionTypeSimpleObject = commonCacheService.getSysSetting(sysUser.getSchoolId()).get(SysSettingConstant.RECOGNITION_TYPE);
            if (StringUtils.isBlank(recognitionTypeSimpleObject.getValue())) {
                throw ExceptionResultEnum.ERROR.exception("识别方式未设置，请联系管理员设置");
            }
            SimpleObject storeTypeSimpleObject = commonCacheService.getSysSetting(sysUser.getSchoolId()).get(SysSettingConstant.STORE_TYPE);
            if (StringUtils.isBlank(storeTypeSimpleObject.getValue())) {
                throw ExceptionResultEnum.ERROR.exception("文件存储方式未设置，请联系管理员设置");
            }
            RecognitionTypeEnum recognitionType = RecognitionTypeEnum.valueOf(recognitionTypeSimpleObject.getValue());
            StoreTypeEnum storeType = StoreTypeEnum.valueOf(storeTypeSimpleObject.getValue());
            if (StoreTypeEnum.ROOM.equals(storeType) && StringUtils.isBlank(examStudent.getExamRoom())) {
                throw ExceptionResultEnum.ERROR.exception("考场必填");
            }
            if (StoreTypeEnum.CLASS.equals(storeType) && StringUtils.isBlank(examStudent.getClassName())) {
                throw ExceptionResultEnum.ERROR.exception("班级必填");
            }

            examStudent.setId(SystemConstant.getDbUuid());
            examStudent.setSchoolId(sysUser.getSchoolId());
            examStudent.setCreateId(sysUser.getId());
            examStudent.setCreateTime(System.currentTimeMillis());

            // 考场去掉空格
            examStudent.setExamRoom(StringUtils.isNotBlank(examStudent.getExamRoom()) ? StringUtils.deleteWhitespace(examStudent.getExamRoom()) : null);
            // 班级去掉空格
            examStudent.setClassName(StringUtils.isNotBlank(examStudent.getClassName()) ? StringUtils.deleteWhitespace(examStudent.getClassName()) : null);
            boolean save = this.save(examStudent);

            if (save) {
                // 保存课程
                examCourseService.saveStudentCourse(examStudent.getExamId(), examStudent);
                // 保存任务（存在则更新考生数量）
                paperScanTaskService.addScanTask(examStudent.getExamId(), recognitionType, storeType, examStudent);
            }
            return examStudent;
        }
    }

    @Override
    public void clearPdfFilePath(Long studentId) {
        ExamStudent examStudent = this.getById(studentId);
        try {
            if (examStudent != null && StringUtils.isNotBlank(examStudent.getFilePath())) {
                FilePathVo filePathVo = JSON.parseObject(examStudent.getFilePath(), FilePathVo.class);
                if (this.clearFilePath(studentId)) {
                    fileUploadService.deleteFile(filePathVo.getPath());
                }
            }
        } catch (Exception e) {
            log.error("清除pdf文件路径失败" + e.getMessage());
        }
    }

    @Override
    public boolean clearFilePath(Long studentId) {
        UpdateWrapper<ExamStudent> updateWrapper = new UpdateWrapper<>();
        updateWrapper.lambda().set(ExamStudent::getFilePath, null)
                .eq(ExamStudent::getId, studentId);
        return this.update(updateWrapper);
    }
}
