package cn.teammodel.controller.admin.service.impl;

import cn.teammodel.common.ErrorCode;
import cn.teammodel.common.PK;
import cn.teammodel.config.exception.ServiceException;
import cn.teammodel.controller.admin.service.AdminAppraiseService;
import cn.teammodel.model.dto.admin.common.GroupDto;
import cn.teammodel.model.dto.admin.common.RGroupList;
import cn.teammodel.model.dto.admin.common.RMember;
import cn.teammodel.repository.*;
import cn.teammodel.model.dto.admin.appraise.TimeRangeDto;
import cn.teammodel.model.dto.admin.appraise.UpdateAchievementRuleDto;
import cn.teammodel.model.entity.User;
import cn.teammodel.model.entity.appraise.AchievementRule;
import cn.teammodel.model.entity.appraise.Appraise;
import cn.teammodel.model.entity.appraise.AppraiseTreeNode;
import cn.teammodel.model.entity.school.ClassInfo;
import cn.teammodel.model.entity.school.School;
import cn.teammodel.model.entity.school.Student;
import cn.teammodel.model.entity.school.Teacher;
import cn.teammodel.model.vo.admin.*;
import cn.teammodel.model.vo.appraise.RecordVo;
import cn.teammodel.security.utils.SecurityUtil;
import cn.teammodel.utils.GroupUtil;
import cn.teammodel.utils.RepositoryUtil;
import cn.teammodel.utils.SchoolDateUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.azure.cosmos.models.CosmosPatchOperations;
import com.azure.cosmos.models.PartitionKey;
import com.azure.spring.data.cosmos.core.query.CosmosPageRequest;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.data.domain.Slice;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.time.*;
import java.time.temporal.TemporalAdjusters;
import java.util.*;
import java.util.stream.Collectors;

import static cn.teammodel.utils.SchoolDateUtil.calculateWeekNum;

/**
 * @author winter
 * &#064;create  2023-12-06 14:46
 */
@Service
public class AdminAppraiseServiceImpl implements AdminAppraiseService {
    @Resource
    private SchoolRepository schoolRepository;
    @Resource
    private ClassRepository classRepository;
    @Resource
    private TeacherRepository teacherRepository;
    @Resource
    private AppraiseRepository appraiseRepository;
    @Resource
    private StudentRepository studentRepository;
    @Resource
    private AppraiseRecordRepository appraiseRecordRepository;

    @Autowired
    private Environment env; // 非静态字段

    private static Environment environment; // 静态字段
    @PostConstruct
    public void init() {
        AdminAppraiseServiceImpl.environment = env; // 在初始化时将非静态字段赋值给静态字段
    }

    @Override
    public AppraiseIndexData getIndexData(TimeRangeDto timeRangeDto) {
        final int SLICE_SIZE = 100;
        if (StringUtils.isBlank(timeRangeDto.getPeriodId())) {
            throw new ServiceException(ErrorCode.PARAMS_ERROR.getCode(), "不能为空");
        }

        final AppraiseIndexData appraiseIndexData = new AppraiseIndexData();
        int totalCount = 0;
        int criticalCount = 0;
        Set<String> creatorIdSet = new HashSet<>();
        Set<String> studentSet = new HashSet<>();
        User loginUser = SecurityUtil.getLoginUser();
        String schoolId = loginUser.getSchoolId();

        // 获取学期起止时间
        List<School.Semester> semesters = schoolRepository.findSemestersById(schoolId, timeRangeDto.getPeriodId());
        SchoolDateUtil.semesterModel semesterModel = SchoolDateUtil.getSemesterByNow(semesters, LocalDate.now());
        LocalDateTime startDatetime = null;
        LocalDateTime endDatetime = null;
        String academicYearId = null;
        if(timeRangeDto.getStartTime() != null) {
            startDatetime = LocalDateTime.ofInstant(Instant.ofEpochMilli(timeRangeDto.getStartTime()), ZoneId.systemDefault());
            endDatetime = LocalDateTime.ofInstant(Instant.ofEpochMilli(timeRangeDto.getEndTime()), ZoneId.systemDefault());
            academicYearId = timeRangeDto.getAcademicYearId();
        }else {
            academicYearId = semesterModel.getAcademicYearId();
            startDatetime = semesterModel.getStartDatetime();
            endDatetime = semesterModel.getEndDatetime();

        }
        if (startDatetime == null || endDatetime == null) throw new ServiceException(ErrorCode.PARAMS_ERROR);
        long totalWeek = calculateWeekNum(startDatetime, endDatetime, null);

        // slice 分段读取
        CosmosPageRequest pageRequest = new CosmosPageRequest(0, SLICE_SIZE, null);
        Slice<RecordVo> slice;
        Map<Long, Integer> countByWeek = SchoolDateUtil.createEmptyWeekMap(totalWeek);

        do {
            slice = appraiseRecordRepository.findAllByAcademicYearId(String.format(PK.PK_APPRAISE_RECORD, schoolId), academicYearId, pageRequest);
            List<RecordVo> content = slice.getContent();
            if (ObjectUtils.isEmpty(content)) {
                break;
            }

            // 分批次计算
            for (RecordVo item : content) {
                // 处理每周的评价数
                long weekNum = calculateWeekNum(startDatetime, endDatetime, item.getCreateTime());
                countByWeek.put(weekNum, countByWeek.getOrDefault(weekNum, 0) + 1);
                // 处理总评价数
                totalCount++;
                // 处理批评数
                if (!item.isPraise()) {
                    criticalCount++;
                }
                // 处理已评价老师总数
                creatorIdSet.add(item.getCreatorId());
                // 处理被评价的学生数
                if ("student".equals(item.getTargetType())) {
                    studentSet.add(item.getTargetId());
                }
            }

            if (slice.hasNext()) {
                pageRequest = (CosmosPageRequest) slice.nextPageable();
            }
        } while (slice.hasNext());

        // 组装数据
        appraiseIndexData.setCountByWeek(countByWeek);
        appraiseIndexData.setTotalCount(totalCount);
        appraiseIndexData.setCriticalCount(criticalCount);
        appraiseIndexData.setPraiseCount(totalCount - criticalCount);
        appraiseIndexData.setTeacherCount(creatorIdSet.size());
        appraiseIndexData.setStudentCount(studentSet.size());
        return appraiseIndexData;
    }

    @Override
    public List<RecordVo> conditionLatestRecord(TimeRangeDto timeRangeDto, HttpServletRequest request) {
        Long startTime = timeRangeDto.getStartTime();
        Long endTime = timeRangeDto.getEndTime();
        String academicYearId = timeRangeDto.getAcademicYearId();
        String schoolId = SecurityUtil.getLoginUser().getSchoolId();
        String typeName = null;
        String type = timeRangeDto.getType();
        if(type != null && !type.isEmpty()) {
            if (timeRangeDto.getType().equals("moral")) {
                typeName = "德育";
            }else if (timeRangeDto.getType().equals("labour")){
                typeName = "劳育";
            }
        }

        // fixme: 是否对时间范围做一些限制(不能确保当前周有数据)
        // 无参默认当前周
        if (startTime == null || endTime == null) {
            // 将时间范围调整为当前周的周一到当前时间
            LocalDateTime mondayOfCurWeek = LocalDateTime.now().with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY))
                    .withHour(0)
                    .withMinute(0)
                    .withSecond(0)
                    .withNano(0);
            startTime = mondayOfCurWeek.atZone(ZoneOffset.systemDefault()).toInstant().toEpochMilli();
            endTime = Instant.now().toEpochMilli();
        }
        List<ClassInfo> classes = classRepository.findClassBySchoolIdAndPeriodId(timeRangeDto.getPeriodId(), String.format(PK.CLASS, schoolId));
        if (classes.isEmpty()) {
            return new ArrayList<>();
        }
        List<String> classIds = classes.stream().map(ClassInfo::getId).collect(Collectors.toList());
        List<RecordVo> res = appraiseRecordRepository.latestRecords(
                String.format(PK.PK_APPRAISE_RECORD, schoolId),
                academicYearId,
                startTime,
                endTime,
                typeName,
                classIds
        );

        GroupDto groupDto = new GroupDto();
        groupDto.setIds(classIds);
        groupDto.setSchoolId(schoolId);
        String url = environment.getProperty("ies.server-url-group");
        Map<String, Object> groupId = GroupUtil.getGroupId(groupDto, new GroupUtil(environment), request, url);
        List<RGroupList> rGroupList = new ArrayList<>();
        for (Map.Entry<String, Object> entry : groupId.entrySet()) {
            String key = entry.getKey();
            Object value = entry.getValue();
            if (key.equals("groups")) {
                String jsonGroups = JSON.toJSONString(value);
                rGroupList = JSON.parseObject(jsonGroups, new TypeReference<List<RGroupList>>() {});
            }
        }


        if (res != null) {
            res = res.stream().sorted((o1, o2) -> o2.getCreateTime().compareTo(o1.getCreateTime())).collect(Collectors.toList());
            for (RecordVo recordVo : res) {
                List<RGroupList> finalRGroupList = rGroupList;
                String className = Optional.ofNullable(recordVo.getClassName())
                        .orElseGet(() -> finalRGroupList.stream()
                                .filter(group -> group.getId().equals(recordVo.getClassId()))
                                .map(RGroupList::getName)
                                .findFirst()
                                .orElse("未知班级"));
                recordVo.setClassName(className);
            }
        }

        return res;
    }

    @Override
    public List<RankPo> classRank(TimeRangeDto timeRangeDto) {
        // todo 不要 page| 批评和表扬数量
        Long startTime = timeRangeDto.getStartTime();
        Long endTime = timeRangeDto.getEndTime();
        String academicYearId = timeRangeDto.getAcademicYearId();
        String schoolId = SecurityUtil.getLoginUser().getSchoolId();

        List<RankPo> rankPoList = appraiseRecordRepository.classRank(
                String.format(PK.PK_APPRAISE_RECORD, schoolId),
                academicYearId,
                startTime,
                endTime
        );

        if (ObjectUtils.isEmpty(rankPoList)) return null;
        Set<String> classIdSet = rankPoList.stream().map(RankPo::getId).collect(Collectors.toSet());
        // 注意: 如果查询 in 的查询集在数据库中不存在,则在结果集也不会为 null.
        if (ObjectUtils.isEmpty(classIdSet)) return rankPoList;
        List<ClassInfo> classes = classRepository.findAllByCodeAndIdIn(String.format(PK.CLASS, schoolId), classIdSet);
        Map<String, String> idNameMap = classes.stream().collect(Collectors.toMap(ClassInfo::getId, ClassInfo::getName));
        rankPoList.forEach(rankVo -> rankVo.setName(idNameMap.get(rankVo.getId())));
        return rankPoList;
    }

    @Override
    public List<RankVo> teacherRank(TimeRangeDto timeRangeDto) {
        Long startTime = timeRangeDto.getStartTime();
        Long endTime = timeRangeDto.getEndTime();
        String academicYearId = timeRangeDto.getAcademicYearId();
        String schoolId = SecurityUtil.getLoginUser().getSchoolId();

        List<RankPo> rankPoList = appraiseRecordRepository.teacherRank(
                String.format(PK.PK_APPRAISE_RECORD, schoolId),
                academicYearId,
                startTime,
                endTime
        );
        // 根据 id 分组
        Map<String, List<RankPo>> idRankPoMap = rankPoList.stream().collect(Collectors.groupingBy(RankPo::getId));
        final List<RankVo> rankVos = new ArrayList<>();

        idRankPoMap.forEach((id, list) -> {
            RankVo rankVo = new RankVo();
            int praiseCount = 0;
            int criticalCount = 0;
            rankVo.setId(id);

            for (RankPo po : list) {
                if (po.getIsPraise()) {
                    praiseCount += po.getCount();
                } else {
                    criticalCount += po.getCount();
                }
            }
            rankVo.setPraiseCount(praiseCount);
            rankVo.setCriticalCount(criticalCount);
            rankVo.setTotalCount(praiseCount + criticalCount);
            rankVos.add(rankVo);
        });
        // 排序
        List<RankVo> res = rankVos.stream()
                .sorted(Comparator.comparing(RankVo::getTotalCount).reversed())
                .collect(Collectors.toList());
        // 设置 name
        if (ObjectUtils.isEmpty(res)) return null;
        Set<String> teacherIdSet = res.stream().map(RankVo::getId).collect(Collectors.toSet());
        if (ObjectUtils.isEmpty(teacherIdSet)) return res;
        List<Teacher> teachers = teacherRepository.findAllByCodeAndIdIn(PK.COMMON_BASE, teacherIdSet);
        Map<String, String> idNameMap = teachers.stream().collect(Collectors.toMap(Teacher::getId, Teacher::getName));
        res.forEach(rankVo -> rankVo.setName(idNameMap.get(rankVo.getId())));
        return res;
    }

    @Override
    public List<AppraiseNodeRankVo> appraiseNodeRank(TimeRangeDto timeRangeDto) {
        Long startTime = timeRangeDto.getStartTime();
        Long endTime = timeRangeDto.getEndTime();
        String academicYearId = timeRangeDto.getAcademicYearId();
        String schoolId = SecurityUtil.getLoginUser().getSchoolId();

        List<AppraiseNodeRankVo> rankVoList = appraiseRecordRepository.appraiseNodeRank(
                String.format(PK.PK_APPRAISE_RECORD, schoolId),
                academicYearId,
                startTime,
                endTime
        );

        if (rankVoList == null) return null;
        List<String> names = rankVoList.stream().map(AppraiseNodeRankVo::getName).collect(Collectors.toList());
        if (ObjectUtils.isEmpty(names)) return null;
        // 去重后的 nodes
        List<AppraiseTreeNode> nodesByName = appraiseRecordRepository.findAppraiseRecordInNames(String.format(PK.PK_APPRAISE_RECORD, schoolId), academicYearId, names);
        // 正常情况下 name 一一对应 todo: 临时解决
        Map<String, AppraiseTreeNode> nameNodeMap = nodesByName.stream()
                .collect(Collectors.toMap(AppraiseTreeNode::getName, item -> item, (existing, replacement) -> {
                    if (replacement.getPath() != null) {
                        return replacement;
                    }
                    return existing;
                }));

        rankVoList = rankVoList.stream()
                .sorted(Comparator.comparing(RankPo::getCount).reversed())
                // 流中对元素操作但不改变流
                .peek(s -> {
                    AppraiseTreeNode node = nameNodeMap.get(s.getName());
                    if (node != null) {
                        s.setPath(node.getPath());
                        s.setIsPraise(node.isPraise());
                    }
                })
                .collect(Collectors.toList());
        return rankVoList;
    }

    @Override
    public List<StudentRankVo> studentRank(TimeRangeDto timeRangeDto) {
        Long startTime = timeRangeDto.getStartTime();
        Long endTime = timeRangeDto.getEndTime();
        String academicYearId = timeRangeDto.getAcademicYearId();
        String schoolId = SecurityUtil.getLoginUser().getSchoolId();

        List<RankPo> rankPoList = appraiseRecordRepository.studentRank(
                String.format(PK.PK_APPRAISE_RECORD, schoolId),
                academicYearId,
                startTime,
                endTime
        );
        // 根据 id 分组
        Map<String, List<RankPo>> idRankPoMap = rankPoList.stream().collect(Collectors.groupingBy(RankPo::getId));
        final List<StudentRankVo> rankVos = new ArrayList<>();

        idRankPoMap.forEach((id, list) -> {
            StudentRankVo rankVo = new StudentRankVo();
            int praiseCount = 0;
            int criticalCount = 0;
            rankVo.setId(id);

            for (RankPo po : list) {
                if (po.getIsPraise()) {
                    praiseCount += po.getCount();
                } else {
                    criticalCount += po.getCount();
                }
            }
            rankVo.setPraiseCount(praiseCount);
            rankVo.setCriticalCount(criticalCount);
            rankVo.setTotalCount(praiseCount + criticalCount);
            rankVos.add(rankVo);
        });
        // 排序
        List<StudentRankVo> res = rankVos.stream()
                .sorted(Comparator.comparing(StudentRankVo::getTotalCount).reversed())
                .collect(Collectors.toList());
        // 设置 student name
        if (ObjectUtils.isEmpty(res)) return null;
        Set<String> studentIdSet = res.stream().map(StudentRankVo::getId).collect(Collectors.toSet());
        if (ObjectUtils.isEmpty(studentIdSet)) return res;

        List<Student> students = studentRepository.findAllByCodeAndIdIn(String.format(PK.STUDENT, schoolId), studentIdSet);
        // 利用数组作为三元组,提取 Student 中的 name 和 classId, picture
        Map<String, String[]> idNameMap = students.stream().collect(Collectors.toMap(Student::getId, item -> {
            String[] studentInfo = new String[3];
            studentInfo[0] = item.getName();
            studentInfo[1] = item.getClassId();
            studentInfo[2] = item.getPicture();
            return studentInfo;
        }));
        res.forEach(rankVo -> {
            rankVo.setName(idNameMap.get(rankVo.getId())[0]);
            rankVo.setClassName(idNameMap.get(rankVo.getId())[1]);
            rankVo.setPicture(idNameMap.get(rankVo.getId())[2]);
        });

        // 设置 class name
        Set<String> classIds = students.stream().map(Student::getClassId).collect(Collectors.toSet());
        List<ClassInfo> classes = classRepository.findAllByCodeAndIdIn(String.format(PK.CLASS, schoolId), classIds);
        Map<String, String> idClassNameMap = classes.stream().collect(Collectors.toMap(ClassInfo::getId, ClassInfo::getName));
        res.forEach(rankVo -> rankVo.setClassName(idClassNameMap.getOrDefault(rankVo.getClassName(), null)));
        return res;
    }

    @Override
    public List<AchievementRule> updateAchieveRule(UpdateAchievementRuleDto ruleDto) {
        String periodId = ruleDto.getPeriodId();
        UpdateAchievementRuleDto.UpdateRule updateRule = ruleDto.getUpdateRule();

        if (ObjectUtils.isEmpty(updateRule) || StringUtils.isBlank(updateRule.getId()) || ObjectUtils.isEmpty(updateRule.getName())) {
            throw new ServiceException(ErrorCode.PARAMS_ERROR.getCode(), "rule id/name 不能为空");
        }
        if (updateRule.getLevelCount() == null || updateRule.getLevelCount() <= 0 || updateRule.getPromotionLevel() == null || updateRule.getPromotionLevel() <= 0) {
            throw new ServiceException(ErrorCode.PARAMS_ERROR.getCode(), "规则不能为空或小于等于0");
        }
        User user = SecurityUtil.getLoginUser();
        String schoolId = user.getSchoolId();

        Appraise appraise = RepositoryUtil.findOne(appraiseRepository.findRulesById(schoolId, periodId), "参数错误，找不到该学段下的评价规则");
        List<AchievementRule> rules = appraise.getAchievementRules();
        if (ObjectUtils.isEmpty(rules)) {
            throw new ServiceException(ErrorCode.OPERATION_ERROR.getCode(), "该学段暂无没有成就规则");
        }
        // sort rules by level
        rules = rules.stream().sorted(Comparator.comparing(AchievementRule::getLevel)).collect(Collectors.toList());
        boolean flag = false;
        int lastPromotionCount = 0;
        for (AchievementRule rule : rules) {
            if (updateRule.getId().equals(rule.getId())) {
                BeanUtils.copyProperties(updateRule, rule);
                lastPromotionCount = rule.getLevelCount() * rule.getPromotionLevel();
                rule.setPromotionCount(lastPromotionCount);
                flag = true;
                continue;
            }
            // 处理后面的节点,将 promotionCount 依次修改
            if (flag) {
                lastPromotionCount = lastPromotionCount + rule.getLevelCount() * rule.getPromotionLevel();
                rule.setPromotionCount(lastPromotionCount);
            }
        }

        CosmosPatchOperations operations = CosmosPatchOperations.create().replace("/achievementRules", rules);
        Appraise saved = appraiseRepository.save(appraise.getId(), new PartitionKey(PK.PK_APPRAISE), Appraise.class, operations);
        return saved.getAchievementRules();
    }

    @Override
    public List<AchievementRule> getAchieveRules(String periodId) {
        User user = SecurityUtil.getLoginUser();
        String schoolId = user.getSchoolId();

        Appraise appraise = RepositoryUtil.findOne(appraiseRepository.findRulesById(schoolId, periodId), "还未创建该学段下的评价规则");
        List<AchievementRule> rules = appraise.getAchievementRules();
        if (ObjectUtils.isEmpty(rules)) {
            rules = Collections.emptyList();
        }
        return rules;
    }

}
