package rd.service.score;

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.github.yitter.idgen.YitIdHelper;
import rd.dao.mapper.score.ScoreMapper;
import rd.data.code.ServiceCode;
import rd.data.dto.FileByteDataWrapper;
import rd.data.dto.score.CourseScoreInfoDto;
import rd.data.dto.score.ScoreExportDto;
import rd.data.dto.score.StudentScore;
import rd.data.db.table.Score;
import rd.exception.ServiceException;

import rd.service.course.CourseService;
import rd.service.user.StudentUserService;
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.ArrayList;
import java.util.List;
import java.util.Map;

@Service
public class ScoreService {
    private final ScoreMapper scoreMapper;
    private final StudentUserService studentUserService;
    private final CourseService courseService;

    public ScoreService(ScoreMapper scoreMapper,
                        StudentUserService studentUserService,
                        CourseService courseService) {

        this.scoreMapper = scoreMapper;
        this.studentUserService = studentUserService;
        this.courseService = courseService;
    }

    public List<StudentScore> getStudentAllScore(Long uid, String term) {
        return scoreMapper.getStudentAllScore(uid, term);
    }

    public List<CourseScoreInfoDto> getCourseTermAllSelectedStudentScore(Long courseId, String term) {
        return scoreMapper.getTermCourseAllSelectedStudentScore(courseId, term);
    }

    @Transactional
    public void insertOrUpdateScore(Long scoreId, Long courseId, String term, CourseScoreInfoDto courseScoreInfoDto) {
        if (courseScoreInfoDto.getUsualScore() == null ||
                courseScoreInfoDto.getExamScore() == null ||
                courseScoreInfoDto.getStudentNumber() == null ||
                term == null
        ) {
            throw ServiceException.error(ServiceCode.DataFormatWrong);
        }

        if (courseScoreInfoDto.getExamRatio() == null) {
            var course = courseService.getCourse(courseId);
            if (course == null) {
                throw ServiceException.error(ServiceCode.DataNotExists, "课程id不存在");
            }

            courseScoreInfoDto.setExamRatio(course.getExamRatio());
        }

        Long studentUid = studentUserService.getByStudentNumber(courseScoreInfoDto.getStudentNumber()).id();

        if (scoreId == null) {
            scoreId = YitIdHelper.nextId();
        }

        Score score = convert(courseScoreInfoDto);
        score.setId(scoreId);
        score.setCourse(courseId);
        score.setStudent(studentUid);
        score.setTerm(term);
        score.setEditTime(LocalDateTime.now());

        scoreMapper.insertOrUpdateScore(score);
    }

    public FileByteDataWrapper exportTermCourseAllStudentScoreExcel(Long courseId, String term) {
        var course = courseService.getCourse(courseId);
        if (course == null) {
            throw ServiceException.error(ServiceCode.DataNotExists, "课程id不存在");
        }

        var scoreList = scoreMapper.getTermCourseAllSelectedStudentScore(courseId, term);

        int count = 0;
        double sum = 0.0;

        List<ScoreExportDto> scoreExportDtoList = new ArrayList<>(scoreList.size());
        for (int i = 0; i < scoreList.size(); i++) {
            var score = scoreList.get(i);
            float examRatio = score.getExamRatio() == null ? course.getExamRatio() : score.getExamRatio();
            ScoreExportDto scoreExportDto = convert(i + 1, score);
            scoreExportDto.setExamRatio(examRatio);
            if (score.getId() != null) {
                count++;
                double weightedUsualScore = score.getUsualScore() * (1 - examRatio);
                double weightedExamScore = score.getExamScore() * examRatio;
                double finalScore = weightedUsualScore + weightedExamScore;
                sum += finalScore;
                scoreExportDto.setFinalScore(String.format("%.2f", finalScore));
            }

            scoreExportDtoList.add(scoreExportDto);
        }

        double avg = 0.0;
        if (count != 0) {
            avg = sum / count;
        }

        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        InputStream template = getClass().getResourceAsStream("/template_score.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(scoreExportDtoList, fillConfig, writeSheet);

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

            map.put("courseName", course.getName());
            map.put("count", scoreExportDtoList.size());
            map.put("recordCount", count);
            map.put("avg", String.format("%.2f", avg));
            map.put("sum", String.format("%.2f", sum));

            excelWriter.fill(map, writeSheet);

            excelWriter.finish();
        }

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

    public static ScoreExportDto convert(int index, CourseScoreInfoDto courseScoreInfoDto) {
        ScoreExportDto scoreExportDto = new ScoreExportDto();
        scoreExportDto.setId(index);
        scoreExportDto.setStudentName(courseScoreInfoDto.getStudentName());
        scoreExportDto.setNumber(courseScoreInfoDto.getStudentNumber());
        scoreExportDto.setUsualScore(courseScoreInfoDto.getUsualScore());
        scoreExportDto.setExamScore(courseScoreInfoDto.getExamScore());

        return scoreExportDto;
    }

    public static Score convert(CourseScoreInfoDto courseScoreInfoDto) {
        Score score = new Score();
        score.setId(courseScoreInfoDto.getId());
//        score.setStudent(score.getStudent());
        score.setUsualScore(courseScoreInfoDto.getUsualScore());
        score.setExamScore(courseScoreInfoDto.getExamScore());
        score.setExamRatio(courseScoreInfoDto.getExamRatio());

        return score;
    }
}
