package com.qmth.jkserver.core.kj.plugin;

import com.qmth.jkserver.common.FileData;
import com.qmth.jkserver.constant.SpringContextHolder;
import com.qmth.jkserver.constant.SystemConstant;
import com.qmth.jkserver.core.exception.JkServerException;
import com.qmth.jkserver.model.Attachment;
import com.qmth.jkserver.model.ExamStudent;
import com.qmth.jkserver.service.AttachmentService;
import com.qmth.jkserver.service.ExamStudentService;
import com.qmth.jkserver.util.FileStoreUtil;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;

/**
 * 单个考场  视唱答案校验
 *
 * @author tianjun
 */
public class ExamRoomSolfegeKjValidate {
    private static final Logger LOGGER = LoggerFactory.getLogger(ExamRoomSolfegeKjValidate.class);

    private String examCode;
    private InputStream inputStream;
    private ZipFile zipFile;
    private ZipInputStream zipInputStream;
    private ExamStudentService examStudentService;
    private Map<String, ExamStudent> STUDENT_CAHE;


    public ExamRoomSolfegeKjValidate(File zip, String examCode, Map<String, ExamStudent> STUDENT_CAHE) throws ZipException, IOException {
        zipFile = new ZipFile(zip);
        inputStream = new BufferedInputStream(new FileInputStream(zip));
        zipInputStream = new ZipInputStream(inputStream);
        this.examStudentService = SpringContextHolder.getBean(ExamStudentService.class);
        this.examCode = examCode;
        this.STUDENT_CAHE = STUDENT_CAHE;
    }

    private ExamStudent getCachedStudent(String examCode, String ticketNo) {
        if (StringUtils.isEmpty(examCode) || StringUtils.isEmpty(ticketNo)) {
            throw new JkServerException("考试识别码以及准考证号不能够为空");
        }
        String key = examCode + ticketNo;
        ExamStudent examStudent = STUDENT_CAHE.get(key);
        if (examStudent != null) {
            LOGGER.info(".....@get student from cache OK!!");
            return examStudent;
        }
        examStudent = examStudentService.findSolfegeStudent(examCode, ticketNo);
        if (examStudent == null) {
            throw new JkServerException(
                    "无法获取到此考生的考务数据,考试识别码:" + examCode + ",准考证号:" + ticketNo);
        } else {
            LOGGER.info(".....@cache student OK!!");
            STUDENT_CAHE.put(key, examStudent);
            return examStudent;
        }
    }

    public void execute() throws IOException {
        InputStream absentCsv = null;
        InputStream statisticsCsv = null;
        InputStream cheatCsv = null;
        InputStream paperCsv = null;
        ZipEntry ze = null;
        try {
            while ((ze = zipInputStream.getNextEntry()) != null) {
                if (!ze.isDirectory()) {
                    long size = ze.getSize();
                    if (size > 0 && (ze.getName().endsWith("csv") || ze.getName().endsWith("txt"))) {
                        if (StringUtils.trimToNull(ze.getName()).equalsIgnoreCase("absent.csv")) {
                            absentCsv = zipFile.getInputStream(ze);
                        }
                        if (StringUtils.trimToNull(ze.getName()).equalsIgnoreCase("cheat.csv")) {
                            cheatCsv = zipFile.getInputStream(ze);
                        }
                        if (StringUtils.trimToNull(ze.getName()).equalsIgnoreCase("paper.csv")) {
                            paperCsv = zipFile.getInputStream(ze);
                        }
                        if (StringUtils.trimToNull(ze.getName()).equalsIgnoreCase("statistics.csv")) {
                            statisticsCsv = zipFile.getInputStream(ze);
                        }
                    }
                }
                zipInputStream.closeEntry();
            }
            // 1、计算缺考的人数
            Map<String, Integer> absentMap = countAbsent(absentCsv);
            // 2、计算实考人数(有答案的人数与作弊人数汇总去重)
            Map<String, Integer> reallyMap = countActual(paperCsv, cheatCsv);
            // 3、读取statistics.csv文件,进行实考人数与缺考人数的校验
            validateActualAndAbsent(absentMap, reallyMap, statisticsCsv);
        } finally {
            if (inputStream != null) {
                IOUtils.closeQuietly(inputStream);
            }
            if (zipInputStream != null) {
                IOUtils.closeQuietly(zipInputStream);
            }
            if (zipFile != null) {
                IOUtils.closeQuietly(zipFile);
            }
            //关闭已经打开的流
            if (absentCsv != null) {
                IOUtils.closeQuietly(absentCsv);
            }
            if (statisticsCsv != null) {
                IOUtils.closeQuietly(statisticsCsv);
            }
            if (cheatCsv != null) {
                IOUtils.closeQuietly(cheatCsv);
            }
            if (paperCsv != null) {
                IOUtils.closeQuietly(paperCsv);
            }
        }
    }

    protected void validateActualAndAbsent(Map<String, Integer> absentMap, Map<String, Integer> reallyMap,
                                           InputStream statisticsCsv) throws IOException {
        FileData statisticsFileData = new FileData(statisticsCsv, "~");
        int absent = 0;
        int real = 0;
        while (statisticsFileData.nextLine()) {
            String[] datas = statisticsFileData.getDatas();
            Integer sk = Integer.valueOf(datas[4]);
            real += (sk == null ? 0 : sk.intValue());
            Integer qk = Integer.valueOf(datas[5]);
            absent += (qk == null ? 0 : qk.intValue());
        }
        statisticsFileData.close();
        int computeAbsent = 0;
        int computeReal = 0;
        for (Integer abs : absentMap.values()) {
            if (abs != null) {
                computeAbsent += abs.intValue();
            }
        }
        for (Integer rel : reallyMap.values()) {
            if (rel != null) {
                computeReal += rel.intValue();
            }
        }
        if (absent != computeAbsent) {
            throw new JkServerException(
                    "缺考总人数校验失败,传入的缺考总人数是:" + absent + ",系统计算出的缺考总人数是:" + computeAbsent);
        }
        if (real != computeReal) {
            throw new JkServerException(
                    "实考总人数校验失败,传入的实考总人数是:" + real + ",系统计算出的实考总人数是:" + computeReal);
        }
    }

    // zip数据包的解压
    protected File decodeZip(String encryptedFileId) throws Exception {
        AttachmentService attachmentService = SpringContextHolder.getBean(AttachmentService.class);
        //2020-12-19日加入阿里云下载
        FileStoreUtil fileStoreConfig = SpringContextHolder.getBean(FileStoreUtil.class);
        Attachment encrypted = attachmentService.findByIdService(Long.parseLong(encryptedFileId));
        File encryptedFile = null;
        if (fileStoreConfig.workIsOss()) {
            StringJoiner stringJoiner = new StringJoiner("").add(fileStoreConfig.getWorkRoot())
                    .add(File.separator).add(encrypted.getAccessUri());
            encryptedFile = fileStoreConfig.ossDownload(stringJoiner.toString().replaceAll("\\\\", "/"), fileStoreConfig.getWorkRoot() + File.separator + encrypted.getAccessUri(), fileStoreConfig.getWorkRoot());
        } else {
            encryptedFile = new File(fileStoreConfig.getWorkRoot(), encrypted.getAccessUri());
        }
        if (!encryptedFile.exists()) {
            throw new JkServerException("监考端上传的原始考试数据包不存在");
        }
        File decodedZip = decode(encryptedFile);
        return decodedZip;
    }

    // 统计每一个批次的缺考人数
    protected Map<String, Integer> countAbsent(InputStream file) throws IOException {
        if (file == null) {
            return new LinkedHashMap<>();
        }
        Map<String, Integer> absentMap = new LinkedHashMap<>();
        FileData absentFileData = new FileData(file, "~");
        while (absentFileData.nextLine()) {
            String[] datas = absentFileData.getDatas();
            String batchId = datas[0];
            Integer total = absentMap.get(batchId);
            if (total == null) {
                total = 0;
            }
            total++;
            absentMap.put(batchId, total);
        }
        absentFileData.close();
        return absentMap;
    }

    // 计算每一个批次的实考人数
    protected Map<String, Integer> countActual(InputStream paperCsv, InputStream cheatCsv) throws IOException {
        Map<String, Set<String>> actualMap = new LinkedHashMap<>();
        if (paperCsv != null) {
            FileData fileData = new FileData(paperCsv, "~");
            while (fileData.nextLine()) {
                String[] datas = fileData.getDatas();
                String ticketNo = datas[0];
                ExamStudent examStudent = getCachedStudent(examCode, ticketNo);
                if (examStudent == null) {
                    throw new JkServerException(
                            "无法获取到此考生的考务数据,考试识别码:" + examCode + ",准考证号:" + ticketNo);
                }
                String batchId = examStudent.getBatchId();
                if (StringUtils.isEmpty(batchId)) {
                    throw new JkServerException("考务考生表中的批次ID是null");
                }
                Set<String> students = actualMap.get(batchId);
                if (students == null) {
                    students = new HashSet<>();
                    actualMap.put(batchId, students);
                }
                students.add(ticketNo);
            }
            fileData.close();
        }
        if (cheatCsv != null) {
            FileData cheatFileData = new FileData(cheatCsv, "~");
            while (cheatFileData.nextLine()) {
                String[] datas = cheatFileData.getDatas();
                String batchId = datas[0];
                String ticketNo = datas[3];
                Set<String> students = actualMap.get(batchId);
                if (students == null) {
                    students = new HashSet<>();
                    actualMap.put(batchId, students);
                }
                students.add(ticketNo);
            }
            cheatFileData.close();
        }
        // 当前考场数据包中每一个批次的实考人数
        Map<String, Integer> reallyMap = new LinkedHashMap<String, Integer>();
        for (String batchId : actualMap.keySet()) {
            if (actualMap.get(batchId) != null) {
                reallyMap.put(batchId, actualMap.get(batchId).size());
            }
        }
        return reallyMap;
    }

    @SuppressWarnings("resource")
    public File decode(File file) throws IOException {
        byte[] startBytes = new byte[23]; // zip头部块
        byte[] endBytes = new byte[23]; // zip尾部块
        byte[] loop = new byte[41];
        RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw");// 加密的文件格式为qm
        File ansFile = new File(file.getParentFile(), file.getName() + SystemConstant.ZIP_PREFIX);
        if (!ansFile.exists()) {
            ansFile.createNewFile();
        }
        BufferedOutputStream os = new BufferedOutputStream(new FileOutputStream(ansFile));
        if (file.length() < (136 + 54)) {
            throw new JkServerException("文件结构错误");
        }
        randomAccessFile.seek(file.length() - 54);
        int len = 0;
        len = randomAccessFile.read(startBytes);
        os.write(startBytes);
        if (len != 23) {
            throw new JkServerException("文件结构错误,头部23字节读取失败");
        }
        len = randomAccessFile.read(endBytes);
        if (len != 23) {
            throw new JkServerException("文件结构错误,尾部23字节读取失败");
        }
        long total = file.length() - 54 - 136;
        // 每次读取41字节 计算需要读取多少次，以及最后一次需要读取多少字节
        int count = new Double(Math.floor(total / 41)).intValue(); // 读取次数
        int left = new Double(total % 41).intValue(); // 最后一次读取多少字节
        // 开始读取中间的部分
        randomAccessFile.seek(file.length() - 54 - left);
        byte[] leftBytes = new byte[left];
        len = randomAccessFile.read(leftBytes);
        if (len != left) {
            throw new JkServerException("加密文件结构错误,尾部字节读取错误");
        } else {
            os.write(leftBytes);
        }
        for (int i = 0; i < count; i++) {
            randomAccessFile.seek(file.length() - 54 - left - (i + 1) * 41);
            len = randomAccessFile.read(loop);
            if (len != 41) {
                throw new JkServerException("加密文件结构错误");
            } else {
                os.write(loop);
            }
        }
        os.write(endBytes);
        IOUtils.closeQuietly(os);
        randomAccessFile.close();
        if (ansFile.length() != file.length() - 144) {
            throw new JkServerException("文件解码失败");
        }
        return ansFile;
    }
}
