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

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
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.core.collection.PageResult;
import com.qmth.boot.core.exception.StatusException;
import com.qmth.boot.core.fss.store.FileStore;
import com.qmth.exam.reserve.bean.Constants;
import com.qmth.exam.reserve.bean.applytask.CurrentApplyTaskVO;
import com.qmth.exam.reserve.bean.login.LoginUser;
import com.qmth.exam.reserve.bean.org.OrgInfo;
import com.qmth.exam.reserve.bean.stdapply.CategoryVO;
import com.qmth.exam.reserve.bean.student.StudentInfo;
import com.qmth.exam.reserve.bean.student.StudentReq;
import com.qmth.exam.reserve.bean.student.StudentVO;
import com.qmth.exam.reserve.bean.student.WechatBindReq;
import com.qmth.exam.reserve.cache.impl.ApplyTaskCacheService;
import com.qmth.exam.reserve.cache.impl.CategoryCacheService;
import com.qmth.exam.reserve.cache.impl.OrgCacheService;
import com.qmth.exam.reserve.config.SysProperty;
import com.qmth.exam.reserve.dao.StudentDao;
import com.qmth.exam.reserve.entity.ApplyTaskEntity;
import com.qmth.exam.reserve.entity.StudentApplyEntity;
import com.qmth.exam.reserve.entity.StudentCourseEntity;
import com.qmth.exam.reserve.entity.StudentEntity;
import com.qmth.exam.reserve.enums.AsyncTaskType;
import com.qmth.exam.reserve.enums.EventType;
import com.qmth.exam.reserve.enums.FileUploadType;
import com.qmth.exam.reserve.enums.Role;
import com.qmth.exam.reserve.service.*;
import com.qmth.exam.reserve.template.execute.StudentPhotoUploadService;
import com.qmth.exam.reserve.util.FileUtil;
import com.qmth.exam.reserve.util.PageUtil;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.compress.utils.FileNameUtils;
import org.apache.commons.lang3.StringUtils;
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 org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.StringJoiner;
import java.util.stream.Collectors;

@Service
public class StudentServiceImpl extends ServiceImpl<StudentDao, StudentEntity> implements StudentService {

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

    @Autowired
    private CategoryCacheService categoryCacheService;

    @Autowired
    private StudentApplyService studentApplyService;

    @Autowired
    private OperateLogService operateLogService;

    @Autowired
    private ApplyTaskService applyTaskService;

    @Autowired
    private FileStore fileStore;

    @Autowired
    private AsyncTaskService asyncTaskService;

    @Autowired
    private StudentPhotoUploadService studentPhotoUploadService;

    @Autowired
    private ApplyTaskCacheService applyTaskCacheService;

    @Autowired
    private SysProperty sysProperty;

    @Autowired
    private StudentCourseService studentCourseService;

    @Autowired
    private OrgCacheService orgCacheService;

    @Override
    public StudentEntity findByStudentCode(Long applyTaskId, String studentCode) {
        if (StringUtils.isEmpty(studentCode)) {
            throw new StatusException("学号不能为空");
        }
        if (applyTaskId == null) {
            throw new StatusException("预约任务ID不能为空");
        }

        LambdaQueryWrapper<StudentEntity> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(StudentEntity::getStudentCode, studentCode);
        wrapper.eq(StudentEntity::getApplyTaskId, applyTaskId);

        return baseMapper.selectOne(wrapper);
    }

    @Override
    public StudentEntity findByOpenIdAndUid(String openId, String uid) {
        if (StringUtils.isEmpty(openId)) {
            throw new StatusException("微信OID不能为空");
        }

        LambdaQueryWrapper<StudentEntity> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(StudentEntity::getOpenId, openId);
        if (StringUtils.isNotEmpty(uid)) {
            wrapper.eq(StudentEntity::getUid, uid);
        }

        return baseMapper.selectOne(wrapper);
    }

    @Override
    public int findStudentApplyNumberById(Long studentId) {
        // 只查询applyNumber等少量字段
        LambdaQueryWrapper<StudentEntity> wrapper = new LambdaQueryWrapper<>();
        wrapper.select(StudentEntity::getApplyNumber);
        wrapper.eq(StudentEntity::getId, studentId);
        StudentEntity stu = this.getOne(wrapper);
        if (stu == null) {
            log.warn("学生信息不存在！studentId:{}", studentId);
            throw new StatusException("学生信息不存在");
        }
        return stu.getApplyNumber();
    }

    @Override
    public StudentEntity findLessInfoByStudentId(Long studentId) {
        // 只查询id、name等少量字段
        LambdaQueryWrapper<StudentEntity> wrapper = new LambdaQueryWrapper<>();
        wrapper.select(StudentEntity::getId, StudentEntity::getName);
        wrapper.eq(StudentEntity::getId, studentId);
        return this.getOne(wrapper);
    }

    @Override
    public StudentInfo findInfoByStudentId(Long studentId) {
        StudentInfo info = baseMapper.findInfoById(studentId);
        if (info == null) {
            throw new StatusException("学生信息不存在");
        }

        return info;
    }

    @Transactional
    @Override
    public void bindingWechat(WechatBindReq req) {
        StudentEntity stu = baseMapper.selectById(req.getStudentId());
        if (stu == null) {
            throw new StatusException("学生信息不存在");
        }

        if (StringUtils.isNotEmpty(stu.getOpenId())) {
            throw new StatusException("微信账号已绑定");
        }

        if (StringUtils.isEmpty(req.getOpenId())) {
            throw new StatusException("微信OID不能为空");
        }

        LambdaUpdateWrapper<StudentEntity> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.set(StudentEntity::getOpenId, req.getOpenId());
        updateWrapper.set(StudentEntity::getUid, req.getUid());
        updateWrapper.eq(StudentEntity::getId, req.getStudentId());
        this.update(updateWrapper);
        log.info("[WECHAT_BINDING] studentId:{} openId:{} uid:{}", req.getStudentId(), req.getOpenId(), req.getUid());
    }

    @Transactional
    @Override
    public void unbindWechatByStudentId(Long studentId) {
        StudentEntity stu = baseMapper.selectById(studentId);
        if (stu == null) {
            throw new StatusException("学生信息不存在");
        }

        if (StringUtils.isEmpty(stu.getOpenId())) {
            return;
        }

        LambdaUpdateWrapper<StudentEntity> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.set(StudentEntity::getOpenId, null);
        updateWrapper.set(StudentEntity::getUid, null);
        updateWrapper.eq(StudentEntity::getId, studentId);
        this.update(updateWrapper);
        log.info("[WECHAT_UNBIND] studentId:{} openId:{} uid:{}", stu.getId(), stu.getOpenId(), stu.getUid());
    }

    @Override
    public List<StudentEntity> listNoFinishStudent(Long taskId, Boolean cancel) {
        return this.baseMapper.listNoFinishStudent(taskId, cancel);
    }

    @Override
    public List<CategoryVO> listNoFinishCategory(Long taskId, Boolean cancel) {
        return baseMapper.listNoFinishCategory(taskId, cancel);
    }

    @Override
    public PageResult<StudentVO> pageStudent(StudentReq req) {
        if (req.getTaskId() == null) {
            OrgInfo org = orgCacheService.currentOrg();
            CurrentApplyTaskVO curApplyTask = applyTaskCacheService.currentApplyTask(org.getOrgId());
            if (curApplyTask == null) {
                return new PageResult<>();
            } else {
                req.setTaskId(curApplyTask.getTaskId());
            }
        }

        IPage<StudentVO> iPage = baseMapper.pageStudent(new Page<>(req.getPageNumber(), req.getPageSize()), req);
        List<StudentVO> recordList = iPage.getRecords();
        for (StudentVO student : recordList) {
            //添加图片访问前缀
            student.setPhotoPath(sysProperty.getServer() + File.separator + student.getPhotoPath());
        }
        return PageUtil.of(iPage);
    }

    @Override
    @Transactional
    public void delete(Long[] studentCourseIds, LoginUser loginUser) {
        // 考生科目ID
        for(Long studentCourseId : studentCourseIds) {
            StudentCourseEntity studentCourseEntity = studentCourseService.getById(studentCourseId);
            if(studentCourseEntity == null) {
                throw new StatusException("考生科目不存在");
            }
            Long studentId = studentCourseEntity.getStudentId();
            List<StudentApplyEntity> studentApplyList = getStudentApplyList(studentId);
            if (CollectionUtils.isNotEmpty(studentApplyList)) {
                log.warn("[考生删除] 删除失败,存在预约信息,student_id:{}", studentId);
                throw new StatusException("考生存在预约信息，无法删除");
            }

            StudentEntity studentEntity = this.getById(studentId);
            if (studentEntity == null) {
                log.warn("[考生删除] 考生不存在,student_id:{}", studentId);
                throw new StatusException("考生不存在，无法删除");
            }

            //教学点管理员
            if (loginUser.getRole().equals(Role.TEACHING)) {
                Long studentCategoryId = studentEntity.getCategoryId();
                Long loginUserCategoryId = loginUser.getCategoryId();
                if (!studentCategoryId.equals(loginUserCategoryId)) {
                    log.warn("[考生删除] student_id:{}, 所在教学点{},登录用户的教学点:{} ", studentId, studentCategoryId, loginUserCategoryId);
                    throw new StatusException("不能删除其他教学点的考生科目信息");
                }
            }

            // 删除科目信息
            studentCourseService.removeById(studentCourseId);

            // 考生是否还有科目，没有科目，删除考生
            LambdaQueryWrapper<StudentCourseEntity> wrapper = new LambdaQueryWrapper<>();
            wrapper.eq(StudentCourseEntity::getStudentId, studentId);
            List<StudentCourseEntity> studentCourseList = studentCourseService.list(wrapper);
            if (CollectionUtils.isEmpty(studentCourseList)) {
                //删除考生信息
                this.removeById(studentId);
            } else {
                //更新考生的预约次数
                this.updateStudentApplyNumber(studentId);
            }

            // 清空缓存
            applyTaskCacheService.clearStudentApplyNumberCache(studentId);

            //写入日志
            operateLogService.insertOperateLog(loginUser.getId(), EventType.DELETE_STUDENT, String.join("-", studentId.toString(), studentCourseId.toString()));
        }
    }

    private List<StudentApplyEntity> getStudentApplyList(Long studentId) {
        LambdaQueryWrapper<StudentApplyEntity> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(StudentApplyEntity::getStudentId, studentId);
        wrapper.eq(StudentApplyEntity::getCancel, Boolean.FALSE);
        return studentApplyService.list(wrapper);
    }

    @Override
    public void modifyApplyNumber(Long studentId, Long operateId, Integer applyNumber) {
        if (applyNumber == null || applyNumber <= 0) {
            throw new StatusException("考生的预约次数必须大于0");
        }
        List<StudentApplyEntity> studentApplyList = getStudentApplyList(studentId);
        if (CollectionUtils.isNotEmpty(studentApplyList) && applyNumber < studentApplyList.size()) {
            throw new StatusException("考生已预约了" + studentApplyList.size() + "次,不能小于已经预约的次数");
        }

        LambdaUpdateWrapper<StudentEntity> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.eq(StudentEntity::getId, studentId);
        updateWrapper.set(StudentEntity::getApplyNumber, applyNumber);
        this.update(updateWrapper);

        //写入日志
        operateLogService.insertOperateLog(operateId, EventType.MODIFY_APPLY_NUMBER, studentId + "->" + applyNumber);
    }

    @Override
    public void uploadStudentPhotoAsync(Long operateId, MultipartFile file) throws IOException {
        ApplyTaskEntity applyTask = applyTaskService.findApplyTask();
        if (applyTask == null) {
            throw new StatusException("未开启预约任务,无法导入");
        }

        File tempFile = FileUtil.getFileTemp(".zip");
        if (tempFile != null) {
            file.transferTo(tempFile);
        }

        //写入异步任务
        Map<String, Object> taskMap = asyncTaskService.saveAsyncTask(AsyncTaskType.UPLOAD_STUDENT_PHOTO, operateId);
        taskMap.computeIfAbsent("taskId", v -> applyTask.getId());
        taskMap.computeIfAbsent("operateId", v -> operateId);
        try {
            studentPhotoUploadService.uploadTask(taskMap, tempFile);
        } catch (Exception e) {
            throw new StatusException(e.getMessage());
        }
    }

    @Override
    public void updateStudentApplyNumber(Long studentId) {
        baseMapper.updateStudentApplyNumber(studentId);
    }

    @Override
    public void uploadStudentPhoto(Long operateId, MultipartFile file) {
        //文件大小限制
        if (file.getSize() > Constants.FILE_SIZE_LIMIT) {
            throw new StatusException("上传的考生照片大小不能超过5M");
        }
        OrgInfo org = orgCacheService.currentOrg();
        CurrentApplyTaskVO curApplyTask = applyTaskCacheService.currentApplyTask(org.getOrgId());
        if (curApplyTask == null) {
            throw new StatusException("未开启预约任务,无法导入");
        }

        //照片必须以考生的身份证命名
        String identityNumber = FileNameUtils.getBaseName(file.getOriginalFilename());
        LambdaQueryWrapper<StudentEntity> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(StudentEntity::getIdentityNumber, identityNumber);
        queryWrapper.eq(StudentEntity::getApplyTaskId, curApplyTask.getTaskId());
        List<StudentEntity> studentList = this.list(queryWrapper);
        if (CollectionUtils.isEmpty(studentList)) {
            throw new StatusException("考生照片必须以考生的身份证命名");
        }

        StringJoiner stringJoiner = FileUtil.getDirName(FileUploadType.UPLOAD, true);
        String path = stringJoiner.toString().replaceAll("\\\\", "/");

        try {
            fileStore.write(path + file.getOriginalFilename(), file.getInputStream(), DigestUtils.md5Hex(file.getInputStream()));
        } catch (Exception e) {
            log.error("[上传考生头像],上传失败:{}", e.getMessage());
            throw new StatusException("考生头像上传失败,请稍后重试");
        }

        //更新考生照片路径
        List<Long> studentIds = studentList.stream().map(StudentEntity::getId).collect(Collectors.toList());
        LambdaUpdateWrapper<StudentEntity> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.set(StudentEntity::getPhotoPath, path + file.getOriginalFilename());
        updateWrapper.in(StudentEntity::getId, studentIds);
        this.update(null, updateWrapper);
    }

}
