package cn.exploring.engine.server.db.service;

import cn.exploring.engine.server.db.dao.SpecialSqlMapper;
import cn.exploring.engine.server.db.dao.StudentInfoMapper;
import cn.exploring.engine.server.db.domain.ClassInfo;
import cn.exploring.engine.server.db.domain.StudentInfo;
import cn.exploring.engine.server.db.domain.StudentInfoExample;
import cn.exploring.engine.server.db.domain.vo.StudentInfoExcelImportResultVo;
import cn.exploring.engine.server.db.domain.vo.StudentInfoExcelImportVo;
import cn.exploring.engine.server.db.domain.vo.StudentVo;
import cn.exploring.engine.server.db.util.SqlUtil;
import cn.exploring.engine.server.db.util.StrUtil;
import cn.exploring.engine.server.db.util.excel.ExcelReaderListener;
import cn.exploring.engine.server.db.util.excel.StudentInfoExcelReaderListener;
import cn.zhxu.bs.BeanSearcher;
import cn.zhxu.bs.SearchResult;
import cn.zhxu.bs.operator.*;
import cn.zhxu.bs.util.MapBuilder;
import cn.zhxu.bs.util.MapUtils;
import com.alibaba.excel.EasyExcel;
import com.alibaba.fastjson.JSON;
import com.google.common.collect.ImmutableMap;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.io.IOException;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

@Service
public class StudentInfoService {

    private static final Log log = LogFactory.getLog(StudentInfoService.class);

    @Resource
    StudentInfoMapper studentInfoMapper;

    @Resource
    ClassService classService;

    @Resource
    SpecialSqlMapper specialSqlMapper;

    @Resource
    StudentPointService studentPointService;

    @Autowired
    BeanSearcher beanSearcher;

    public static final String STRING_DEFAULT_HEAD = "{{default}}";

    public static final String URL_DEFAULT_HEAD = "https://storage.exploring.cn/class-work/static/img/head-student-default.png";

    /**
     * 添加
     * @param studentInfo
     * @return
     */
    public String addStudentInfo(StudentInfo studentInfo) {
        if (selectStudentInfoByUniqueId(studentInfo.getStudentUniqueId()) != null) {
            return "Fail 学号重复！ 请重新输入学号";
        }
        LocalDateTime now = LocalDateTime.now();

        StudentInfo record = new StudentInfo();
        BeanUtils.copyProperties(studentInfo, record,
                StudentInfo.Column.id.getJavaProperty(),
                StudentInfo.Column.callTimes.getJavaProperty(),
                StudentInfo.Column.addTime.getJavaProperty(),
                StudentInfo.Column.updateTime.getJavaProperty(),
                StudentInfo.Column.deleted.getJavaProperty(),
                "incrementColumnsInfoMap"
               );

        record.setCallTimes(0);
        record.setAddTime(now);
        record.setUpdateTime(now);
        record.setDeleted(StudentInfo.NOT_DELETED);

        if (StringUtils.isBlank(record.getHead()) || URL_DEFAULT_HEAD.equalsIgnoreCase(record.getHead())) {
            record.setHead(STRING_DEFAULT_HEAD);
        }

        studentInfoMapper.insert(record);
        return "OK 添加成功";
    }

    /**
     * 随机选取一个 学生数据
     * @param classId
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    public List<StudentVo> selectStudentInfo(Integer classId, Integer maxCall) {
        return beanSearcher.searchAll(StudentVo.class, MapUtils.builder()
                .field(StudentVo::getDeleted, StudentInfo.NOT_DELETED).op(Equal.class)
                .field(StudentVo::getClassId, classId).op(Equal.class)
                .field(StudentVo::getCallTimes, maxCall).op(LessThan.class)
                .build());
    }

    @Transactional(rollbackFor = Exception.class)
    public StudentVo randomSelectStudentInfo(Integer classId, Integer maxCall) {
        StudentVo record = beanSearcher.searchFirst(StudentVo.class, MapUtils.builder()
                .field(StudentVo::getDeleted, StudentInfo.NOT_DELETED).op(Equal.class)
                .field(StudentVo::getClassId, classId).op(Equal.class)
                .field(StudentVo::getCallTimes, maxCall).op(LessThan.class)
                .build());

        increaseCallTimeByPrimaryKey(record.getId());
        return record;
    }

   /**
     * 更新数据
     * @param studentInfo
     * @return
     */
    public String updateStudentInfo(StudentInfo studentInfo) {
        String logPrefix = StrUtil.format("[sessionId:{}]【更新学生信息】", UUID.randomUUID().toString().replace("-", ""));
        log.info(StrUtil.format("{} 请求: '{}'", logPrefix, JSON.toJSONString(studentInfo)));



        StudentInfo record = selectByPrimaryKey(studentInfo.getId());
        if (Objects.isNull(record)) {
            log.error(StrUtil.format("{} 查询失败"));
            return "记录不存在";
        }
        log.info(StrUtil.format("{} 查询: '{}'", logPrefix, JSON.toJSONString(record)));

        StudentInfo studentInfo1 = selectStudentInfoByUniqueId(studentInfo.getStudentUniqueId());
        if (studentInfo1 != null && !studentInfo1.getId().equals(studentInfo.getId())) {
            log.error(StrUtil.format("{} 学号重复  学号:'{}'  重复学生对象: '{}'",
                    logPrefix, studentInfo.getStudentUniqueId(), JSON.toJSONString(studentInfo1)));
            return "学号重复！ 请重新输入学号";
        }

        if (StringUtils.isBlank(studentInfo.getHead())
                || StringUtils.equalsIgnoreCase(studentInfo.getHead().trim(), URL_DEFAULT_HEAD)
                ||StringUtils.equalsIgnoreCase(studentInfo.getHead().trim(), STRING_DEFAULT_HEAD)) {
            studentInfo.setHead(STRING_DEFAULT_HEAD);
            log.info(StrUtil.format("{} 配置为默认头像", logPrefix));
        }

        BeanUtils.copyProperties(studentInfo, record,
                StudentInfo.Column.id.getJavaProperty(),
                StudentInfo.Column.addTime.getJavaProperty(),
                StudentInfo.Column.updateTime.getJavaProperty(),
                StudentInfo.Column.deleted.getJavaProperty());

        record.setUpdateTime(LocalDateTime.now());
        studentInfoMapper.updateByPrimaryKey(record);

        log.info(StrUtil.format("{} 更新完成: '{}'", logPrefix, JSON.toJSONString(record)));
        return null;
    }

    /**
     * 批量删除数据
     * @param idList
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    public String batchDeleted(List<Integer> idList) {
        String logPrefix = StrUtil.format("[sessionId:{}]【删除学生】", UUID.randomUUID().toString().replace("-", ""));
        log.info(StrUtil.format("{} 请求: {}", logPrefix, JSON.toJSONString(idList)));
        List<StudentInfo> studentInfoList = selectStudentInfo(null, null, idList);
        log.info(StrUtil.format("{} 查询到的学生记录: {}", logPrefix, JSON.toJSONString(studentInfoList)));

        List<Integer> notExist = idList.stream().filter(i -> studentInfoList.stream().noneMatch(s -> Objects.equals(s.getId(), i))).collect(Collectors.toList());
        if (!CollectionUtils.isEmpty(notExist)) {
            log.info(StrUtil.format("{} 存在无效学生  学生ID: {}", logPrefix, JSON.toJSONString(notExist)));
            return "学生不存在";
        }

        LocalDateTime now = LocalDateTime.now();
        int update = studentInfoMapper.batchUpsert(studentInfoList.stream().peek(x -> {
            x.setUpdateTime(now);
            x.setDeleted(null);
        }).collect(Collectors.toList()));
        log.info(StrUtil.format("{} 删除成功  条目数量: '{}'", logPrefix, update));
        return null;
    }

    /**
     * 根据标记学号进行查询
     * @param uniqueId
     * @return
     */
    public StudentInfo selectStudentInfoByUniqueId(String uniqueId) {
        StudentInfoExample example = new StudentInfoExample();
        StudentInfoExample.Criteria criteria = example.createCriteria();
        criteria.andDeletedEqualTo(false).andStudentUniqueIdEqualTo(uniqueId);
        return studentInfoMapper.selectOneByExample(example);

    }

    public List<StudentInfo> selectStudentInfoByUniqueId(Collection<String> uniqueIdList) {
        StudentInfoExample example = new StudentInfoExample();
        StudentInfoExample.Criteria criteria = example.createCriteria();
        criteria.andDeletedEqualTo(false).andStudentUniqueIdIn(new ArrayList<>(uniqueIdList));
        return studentInfoMapper.selectByExample(example);

    }

    /**
     * 检索学生相关的信息
     * @param studentName
     * @param studentId
     * @return
     */
    public List<StudentInfo> selectStudentInfo(String studentName, String studentId, List<Integer> idList) {
        StudentInfoExample example = new StudentInfoExample();
        StudentInfoExample.Criteria criteria = example.createCriteria();
        criteria.andDeletedEqualTo(false);
        if (!StringUtils.isEmpty(studentName)) {
            criteria.andNameLike("%" + studentName + "%");
        }
        if (!StringUtils.isEmpty(studentId)) {
            criteria.andStudentUniqueIdEqualTo(studentId);
        }
        if (idList != null && idList.size() > 0) {
            criteria.andIdIn(idList);
        }
        return studentInfoMapper.selectByExample(example);
    }

    public MapBuilder voWrapper(String studentName, String studentId, List<Integer> idList, Integer classId, Integer minCall, Integer maxCall, Integer page, Integer limit, String sort, String order) {
        MapBuilder wrapper = MapUtils.builder()
                .field(StudentVo::getDeleted, StudentInfo.NOT_DELETED).op(Equal.class)
                .field(StudentVo::getClassId, classId).op(Equal.class)
                .field(StudentVo::getCallTimes, minCall, maxCall).op(Between.class)
                .field(StudentVo::getName, studentName).op(Contain.class)
                .field(StudentVo::getStudentUniqueId, studentId).op(Contain.class);

        if (!CollectionUtils.isEmpty(idList) && !CollectionUtils.isEmpty(idList = idList.stream().filter(Objects::nonNull).collect(Collectors.toList()))) {
            wrapper.field(StudentVo::getId, idList).op(InList.class);
        }

        return SqlUtil.page(wrapper, page, limit, sort, order);

    }

    /**
     * 检索学生相关的信息
     * @param studentName
     * @param studentId
     * @return
     */
    public Map<String, Object> selectStudentInfoByWeb(String studentName, String studentId, List<Integer> idList, Integer classId, Integer page, Integer limit, String sort, String order, Integer minCall, Integer maxCall) {

        SearchResult<StudentVo> result = beanSearcher.search(StudentVo.class,  voWrapper(studentName, studentId, idList, classId, minCall, maxCall, page, limit, sort, order).build());

        return ImmutableMap.of("total", result.getTotalCount().longValue(), "items", updateHead(result.getDataList()));
    }

    public static <T extends StudentInfo> Collection<T> updateHead(Collection<T> list) {
        list.forEach(x -> {
            if (Objects.nonNull(x) && (StringUtils.isBlank(x.getHead()) || StringUtils.equalsIgnoreCase(x.getHead(), STRING_DEFAULT_HEAD))) {
                x.setHead(URL_DEFAULT_HEAD);
            }
        });

        return list;
    }

//    /**
//     * 查询可以被点名的学生名单
//     * @param classId   班级ID
//     * @param maxCall   排除点名达到指定次数的学生（null为不排除）
//     * @return  查询到的list和总数信息
//     */
//    @Transactional(propagation = Propagation.NOT_SUPPORTED, readOnly = true)
//    public Map<String, Object> listForCall(Integer classId, Integer maxCall) {
//        Set<Integer> excluded = null;
//        if (Objects.nonNull(maxCall) && maxCall > 0) {
//            excluded = studentPointService.countByClassGroupByStudent(classId).entrySet().stream().filter(entry -> entry.getValue() >= maxCall).map(Map.Entry::getKey).collect(Collectors.toSet());
//        }
//
//        StudentInfoExample.Criteria criteria = new StudentInfoExample().createCriteria()
//                .andDeletedEqualTo(StudentInfo.NOT_DELETED)
//                .andClassIdEqualTo(classId);
//
//        if (Objects.nonNull(maxCall) && maxCall > 0) {
//            criteria.andIdNotIn(studentPointService.countByClassGroupByStudent(classId).entrySet().stream().filter(entry -> entry.getValue() >= maxCall).map(Map.Entry::getKey).collect(Collectors.toList()));
//        }
//
//        List<StudentInfo> queryList = studentInfoMapper.selectByExample(criteria.example())
//                .stream().peek(x -> {
//                    if (StringUtils.isEmpty(x.getHead()) || x.getHead().trim().equalsIgnoreCase(STRING_DEFAULT_HEAD)) {
//                        x.setHead(URL_DEFAULT_HEAD);
//                    }
//                }).collect(Collectors.toList());
//        return ImmutableMap.of("total", queryList.size(), "items", queryList);
//    }

    public List<StudentInfoExcelImportVo> importByExcel(MultipartFile file) throws IOException {
        ExcelReaderListener<StudentInfoExcelImportVo> listener = new StudentInfoExcelReaderListener();
        EasyExcel.read(file.getInputStream(), StudentInfoExcelImportResultVo.class, listener).sheet().doRead();
        return listener.getAddList();
    }

    public List<StudentInfoExcelImportResultVo> checkImportByExcel(Collection<StudentInfoExcelImportVo> importVoList, Integer classId) {
        Set<String> studentUidSet = importVoList.stream().map(StudentInfoExcelImportVo::getStudentUid).filter(StringUtils::isNotBlank).collect(Collectors.toSet());
        Map<String, StudentInfo> existStudent = CollectionUtils.isEmpty(studentUidSet) ? Collections.emptyMap() : selectStudentInfoByUniqueId(studentUidSet)
                .stream().collect(Collectors.toMap(StudentInfo::getStudentUniqueId, x -> x, (k1, k2) -> k1));

        Set<String> duplicateStudentUid = importVoList.stream()
                .map(StudentInfoExcelImportVo::getStudentUid)
                .filter(org.apache.commons.lang3.StringUtils::isNotBlank)
                .collect(Collectors.groupingBy(String::trim, Collectors.counting()))
                .entrySet().stream().filter(entry -> entry.getValue() > 1).map(Map.Entry::getKey).collect(Collectors.toSet());

        Map<Integer, String> classNameMap = classService.listAll().stream().collect(Collectors.toMap(ClassInfo::getId, ClassInfo::getClassName));

        return importVoList.stream().map(vo -> {
            List<String> err = new ArrayList<>();

            if (org.apache.commons.lang3.StringUtils.isBlank(vo.getStudentUid())) {
                err.add("学号不能为空");
            } else if (duplicateStudentUid.contains(vo.getStudentUid().trim())) {
                err.add("学号不能重复");
            }

            if (org.apache.commons.lang3.StringUtils.isBlank(vo.getName())) {
                err.add("姓名不能为空");
            }


            if (org.apache.commons.lang3.StringUtils.isNotBlank(vo.getStudentUid()) && existStudent.containsKey(vo.getStudentUid())) {
                StudentInfo exist = existStudent.get(vo.getStudentUid());
                err.add(String.format("学号[%s]已存在，对应学生信息：[班级: '%s', 姓名: '%s']", vo.getStudentUid(), classNameMap.getOrDefault(exist.getClassId(), ""), exist.getName()));
            }

            if (CollectionUtils.isEmpty(err)) {
                return null;
            }

            StudentInfoExcelImportResultVo result = new StudentInfoExcelImportResultVo(vo);
            result.setErrMsg(err);

            return result;
        }).filter(Objects::nonNull).collect(Collectors.toList());
    }

    @Transactional(rollbackFor = Exception.class)
    public List<StudentInfoExcelImportResultVo> handleImport(Collection<? extends StudentInfoExcelImportVo> voList, Integer classId) {

        LocalDateTime now = LocalDateTime.now();
        return voList.stream().map(vo -> {
            StudentInfo record = new StudentInfo() {{
                setName(vo.getName());
                setClassId(classId);
                setStudentUniqueId(vo.getStudentUid());
                setCallTimes(0);
                setAddTime(now);
                setUpdateTime(now);
                setDeleted(StudentInfo.NOT_DELETED);
                setHead(STRING_DEFAULT_HEAD);
            }};

            studentInfoMapper.insert(record);

            StudentInfoExcelImportResultVo result = new StudentInfoExcelImportResultVo(vo);
            result.setId(record.getId());
            return result;
        }).collect(Collectors.toList());
    }

    public StudentInfo selectByPrimaryKey(Integer id) {
        return Objects.nonNull(id) ? studentInfoMapper.selectOneByExample(new StudentInfoExample().createCriteria()
                        .andDeletedEqualTo(StudentInfo.NOT_DELETED)
                        .andIdEqualTo(id)
                .example()) : null;
    }

    public List<StudentInfo> selectByPrimaryKey(Collection<Integer> idList) {
        return CollectionUtils.isEmpty(idList) ? Collections.emptyList()
                : studentInfoMapper.selectByExample(new StudentInfoExample().createCriteria()
                        .andDeletedEqualTo(StudentInfo.NOT_DELETED)
                        .andIdIn(new ArrayList<>(idList))
                    .example());
    }

    @Transactional(rollbackFor = Exception.class)
    public String handleEmptyAllCall() {
        String logPrefix = StrUtil.format("[sessionId:{}]【清空点名次数】", UUID.randomUUID().toString().replace("-", ""));
        log.info(StrUtil.format("{} 请求"));

        int update = studentInfoMapper.updateByExampleSelective(
                StudentInfo.builder().callTimes(0).updateTime(LocalDateTime.now()).build(),
                new StudentInfoExample().createCriteria().andDeletedEqualTo(StudentInfo.NOT_DELETED).example(),
                StudentInfo.Column.callTimes, StudentInfo.Column.updateTime
        );

        log.info(StrUtil.format("{} 更新成功  条目数量: '{}'", logPrefix, update));

        return null;
    }

    @Transactional(rollbackFor = Exception.class)
    public void increaseCallTimeByPrimaryKey(Integer id) {
        increaseCallTime(studentInfoMapper.selectByPrimaryKey(id));
    }


    @Transactional(rollbackFor = Exception.class)
    public void increaseCallTime(StudentInfo studentInfo) {
        Integer callTimes = studentInfo.getCallTimes();
        studentInfo.setCallTimes(callTimes + 1);
        studentInfo.setUpdateTime(LocalDateTime.now());
        int i = studentInfoMapper.updateByExampleSelective(studentInfo,
                new StudentInfoExample().createCriteria().andDeletedEqualTo(StudentInfo.NOT_DELETED).andIdEqualTo(studentInfo.getId()).andCallTimesEqualTo(callTimes).example(),
                StudentInfo.Column.updateTime, StudentInfo.Column.callTimes);
        if (i <= 0) {
            throw new RuntimeException("更新点名次数失败");
        }
    }

    @Transactional(propagation = Propagation.NOT_SUPPORTED, readOnly = true)
    public Map<Integer, List<StudentInfo>> selectStudentByClassIdListGroupByClassId(Collection<Integer> classIdList) {
        return CollectionUtils.isEmpty(classIdList) ? ImmutableMap.of()
                : studentInfoMapper.selectByExample(new StudentInfoExample().createCriteria()
                        .andDeletedEqualTo(StudentInfo.NOT_DELETED)
                        .andClassIdIn(new ArrayList<>(classIdList))
                .example())
                .stream().collect(Collectors.groupingBy(StudentInfo::getClassId, Collectors.toList()));
    }
}
