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.LambdaUpdateWrapper;
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.api.exception.ApiException;
import com.qmth.paper.library.business.bean.result.PaperLibraryImageResult;
import com.qmth.paper.library.business.bean.result.PaperLibraryResult;
import com.qmth.paper.library.business.bean.result.TaskStudentResult;
import com.qmth.paper.library.business.bean.vo.FilePathVo;
import com.qmth.paper.library.business.bean.vo.PathSequenceVo;
import com.qmth.paper.library.business.bean.vo.RecognitionResultVo;
import com.qmth.paper.library.business.entity.PaperLibrary;
import com.qmth.paper.library.business.entity.PaperScanTask;
import com.qmth.paper.library.business.mapper.PaperLibraryMapper;
import com.qmth.paper.library.business.service.*;
import com.qmth.paper.library.business.templete.execute.AsyncGeneratePdfService;
import com.qmth.paper.library.common.bean.dto.syssetting.SimpleObject;
import com.qmth.paper.library.common.bean.params.DownLoadPaperParams;
import com.qmth.paper.library.common.contant.SysSettingConstant;
import com.qmth.paper.library.common.contant.SystemConstant;
import com.qmth.paper.library.common.entity.*;
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.BasicSchoolService;
import com.qmth.paper.library.common.service.BasicSemesterService;
import com.qmth.paper.library.common.service.CommonCacheService;
import com.qmth.paper.library.common.service.TBTaskService;
import com.qmth.paper.library.common.util.*;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.json.JSONArray;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * <p>
 * 图片库 服务实现类
 * </p>
 */
@Service
public class PaperLibraryServiceImpl extends ServiceImpl<PaperLibraryMapper, PaperLibrary> implements PaperLibraryService {

    @Resource
    private PaperScanTaskService paperScanTaskService;
    @Resource
    private BasicSchoolService basicSchoolService;
    @Resource
    private BasicExamService basicExamService;
    @Resource
    private BusinessCacheService businessCacheService;
    @Resource
    private BasicSemesterService basicSemesterService;
    @Resource
    private ExamStudentService examStudentService;
    @Resource
    private FileUploadService fileUploadService;
    @Resource
    private OcrUtil ocrUtil;
    @Resource
    private FileStoreUtil fileStoreUtil;
    @Resource
    private CommonCacheService commonCacheService;
    @Resource
    private LockService lockService;
    @Resource
    private TBTaskService tbTaskService;
    @Resource
    private AsyncGeneratePdfService asyncGeneratePdfService;

    @Override
    public int countScanStudentCount(Long paperScanTaskId) {
        return this.baseMapper.countScanStudentCount(paperScanTaskId);
    }

    @Override
    public PaperLibraryResult listStudentPicture(Long paperScanTaskId, Long studentId) {
        PaperLibraryResult paperLibraryResult = new PaperLibraryResult();
        ExamStudent examStudent = examStudentService.getById(studentId);
        if (examStudent != null) {
            paperLibraryResult.setStudentName(examStudent.getStudentName());
            paperLibraryResult.setStudentCode(examStudent.getStudentCode());
            paperLibraryResult.setCourseCode(examStudent.getCourseCode());
            paperLibraryResult.setCourseName(examStudent.getCourseName());
            paperLibraryResult.setCollegeName(examStudent.getCollegeName());
            paperLibraryResult.setMajorName(examStudent.getMajorName());
            paperLibraryResult.setClassName(examStudent.getClassName());
            paperLibraryResult.setTeacher(examStudent.getTeacher());
            paperLibraryResult.setTeachClass(examStudent.getTeachClass());
            paperLibraryResult.setBindCount(examStudent.getBindCount());
            paperLibraryResult.setScore(examStudent.getScore());
        }

        QueryWrapper<PaperLibrary> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda().eq(paperScanTaskId != null, PaperLibrary::getPaperScanTaskId, paperScanTaskId)
                .eq(PaperLibrary::getStudentId, studentId)
                .orderByAsc(PaperLibrary::getSequence);
        List<PaperLibrary> paperLibraryList = this.list(queryWrapper);

        List<PaperLibraryImageResult> fileUrlList = new ArrayList<>();
        for (PaperLibrary paperLibrary : paperLibraryList) {
            if (StringUtils.isBlank(paperLibrary.getPath())) {
                throw ExceptionResultEnum.ERROR.exception("未找到图片路径");
            }
            List<FilePathVo> objectList = JSON.parseArray(paperLibrary.getPath(), FilePathVo.class);
            PaperLibraryImageResult paperLibraryImageResult = new PaperLibraryImageResult();
            paperLibraryImageResult.setPaperLibraryId(paperLibrary.getId());
            List<String> fileUrls = new ArrayList<>();
            for (FilePathVo filePathVo : objectList) {
                fileUrls.add(fileUploadService.filePreview(filePathVo));
            }
            paperLibraryImageResult.setFileUrls(fileUrls);
            paperLibraryImageResult.setBatchNo(paperLibrary.getBatchNo());
            fileUrlList.add(paperLibraryImageResult);
        }
        paperLibraryResult.setFileUrls(fileUrlList);
        return paperLibraryResult;
    }

    @Override
    public int countScanCount(Long paperScanTaskId) {
        QueryWrapper<PaperLibrary> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda().eq(PaperLibrary::getPaperScanTaskId, paperScanTaskId);
        return this.count(queryWrapper);
    }

    @Override
    public IPage<TaskStudentResult> pageStudent(Long examId, String courseCode, String studentCode, Integer pageNumber, Integer pageSize) {
        Page<TaskStudentResult> ipage = new Page<>(pageNumber, pageSize);
        return this.baseMapper.listByStudentCode(ipage, examId, courseCode, studentCode);
    }

    @Override
    public RecognitionResultVo recognition(PaperScanTask paperScanTask, String studentCode, MultipartFile file, Integer x, Integer y, Integer width, Integer height, Integer rotate) {
        RecognitionResultVo recognitionResultVo = new RecognitionResultVo(studentCode, null);
        BasicSchool basicSchool = basicSchoolService.getById(paperScanTask.getSchoolId());
        String courseNameCode = String.format("%s(%s)", paperScanTask.getCourseName(), paperScanTask.getCourseCode());
        String cutRootPath = fileStoreUtil.buildPath(UploadFileEnum.CUT, true, basicSchool.getCode(), courseNameCode);
        // 裁剪识别区图片
        String fileName = file.getOriginalFilename();
        String prefixFileName = fileName.substring(0, fileName.lastIndexOf("."));
        String suffix = fileName.substring(fileName.lastIndexOf("."));
        String nanoId = SystemConstant.getNanoId();
        File outFile = new File(cutRootPath, "original" + File.separator + prefixFileName + "-" + nanoId + suffix);
        if (!outFile.exists()) {
            outFile.getParentFile().mkdirs();
        }
        File tempFile = SystemConstant.getFileTempDirVar(suffix);
        try {
            FileUtils.copyInputStreamToFile(file.getInputStream(), tempFile);
            ImageUtil.subImg(tempFile, x, y, width, height, outFile);
            if (outFile == null) {
                return recognitionResultVo;
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            if (tempFile.exists()) {
                tempFile.delete();
            }
        }

        // 旋转
        File rotateOutFile = new File(cutRootPath, "rotate" + File.separator + prefixFileName + "-" + nanoId + suffix);
        File rotateFile = ImageUtil.rotate(outFile, rotate, rotateOutFile);

        List<String> stringList = new ArrayList<>();
        String wordsResult = null;
        String result = studentCode;
        try {
            if (StringUtils.isBlank(studentCode)) {
                Map<String, SimpleObject> sysSetting = commonCacheService.getSysSetting(basicSchool.getId());
                String type = sysSetting.get(SysSettingConstant.RECOGNITION_TYPE).getValue();
                if (RecognitionTypeEnum.OCR.equals(type)) {
                    org.json.JSONObject callHandwriting;
                    if (rotateFile.exists()) {
                        callHandwriting = ocrUtil.callHandwriting(rotateFile.getPath());
                    } else {
                        callHandwriting = ocrUtil.callHandwriting(outFile.getPath());
                    }

                    // 未联网提示
                    if (!callHandwriting.has(SystemConstant.OCR_WORDS_RESULT)) {
                        // ocr错误提示
                        if (callHandwriting.has(SystemConstant.OCR_ERROR_MSG)) {
                            throw ExceptionResultEnum.ERROR.exception(callHandwriting.getString(SystemConstant.OCR_ERROR_MSG));
                        } else {
                            throw ExceptionResultEnum.ERROR.exception("OCR识别异常，请检查网络是否连接或者关闭OCR识别");
                        }
                    }

                    JSONArray jsonArray = callHandwriting.getJSONArray(SystemConstant.OCR_WORDS_RESULT);
                    if (jsonArray.length() == 0) {
                        throw ExceptionResultEnum.ERROR.exception("未正确识别出内容");
                    } else {
                        jsonArray.forEach(m -> {
                            org.json.JSONObject object = (org.json.JSONObject) m;
                            if (object.has(SystemConstant.OCR_WORDS)) {
                                stringList.add(object.getString(SystemConstant.OCR_WORDS));
                            }
                        });
                        wordsResult = String.join(",", stringList);

                        //自动绑定，默认返回第一条
                        result = stringList.get(0);
                    }
                } else if (RecognitionTypeEnum.BAR_CODE.equals(type)) {
                    if (rotateFile.exists()) {
                        result = BarCodeUtils.decode(rotateFile);
                    } else {
                        result = BarCodeUtils.decode(outFile);
                    }
                    wordsResult = result;
                } else {
                    throw ExceptionResultEnum.ERROR.exception("不支持的识别方式");
                }
            }
        } catch (Exception e) {
            wordsResult = e.getMessage();
            throw ExceptionResultEnum.ERROR.exception("识别异常：" + e.getMessage());
        } finally {
            recognitionResultVo.setBindResult(result);
            recognitionResultVo.setRecognizeResult(wordsResult);

            if (rotateOutFile.exists()) {
                rotateOutFile.delete();
            }
            if (rotateFile.exists()) {
                rotateFile.delete();
            }
        }
        return recognitionResultVo;
    }

    @Override
    public List<Map> listMaxSequenceByPaperScanTaskId(Long paperScanTaskId) {
        return this.baseMapper.listMaxSequenceByPaperScanTaskId(paperScanTaskId);
    }

    @Override
    public void removeByPaperScanTaskId(Long paperScanTaskId) {
        QueryWrapper<PaperLibrary> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda().eq(PaperLibrary::getPaperScanTaskId, paperScanTaskId);
        this.remove(queryWrapper);
    }

    @Transactional
    @Override
    public boolean rebind(Long paperLibraryId, Long studentId) {
        PaperLibrary paperLibrary = this.getById(paperLibraryId);
        if (paperLibrary == null) {
            throw ExceptionResultEnum.ERROR.exception("图片数据不存在");
        }
        ExamStudent student = examStudentService.getById(studentId);
        PaperScanTask task = paperScanTaskService.getByExamIdAndCourseCode(student.getExamId(), student.getCourseCode());
        if (Objects.isNull(task)) {
            throw ExceptionResultEnum.ERROR.exception("考生找不到扫描任务");
        }

        // 自己绑定自己，直接返回
        if (paperLibrary.getStudentId() != null && paperLibrary.getStudentId().equals(studentId)) {
            return true;
        }
        PathSequenceVo pathSequenceVo = null;
        try {
            // 将图片复制到学号目录下，并删除原图片
            pathSequenceVo = copyImageToStudentCode(paperLibrary, task, studentId, UploadFileEnum.IMAGE);
        } catch (Exception e) {
            log.error("纠错操作复制图片失败");
            throw ExceptionResultEnum.ERROR.exception("纠错失败");
        }

        // 更新绑定考生ID
        UpdateWrapper<PaperLibrary> updateWrapper = new UpdateWrapper<>();
        LambdaUpdateWrapper<PaperLibrary> lambda = updateWrapper.lambda();
        lambda.set(PaperLibrary::getStudentId, studentId);
        lambda.set(PaperLibrary::getPaperScanTaskId, task.getId());
        if (pathSequenceVo != null) {
            lambda.set(PaperLibrary::getPath, pathSequenceVo.getPath());
            lambda.set(PaperLibrary::getSequence, pathSequenceVo.getSequence());
        }
        lambda.eq(PaperLibrary::getId, paperLibraryId);
        this.update(updateWrapper);
        // 更新考生绑定数量
        if (paperLibrary.getStudentId() != null) {
            examStudentService.updateBindCount(paperLibrary.getStudentId());
            // 清除pdf文件记录
            examStudentService.clearPdfFilePath(paperLibrary.getStudentId());
        }
        examStudentService.updateBindCount(studentId);
        // 清除pdf文件记录
        examStudentService.clearPdfFilePath(studentId);

        //更新扫描任务扫描考生数量、扫描图片数量
        paperScanTaskService.updateScanCount(paperLibrary.getPaperScanTaskId());
        if(!task.getId().equals(paperLibrary.getPaperScanTaskId())) {
            paperScanTaskService.updateScanCount(task.getId());
        }
        return true;
    }

    private PathSequenceVo copyImageToStudentCode(PaperLibrary paperLibrary, PaperScanTask task, Long studentId, UploadFileEnum uploadFileEnum) {
        PaperScanTask paperScanTask = paperScanTaskService.getById(task.getId());
        BasicSchool basicSchool = basicSchoolService.getById(paperScanTask.getSchoolId());
        BasicExam basicExam = basicExamService.getById(paperScanTask.getExamId());
        BasicSemester basicSemester = basicSemesterService.getById(basicExam.getSemesterId());
        String courseNameCode = String.format("%s(%s)", paperScanTask.getCourseName(), paperScanTask.getCourseCode());
        ExamStudent examStudent = examStudentService.getById(studentId);
        String rootPath = fileStoreUtil.buildPath(UploadFileEnum.IMAGE, false, basicSchool.getName(), basicSemester.getName(), basicExam.getName(), courseNameCode);
        if (StoreTypeEnum.ROOM.equals(paperScanTask.getStoreType())) {
            rootPath = fileStoreUtil.buildPath(UploadFileEnum.IMAGE, false, basicSchool.getName(), basicSemester.getName(), basicExam.getName(), courseNameCode, examStudent.getExamRoom());
        } else if (StoreTypeEnum.CLASS.equals(paperScanTask.getStoreType())) {
            rootPath = fileStoreUtil.buildPath(UploadFileEnum.IMAGE, false, basicSchool.getName(), basicSemester.getName(), basicExam.getName(), courseNameCode, examStudent.getClassName());
        } else if (StoreTypeEnum.COURSE.equals(paperScanTask.getStoreType())) {
            rootPath = fileStoreUtil.buildPath(UploadFileEnum.IMAGE, false, basicSchool.getName(), basicSemester.getName(), basicExam.getName(), courseNameCode, examStudent.getStudentCode());
        }
        // 根目录
        boolean isAllCopy = true;
        List<FilePathVo> objects = new ArrayList<>();
//        int sequence = businessCacheService.getSequence(paperScanTask.getId(), examStudent.getStudentCode());
        int sequence = getStudentMaxSequence(paperScanTask.getId(), null, studentId);
        String sequenceStr = String.valueOf(sequence + 1);
        try {
            int idx = 1;
            String prefix = examStudent.getStudentCode() + "-" + sequenceStr;
            List<FilePathVo> filePathVoList = JSON.parseArray(paperLibrary.getPath(), FilePathVo.class);
            for (FilePathVo filePath : filePathVoList) {
                String suffix = filePath.getPath().substring(filePath.getPath().lastIndexOf("."));
                // 目标文件名
                String dirName = rootPath + File.separator + prefix + "-" + idx + suffix;
                File file = new File(dirName);
                if (!file.exists()) {
                    file.getParentFile().mkdirs();
                    file.createNewFile();
                }
                boolean success = fileUploadService.copy(filePath.getPath(), dirName);
                if (isAllCopy) {
                    isAllCopy = success;
                }
                objects.add(new FilePathVo(dirName, uploadFileEnum, filePath.getType(), filePath.getMd5()));
                idx++;
            }
            if (isAllCopy) {
                for (FilePathVo filePathVo : filePathVoList) {
                    fileUploadService.deleteFile(filePathVo.getPath());
                }
            }
        } catch (Exception e) {
            log.error(SystemConstant.LOG_ERROR, e);
            if (e instanceof ApiException) {
                ResultUtil.error((ApiException) e, e.getMessage());
            } else {
                ResultUtil.error(e.getMessage());
            }
        }
        return isAllCopy ? new PathSequenceVo(JSON.toJSONString(objects), sequence+1) : null;
    }

    @Override
    public int countByStudentId(Long studentId) {
        QueryWrapper<PaperLibrary> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda().eq(PaperLibrary::getStudentId, studentId);
        return this.count(queryWrapper);
    }

    @Override
    public int countByPaperScanTaskId(Long paperScanTaskId) {
        QueryWrapper<PaperLibrary> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda().eq(PaperLibrary::getPaperScanTaskId, paperScanTaskId);
        return this.count(queryWrapper);
    }

    @Override
    public int countScanStudentByScannerAndDate(Long schoolId, Long createId, String scanDate) {
        return baseMapper.countScanStudentByScannerAndDate(schoolId, createId, scanDate);
    }

    @Override
    public List<String> listBatchNo(Long studentId) {
        List<String> list = baseMapper.listBatchNo(studentId);
        return list.stream().filter(m -> StringUtils.isNotBlank(m)).collect(Collectors.toList());
    }

    @Transactional
    @Override
    public boolean deletePicture(List<Long> paperLibraryIds) {
        List<PaperLibrary> paperLibraryList = this.listByIds(paperLibraryIds);
        if (CollectionUtils.isEmpty(paperLibraryList)) {
            throw ExceptionResultEnum.ERROR.exception("图片数据不存在");
        }

        // 删除后，重新统计绑定数量
        if (this.removeByIds(paperLibraryIds)) {
            Set<Long> studentIds = paperLibraryList.stream().filter(m->m.getStudentId() != null).map(PaperLibrary::getStudentId).collect(Collectors.toSet());
            for (Long studentId : studentIds) {
                examStudentService.updateBindCount(studentId);
                // 清除pdf文件记录
                examStudentService.clearPdfFilePath(studentId);
            }
        }
        // 更新扫描任务扫描考生数、扫描图片数
        Set<Long> paperScanTaskIds = paperLibraryList.stream().map(PaperLibrary::getPaperScanTaskId).collect(Collectors.toSet());
        for (Long paperScanTaskId : paperScanTaskIds) {
            paperScanTaskService.updateScanCount(paperScanTaskId);
        }

        //删除图片
        for(PaperLibrary paperLibrary : paperLibraryList) {
            List<FilePathVo> filePathVoList = JSON.parseArray(paperLibrary.getPath(), FilePathVo.class);
            for(FilePathVo filePathVo : filePathVoList) {
                boolean deleted = fileUploadService.deleteFile(filePathVo.getPath());
                if (!deleted) {
                    log.warn("[删除图片] 删除图片失败,路径:" + filePathVo.getPath());
                }
            }
        }
        return true;
    }

    @Override
    public boolean rotatePictureUpload(Long paperLibraryId, boolean isFront, Integer rotate) {
        File outputFile = null;
        try {
            lockService.waitlock(LockType.ROTATE_PICTURE, paperLibraryId, isFront);
            PaperLibrary paperLibrary = this.getById(paperLibraryId);
            if (paperLibrary == null) {
                throw ExceptionResultEnum.ERROR.exception("图片数据不存在");
            }
            List<FilePathVo> filePathVoList = JSON.parseArray(paperLibrary.getPath(), FilePathVo.class);
            if (CollectionUtils.isEmpty(filePathVoList) || filePathVoList.size() != 2) {
                throw ExceptionResultEnum.ERROR.exception("图片路径数据错误");
            }
            // 取图片路径
            FilePathVo filePathVo = filePathVoList.get(isFront ? 0 : 1);
            // 旋转图片，并重新上传（文件名不变）
            outputFile = SystemConstant.getFileTempVar(filePathVo.getPath().substring(filePathVo.getPath().lastIndexOf(SystemConstant.ORG_POINT)));
            log.warn("[图片旋转]临时文件路径:" + outputFile.getAbsolutePath());
            ImageUtil.rotate(fileUploadService.downloadInputStream(filePathVo.getPath(), filePathVo.getType()), rotate, outputFile);
            if (outputFile.exists()) {
                fileUploadService.uploadRotateFile(outputFile, filePathVo.getUploadType(), filePathVo.getPath());
            }
            return true;
        } catch (Exception e) {
            throw ExceptionResultEnum.ERROR.exception("旋转图片保存失败：" + e.getMessage());
        } finally {
            if (outputFile != null && outputFile.exists()) {
                boolean deleted = outputFile.delete();
                if (!deleted) {
                    log.error("[图片旋转]临时文件删除失败!" + outputFile.getAbsolutePath());
                }
            }
            lockService.unlock(LockType.ROTATE_PICTURE, paperLibraryId, isFront);
        }
    }

    @Override
    public Map<String, Object> pdfGenerate(DownLoadPaperParams params) {
        SysUser user = (SysUser) ServletUtil.getRequestUser();
        boolean lockFlag = lockService.trylock(LockType.STUDENT_PDF_GENERATE, user.getId());
        if(!lockFlag) {
            throw ExceptionResultEnum.ERROR.exception("已经在生成，在生成完成之前，不要重复生成");
        }
        String remark = JSON.toJSONString(params);
        Map<String, Object> map = tbTaskService.saveTask(TaskTypeEnum.STUDENT_PDF_GENERATE, remark, (SysUser) ServletUtil.getRequestUser());
        asyncGeneratePdfService.exportTask(map);
        return map;
    }

    @Override
    public int getStudentMaxSequence(Long paperScanTaskId, String batchNo, Long studentId) {
        return  baseMapper.getStudentMaxSequence(paperScanTaskId, batchNo, studentId);
    }

}
