package com.yida.data.user.service.impl;

import cc.mrbird.febs.common.redis.service.RedisService;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.IdUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yida.data.common.core.common.ResultBean;
import com.yida.data.common.core.common.ResultMsgType;
import com.yida.data.common.core.common.ResultStatusType;
import com.yida.data.common.core.entity.CurrentUser;
import com.yida.data.common.core.entity.constant.FebsConstant;
import com.yida.data.common.core.entity.school.EduSchoolStudentInformation;
import com.yida.data.common.core.entity.user.EduUserDept;
import com.yida.data.common.core.enums.ImportTemplateTypeEnum;
import com.yida.data.common.core.enums.ReportStatusEnum;
import com.yida.data.common.core.utils.EasyExcelUtil;
import com.yida.data.common.core.utils.FileUtil;
import com.yida.data.common.core.utils.RedisUtil;
import com.yida.data.school.dto.news.SysNoticeSaveDTO;
import com.yida.data.school.feign.news.RemoteNoticeSystemService;
import com.yida.data.user.dto.*;
import com.yida.data.user.excel.WelcomeInviteDataExcelListener;
import com.yida.data.user.excel.WelcomeStudentDataExcelListener;
import com.yida.data.user.mapper.EduSchoolStudentInformationMapper;
import com.yida.data.user.service.EduSchoolStudentInformationService;
import com.yida.data.user.service.EduUserDeptService;
import com.yida.data.user.service.ImportWelcomeStudentService;
import com.yida.data.user.vo.CheckStudentInformationReturnVO;
import com.yida.data.user.vo.EduSchoolStudentInformationVo;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.ehcache.impl.internal.concurrent.ConcurrentHashMap;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StopWatch;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.InputStream;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;

/**
 * 学校智慧迎新-学生报到信息service层
 *
 * @author ZYJ
 * @date 2021/8/20
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class EduSchoolStudentInformationServiceImpl extends
    ServiceImpl<EduSchoolStudentInformationMapper, EduSchoolStudentInformation>
    implements EduSchoolStudentInformationService {

    private final RedisService redisService;
    private final RemoteNoticeSystemService remoteNoticeSystemService;

    private final ImportWelcomeStudentService importWelcomeStudentService;
    private final EduUserDeptService userDeptService;

    @Value("${febs.uploadUrl}")
    private String uploadUrl;

    /**
     * 验证提交的学生信息
     *
     * @param checkStudentInformationDTO 验证学生信息请求类
     * @return com.yida.data.common.core.common.ResultBean<java.util.List < com.yida.data.school.vo.CheckStudentInformationReturnVO>>
     * @author ZYJ
     * @date 2021/8/19 19:47
     */
    @Override
    @Transactional(readOnly = true, rollbackFor = Exception.class)
    public ResultBean<List<CheckStudentInformationReturnVO>> checkStudentInformation(
        CheckStudentInformationDTO checkStudentInformationDTO) {
        // 返回的验证错误信息
        StringBuilder returnMessage = new StringBuilder();
        // 验证成功的学生返回数据
        List<CheckStudentInformationReturnVO> list = new ArrayList<>();
        // 循环验证学生信息是否正确，如果不正确则返回错误信息
        for (CheckStudentInformationDTO.StudentInformationDTO studentInformationDTO : checkStudentInformationDTO.getList()) {
            // 调用验证学生信息方法
            EduSchoolStudentInformation eduSchoolStudentInformation = this
                .getOne(Wrappers.lambdaQuery(new EduSchoolStudentInformation())
                    .eq(EduSchoolStudentInformation::getStudentName, studentInformationDTO.getStudentName())
                    .eq(EduSchoolStudentInformation::getStudentIdCard, studentInformationDTO.getStudentIdCard())
                    .eq(EduSchoolStudentInformation::getSchoolId, checkStudentInformationDTO.getSchoolId()));
            if (!Optional.ofNullable(eduSchoolStudentInformation).isPresent()) {
                returnMessage.append("学生: ")
                    .append(studentInformationDTO.getStudentName())
                    .append("信息校验错误！请检查后再次验证");
                break;
            }
            list.add(CheckStudentInformationReturnVO.builder()
                .studentId(eduSchoolStudentInformation.getId())
                .studentName(eduSchoolStudentInformation.getStudentName())
                .studentIdCard(eduSchoolStudentInformation.getStudentIdCard())
                .schoolName(eduSchoolStudentInformation.getSchoolName())
                .campusName(eduSchoolStudentInformation.getCampusName())
                .sectionName(eduSchoolStudentInformation.getSectionName())
                .gradeName(eduSchoolStudentInformation.getGradeName())
                .className(eduSchoolStudentInformation.getClassName())
                .build());
        }
        // 判断是否有验证错误的学生信息
        if (StringUtils.isNotBlank(returnMessage.toString())) {
            return ResultBean.buildError(returnMessage.toString());
        }
        return ResultBean.buildSuccess(list);
    }

    /***
     * @Author jianMingWang
     * @Description //分页查询学生信息
     * @Date 17:12 2021/8/21
     * @Param [page, campusId, gradeId, classId, studentName, studentIdCard]
     * @return com.baomidou.mybatisplus.core.metadata.IPage<com.yida.data.school.vo.EduSchoolStudentInformationVo>
     **/
    @Override
    public IPage<EduSchoolStudentInformationVo> listStudentInfo(Page page, Integer schoolId, Integer campusId, Integer sectionId,
        Integer gradeId, Integer classId, String studentName,
        String studentIdCard, Integer reportStatus) {
        return baseMapper.listStudentInfo(page, schoolId, campusId, sectionId, gradeId,
            classId, studentName, studentIdCard, reportStatus);
    }

    /**
     * 导入迎新学生数据
     *
     * @param file      导入的excel文件
     * @param schoolId  学校id
     * @param timestamp 时间戳
     * @return com.yida.data.common.core.common.ResultBean<org.ehcache.impl.internal.concurrent.ConcurrentHashMap <
        * java.lang.Integer, java.lang.Object>>
     * @throws Exception 导入迎新学生数据抛出的异常
     * @author ZYJ
     * @date 2021/8/23 16:10
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public ResultBean<ConcurrentHashMap<Integer, Object>> importWelcomeStudentDataExcel(
        MultipartFile file, Long schoolId, long timestamp)
        throws Exception {
        // 验证文件类型
        if (!FileUtil.checkMultipartFile(file, FebsConstant.EXCEL_FILE_TYPE)) {
            return ResultBean.buildError(ResultMsgType.FILE_TYPE_ERROR.getValue());
        }
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        // 调用导入方法
        ConcurrentHashMap<Integer, Object> errorMap = new ConcurrentHashMap<>(16);
        EasyExcel.read(file.getInputStream(), WelcomeStudentImportDTO.class,
            new WelcomeStudentDataExcelListener(importWelcomeStudentService, errorMap, schoolId, timestamp))
            .sheet().headRowNumber(1).doRead();
        stopWatch.stop();
        log.info("导入迎新学生数据总共耗时: {}", stopWatch.getTotalTimeSeconds() + "秒");
        // 判断是否有错误数据
        if (errorMap.size() > 0) {
            return ResultBean
                .buildError(ResultStatusType.IMPORT_FAIL.getValue(), ResultMsgType.IMPORT_EXCEL_FAIL.getValue(), errorMap);
        }
        return ResultBean.buildSuccess(ResultMsgType.IMPORT_EXCEL_SUCCESS.getValue(), null);
    }

    /**
     * 根据查询条件导出迎新学生数据
     *
     * @param schoolId      学校id
     * @param campusId      学区id
     * @param sectionId     学段id
     * @param gradeId       年级id
     * @param classId       班级id
     * @param studentName   学生姓名
     * @param studentIdCard 学生身份证号码
     * @param reportStatus  报到状态 {@link ReportStatusEnum}
     * @param response      response返回类
     * @author ZYJ
     * @date 2021/8/24 10:55
     */
    @Override
    @Transactional(readOnly = true, rollbackFor = Exception.class)
    public void exportWelcomeStudentData(Integer schoolId, Integer campusId, Integer sectionId,
        Integer gradeId, Integer classId, String studentName,
        String studentIdCard, Integer reportStatus, HttpServletResponse response) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        // 根据查询条件导出迎新学生数据
        List<WelcomeStudentExportDTO> list = this.baseMapper.exportWelcomeStudentData(schoolId, campusId, sectionId,
            gradeId, classId, studentName, studentIdCard, reportStatus);
        // 导出Excel
        EasyExcelUtil.exportExcelData(list, WelcomeStudentExportDTO.class, "智慧迎新导出数据",
            ExcelTypeEnum.XLS, response);
        stopWatch.stop();
        log.info("导出智慧迎新学生数据总共耗时: {}", stopWatch.getTotalTimeSeconds() + "秒");
    }

    /**
     * 下载智慧迎新相关导入模板
     *
     * @param type     模板类型 {@link ImportTemplateTypeEnum}
     * @param response response返回类
     * @throws Exception 导出模板异常信息
     * @author ZYJ
     * @date 2021/8/24 16:16
     */
    @Override
    public void downloadTemplate(Integer type, HttpServletResponse response)
        throws Exception {
        String fileName =
            type.equals(ImportTemplateTypeEnum.WELCOME_STUDENT_TEMPLATE.getType()) ? "智慧迎新数据导入模板.xls" : "学生基础信息导入模板.xls";
        // 获取导入文件模板
        ClassPathResource classPathResource = new ClassPathResource("template/" + fileName);
        InputStream inputStream = classPathResource.getInputStream();
        FileUtil.download(FileUtil.inputStreamToFile(inputStream), fileName, false, response);
    }

    /**
     * 导入学生基础信息
     *
     * @param fileUrl     导入的excel文件地址
     * @param schoolId    学校id
     * @param currentUser 当前登录用户信息
     * @author ZYJ
     * @date 2021/8/24 17:16
     */
    @Override
    public void importWelcomeInviteData(String fileUrl, Long schoolId, CurrentUser currentUser) {
        // 当前导入数据唯一标识
        String uuid = IdUtil.simpleUUID();
        try {
            StopWatch stopWatch = new StopWatch();
            stopWatch.start();
            // 生成智慧迎新部门缓存数据
            getDeptInfo(schoolId, uuid);
            // 调用导入方法
            List<StudentErrorExportDTO> errorList = new ArrayList<>();
            EasyExcel.read(new File(fileUrl), WelcomeInviteImportDTO.class,
                new WelcomeInviteDataExcelListener(importWelcomeStudentService, errorList, schoolId, uuid, currentUser))
                .sheet().headRowNumber(11).doRead();
            stopWatch.stop();
            log.info("导入学生数据总共耗时: {}", stopWatch.getTotalTimeSeconds() + "秒");
            // 判断是否有错误数据
            if (CollUtil.isNotEmpty(errorList)) {
                // 错误信息文件名称
                String fileName = FileUtil.getLocalUploadAddress() + uuid + ".xlsx";
                EasyExcel.write(fileName, StudentErrorExportDTO.class)
                    .sheet("错误信息")
                    .doWrite(errorList);
                // 上传到媒资
                String url = FileUtil.uploadFileToMediaServer(uploadUrl, new File(fileName));
                // 保存导入不成功的数据和原因
                remoteNoticeSystemService.sendNotice(
                    SysNoticeSaveDTO.builder()
                        .userId(Collections.singletonList(currentUser.getUserId()))
                        .content("学生信息部分数据导入失败. 详情信息请点击下载  <a style='color: blue' href='" + url + "'>错误信息.xlsx</a>")
                        .createDate(LocalDateTime.now())
                        .build());
            } else {
                remoteNoticeSystemService.sendNotice(
                    SysNoticeSaveDTO.builder()
                        .userId(Collections.singletonList(currentUser.getUserId()))
                        .content("学生信息导入成功")
                        .createDate(LocalDateTime.now())
                        .build());
            }
        } catch (Exception e) {
            log.error("导入一键邀请数据失败: {}", e.getMessage(), e);
            remoteNoticeSystemService.sendNotice(
                SysNoticeSaveDTO.builder()
                    .userId(Collections.singletonList(currentUser.getUserId()))
                    .content("学生信息导入失败")
                    .createDate(LocalDateTime.now())
                    .build());
        } finally {
            // 清空当前导入的redis学校数据
            redisService.del(RedisUtil.getDeptKey(uuid));
        }
    }

    /**
     * 根据学校id查询学校下的部门数据
     *
     * @param schoolId 学校id
     * @param str      某次导入的唯一标识
     * @author ZYJ
     * @date 2021/8/23 19:15
     */
    @Override
    public void getDeptInfo(Long schoolId, String str) {
        // redis导入key值
        String redisKey = RedisUtil.getDeptKey(str);
        // 查询学区
        List<EduUserDept> campusResult = userDeptService.findChildByParent(-schoolId);
        if (!CollectionUtils.isEmpty(campusResult)) {
            // 循环生成校区缓存数据并查询下级数据
            campusResult.forEach(campus -> {
                // 生成学校下学区缓存
                redisService
                    .hset(redisKey, RedisUtil.getDeptRedisKey(campus.getDeptName(), campus.getDeptType(), schoolId, schoolId),
                        campus);
                // 查询学段
                List<EduUserDept> sectionResult = userDeptService.findChildByParent(campus.getDeptId());
                if (!CollectionUtils.isEmpty(sectionResult)) {
                    // 循环生成学段缓存数据并查询下级数据
                    sectionResult.forEach(section -> {
                        // 生成学校下学段缓存
                        redisService.hset(redisKey,
                            RedisUtil.getDeptRedisKey(section.getDeptName(), section.getDeptType(), schoolId,
                                campus.getDeptId()),
                            section);
                        // 查询年级
                        List<EduUserDept> gradeResult = userDeptService.findChildByParent(section.getDeptId());
                        if (!CollectionUtils.isEmpty(gradeResult)) {
                            // 循环生成年级缓存数据并查询下级数据
                            gradeResult.forEach(grade -> {
                                // 生成学校下年级缓存
                                redisService
                                    .hset(redisKey, RedisUtil.getDeptRedisKey(grade.getDeptName(), grade.getDeptType(), schoolId,
                                        section.getDeptId()), grade);
                                // 查询班级
                                List<EduUserDept> classResult = userDeptService.findChildByParent(grade.getDeptId());
                                if (!CollectionUtils.isEmpty(classResult)) {
                                    // 生成学校下班级缓存
                                    classResult.forEach(classDept -> redisService.hset(redisKey,
                                        RedisUtil.getDeptRedisKey(classDept.getDeptName(),
                                            classDept.getDeptType(), schoolId, grade.getDeptId()),
                                        classDept));
                                }
                            });
                        }
                    });
                }
            });
        }
    }
}
