package rd.service.course;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.util.MapUtils;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.fill.FillConfig;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.github.yitter.idgen.YitIdHelper;
import rd.dao.mapper.course.CourseMapper;
import rd.dao.mapper.course.CourseSelectionMapper;
import rd.data.dto.FileByteDataWrapper;
import rd.data.dto.course.CourseDTO;
import rd.data.dto.course.CourseNameDTO;
import rd.data.dto.schedule.NotSelectStudentExportDto;
import rd.data.dto.schedule.SelectedStudentExportDto;
import rd.data.db.CourseScheduleSelectionInfo;
import rd.data.db.table.Course;
import rd.data.db.table.StudentInfo;
import rd.data.model.response.PaginationWrapper;
import rd.service.cache.GlobalCache;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.time.LocalDateTime;
import java.util.*;

@Service
public class CourseService {
    private final CourseMapper courseMapper;
    private final CourseSelectionMapper courseSelectionMapper;

    public CourseService(CourseMapper courseMapper, CourseSelectionMapper courseSelectionMapper) {
        this.courseMapper = courseMapper;
        this.courseSelectionMapper = courseSelectionMapper;
    }

    /**
     * 添加一个课程信息
     *
     * @param courseRequest 添加请求
     * @return 已添加的课程信息
     */
    @Transactional
    public CourseDTO add(CourseDTO courseRequest) {
        Long id = YitIdHelper.nextId();

        Course course = new Course();
        course.setId(id);
        course.setName(courseRequest.getName());
        course.setCourseHours(courseRequest.getCourseHours());
        course.setSpringTerm(courseRequest.getSpringTerm());
        course.setExamRatio(courseRequest.getExamRatio());

        courseMapper.insert(course);

        return this.getCourseDTO(id, false);
    }

    /**
     * 删除指定id的课程
     *
     * @param id 欲删除的课程id
     */
    @Transactional
    public void delete(Long id) {
        Course course = new Course();
        course.setDeleted(true);
        course.setEditTime(LocalDateTime.now());
        courseMapper.update(course, new QueryWrapper<Course>()
                .eq("id", id)
                .eq("deleted", false)
        );

        GlobalCache.courseCache.invalidate(id);
        GlobalCache.ActivityTargetCourseCache.invalidateAll();
    }

    public CourseDTO getCourseDTO(Long id) {
        return getCourseDTO(id, true);
    }

    public Course getCourse(Long id) {
        return getCourse(id, true);
    }

    /**
     * 根据课程id获取课程信息
     *
     * @param id       课程id
     * @param useCache 是否使用缓存
     * @return 课程信息
     */
    public CourseDTO getCourseDTO(Long id, boolean useCache) {
        var result = GlobalCache.courseCache.getIfPresent(id);
        if (!useCache || result == null) {
            result = courseMapper.selectOne(new QueryWrapper<Course>()
                    .eq("id", id)
                    .eq("deleted", false)
            );
            if (result == null) {
                return null;
            }

            GlobalCache.courseCache.put(id, result);
        }

        return convert(result);
    }

    /**
     * 根据课程id获取课程信息
     *
     * @param id       课程id
     * @param useCache 是否使用缓存
     * @return 课程信息
     */
    public Course getCourse(Long id, boolean useCache) {
        if (useCache) {
            return GlobalCache.courseCache.get(id, this::getCourseFromDB);
        } else {
            var course = this.getCourseFromDB(id);
            GlobalCache.courseCache.put(id, course);
            return course;
        }
    }

    private Course getCourseFromDB(Long id) {
        return courseMapper.selectOne(new QueryWrapper<Course>()
                .eq("id", id)
                .eq("deleted", false)
        );
    }

    /**
     * 根据课程id批量获取课程信息
     *
     * @param idList 课程id
     * @return 课程信息
     */
    public Map<Long, Course> getCourse(List<Long> idList) {
        return GlobalCache.courseCache.getAll(idList, any -> {
            var courses = courseMapper.selectList(new QueryWrapper<Course>()
                    .in("id", idList)
                    .eq("deleted", false)
            );

            Map<Long, Course> courseIdMap = new HashMap<>(courses.size());
            for (Course course : courses) {
                courseIdMap.put(course.getId(), course);
            }

            return courseIdMap;
        });
    }

    /**
     * 分页获取所有课程
     *
     * @param page     第几页
     * @param pageSize 每页数据量
     * @return 分页包装数据
     */
    public PaginationWrapper<List<CourseDTO>> list(int page, int pageSize) {
        if (pageSize == 0) {
            pageSize = 1024;
        }

        Page<Course> queryPage = new Page<>(page, pageSize);
        var result = courseMapper.selectPage(queryPage, new QueryWrapper<Course>()
                .eq("deleted", false)
        );

        List<CourseDTO> courseDTOList = new ArrayList<>();
        var records = result.getRecords();
        for (var course : records) {
            courseDTOList.add(convert(course));
        }

        return new PaginationWrapper<>(
                (int) result.getCurrent(),
                (int) result.getSize(),
                (int) result.getTotal(),
                courseDTOList
        );
    }

    /**
     * 分页获取所有课程名
     *
     * @param page     第几页
     * @param pageSize 每页数据量
     * @return 分页包装数据
     */
    public PaginationWrapper<List<CourseNameDTO>> listCourseName(int page, int pageSize) {
        if (pageSize == 0) {
            pageSize = 1024;
        }

        Page<Course> queryPage = new Page<>(page, pageSize);
        var result = courseMapper.selectPage(queryPage, new QueryWrapper<Course>()
                .select("id", "name")
                .eq("deleted", false)
        );

        List<CourseNameDTO> courseDTOList = new ArrayList<>();
        var records = result.getRecords();
        for (var course : records) {
            courseDTOList.add(new CourseNameDTO(course.getId(), course.getName()));
        }

        return new PaginationWrapper<>(
                (int) result.getCurrent(),
                (int) result.getSize(),
                (int) result.getTotal(),
                courseDTOList
        );
    }

    public CourseNameDTO getCourseName(Long courseId) {
        var course = getCourse(courseId);
        if (course == null) {
            return null;
        }

        return new CourseNameDTO(course.getId(), course.getName());
    }

    private CourseDTO convert(Course course) {
        CourseDTO courseDTO = new CourseDTO();
        courseDTO.setId(course.getId());
        courseDTO.setName(course.getName());
        courseDTO.setCourseHours(course.getCourseHours());
        courseDTO.setExamRatio(course.getExamRatio());
        courseDTO.setSpringTerm(course.getSpringTerm());

        return courseDTO;
    }

    private Course convert(CourseDTO courseDTO) {
        Course course = new Course();
        course.setName(courseDTO.getName());
        course.setCourseHours(courseDTO.getCourseHours());
        course.setExamRatio(courseDTO.getExamRatio());
        course.setSpringTerm(courseDTO.getSpringTerm());

        return course;
    }

    /**
     * 更新指定id的课程信息
     *
     * @param id  课程id
     * @param dto 新的课程信息
     * @return 更新好的课程信息
     */
    @Transactional
    public CourseDTO update(Long id, CourseDTO dto) {
        var course = convert(dto);
        course.setEditTime(LocalDateTime.now());

        courseMapper.update(course, new QueryWrapper<Course>()
                .eq("id", id)
                .eq("deleted", false)
        );

        GlobalCache.courseCache.invalidate(id);
        GlobalCache.ActivityTargetCourseCache.invalidateAll();
        return this.getCourseDTO(id, false);
    }

    private static final Map<Integer, String> weekdayNameMap = new HashMap<>();

    static {
        weekdayNameMap.put(1, "星期一");
        weekdayNameMap.put(2, "星期二");
        weekdayNameMap.put(3, "星期三");
        weekdayNameMap.put(4, "星期四");
        weekdayNameMap.put(5, "星期五");
        weekdayNameMap.put(6, "星期六");
        weekdayNameMap.put(7, "星期日");
    }

    private static final Map<Integer, String> sectionNameMap = new HashMap<>();

    static {
        sectionNameMap.put(0, "第一大节(1~2)");
        sectionNameMap.put(1, "第二大节(3~4)");
        sectionNameMap.put(2, "第三大节(5~6)");
        sectionNameMap.put(3, "第四大节(7~7)");
        sectionNameMap.put(4, "第五大节(9~10)");
        sectionNameMap.put(5, "第六大节(11~12)");
    }

    public List<CourseScheduleSelectionInfo> getSelectedStudentList(Long courseId, String term) {
        return courseSelectionMapper.getSelectedStudentList(courseId, term);
    }

    public List<StudentInfo> getNotSelectStudentList(Long courseId, String term) {
        return courseSelectionMapper.getNotSelectStudentList(courseId, term);
    }

    public FileByteDataWrapper exportSelectedStudents(Long courseId, String term) {
        Course course = this.getCourse(courseId);
        List<CourseScheduleSelectionInfo> courseScheduleSelectionInfoList = this.getSelectedStudentList(courseId, term);

        // 按学生uid聚合
        Map<Long, List<CourseScheduleSelectionInfo>> selectedStudentMap = new HashMap<>(courseScheduleSelectionInfoList.size());
        for (CourseScheduleSelectionInfo selectedStudentInfo : courseScheduleSelectionInfoList) {
            selectedStudentMap.computeIfAbsent(selectedStudentInfo.getUid(), uid -> new ArrayList<>())
                    .add(selectedStudentInfo);
        }

        int count = 0;
        List<SelectedStudentExportDto> selectedStudentExportDtoList = new ArrayList<>(courseScheduleSelectionInfoList.size());
        for (Long uid : selectedStudentMap.keySet()) {
            count++;
            List<CourseScheduleSelectionInfo> selectionInfos = selectedStudentMap.get(uid);
            var studentName = selectionInfos.get(0).getName();
            var studentNumber = selectionInfos.get(0).getStudentNumber();
            var clazz = selectionInfos.get(0).getClazz();

            SelectedStudentExportDto selectedStudentExportDto = new SelectedStudentExportDto();
            selectedStudentExportDto.setId(count);
            selectedStudentExportDto.setStudentNumber(studentNumber);
            selectedStudentExportDto.setClazz(clazz);
            selectedStudentExportDto.setName(studentName);
            StringJoiner stringJoiner = new StringJoiner("，\r\n");
            for (CourseScheduleSelectionInfo info : selectionInfos) {
                stringJoiner.add(String.format("%s%s(%d周~%d周)",
                        weekdayNameMap.getOrDefault(info.getWeekday(), info.getWeekday().toString()),
                        sectionNameMap.getOrDefault(info.getTime(), info.getTime().toString()),
                        info.getStartWeek(), info.getEndWeek()
                ));
            }

            selectedStudentExportDto.setSelectedSchedule(stringJoiner.toString());
            selectedStudentExportDtoList.add(selectedStudentExportDto);
        }

        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        InputStream template = getClass().getResourceAsStream("/template_student_list.xlsx");
        try (ExcelWriter excelWriter = EasyExcel.write(outputStream).withTemplate(template).build()) {
            WriteSheet writeSheet = EasyExcel.writerSheet().build();

            FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
            excelWriter.fill(selectedStudentExportDtoList, fillConfig, writeSheet);

            Map<String, Object> map = MapUtils.newHashMap();

            map.put("courseName", course.getName());
            map.put("term", term);
            map.put("count", count);

            excelWriter.fill(map, writeSheet);

            excelWriter.finish();
        }

        var data = outputStream.toByteArray();
        return new FileByteDataWrapper(data, course.getName());
    }

    public FileByteDataWrapper exportNotSelectStudents(Long courseId, String term) {
        Course course = this.getCourse(courseId);
        List<StudentInfo> notSelectStudentList = this.getNotSelectStudentList(courseId, term);

        int count = 0;
        List<NotSelectStudentExportDto> selectedStudentExportDtoList = new ArrayList<>(notSelectStudentList.size());
        for (var notSelectStudent : notSelectStudentList) {
            count++;
            NotSelectStudentExportDto selectedStudentExportDto = new NotSelectStudentExportDto();
            selectedStudentExportDto.setId(count);
            selectedStudentExportDto.setStudentNumber(notSelectStudent.getStudentNumber());
            selectedStudentExportDto.setClazz(notSelectStudent.getClazz());
            selectedStudentExportDto.setName(notSelectStudent.getName());

            selectedStudentExportDtoList.add(selectedStudentExportDto);
        }

        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        InputStream template = getClass().getResourceAsStream("/template_student_list_not_selected.xlsx");
        try (ExcelWriter excelWriter = EasyExcel.write(outputStream).withTemplate(template).build()) {
            WriteSheet writeSheet = EasyExcel.writerSheet().build();

            FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
            excelWriter.fill(selectedStudentExportDtoList, fillConfig, writeSheet);

            Map<String, Object> map = MapUtils.newHashMap();

            map.put("courseName", course.getName());
            map.put("term", term);
            map.put("count", selectedStudentExportDtoList.size());

            excelWriter.fill(map, writeSheet);

            excelWriter.finish();
        }

        var data = outputStream.toByteArray();
        return new FileByteDataWrapper(data, course.getName());
    }
}
