package com.ssj.service.sys.situation.service.impl;

import com.alibaba.dubbo.config.annotation.Service;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.google.common.base.Strings;
import com.ssj.bean.sys.homework.domain.*;
import com.ssj.bean.sys.situation.domain.LearningSituation;
import com.ssj.bean.weixin.libmy.domain.LibVip;
import com.ssj.bean.weixin.push.domain.Miniprogram;
import com.ssj.bean.weixin.push.domain.SendTemplateShort;
import com.ssj.bean.weixin.user.domain.User;
import com.ssj.conch.constant.ConchConstants;
import com.ssj.conch.constant.CourseConstant;
import com.ssj.conch.util.AIServiceUtils;
import com.ssj.conch.util.enums.GradeEnum;
import com.ssj.dao.sys.situation.dao.LearningConditionDao;
import com.ssj.es.service.base.BaseSearchService;
import com.ssj.framework.core.persistence.PagingHibernateJdbcDao;
import com.ssj.framework.core.security.manager.TokenManager;
import com.ssj.framework.core.util.CodecUtil;
import com.ssj.framework.core.util.Constant;
import com.ssj.framework.core.util.RedisUtil;
import com.ssj.framework.idworker.IdWorker;
import com.ssj.framework.weixin.util.DateUtil;
import com.ssj.framework.weixin.util.WxUtil;
import com.ssj.mapper.knowledge.KmtKnowledgeMapper;
import com.ssj.service.conch.conch.service.HomeworkRankingService;
import com.ssj.service.conch.parents.v2.dto.*;
import com.ssj.service.conch.parents.v2.request.LearningGrowthReportReq;
import com.ssj.service.kmt.constant.KnowledgeVersion;
import com.ssj.service.kmt.dto.KnowledgeBaseDto;
import com.ssj.service.kmt.service.KmtExerciseMapperService;
import com.ssj.service.mapper.HomeworkMapper;
import com.ssj.service.mapper.LearningGrowthReportMapper;
import com.ssj.service.mapper.LibVipMapper;
import com.ssj.service.sys.problem.service.IBanbenDetailService;
import com.ssj.service.sys.push.service.SconXingeService;
import com.ssj.service.sys.situation.service.LearningConditionService;
import com.ssj.service.weixin.library.service.ILibVipService;
import com.ssj.service.weixin.push.service.PushTemplateService;
import com.ssj.service.weixin.user.service.UserService;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.QueryStringQueryBuilder;
import org.elasticsearch.index.query.TermQueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.text.ParseException;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.*;
import java.util.stream.Collectors;

@Service(version = "1.0")
@Transactional
public class LearningConditionServiceImpl implements LearningConditionService {

	private static final Logger logger = LoggerFactory.getLogger(LearningConditionServiceImpl.class);

	@Autowired
	private ILibVipService libVipService;
	@Autowired
	private PushTemplateService pushTemplateService;
	@Autowired
	private UserService userService;
	@Autowired
	private SconXingeService sconXingeService;
	@Autowired
    private LearningConditionDao conditionDao;
	@Autowired
	private PagingHibernateJdbcDao pagingHibernateJdbcDao;
	@Autowired
	private RedisUtil redisUtil;
	@Autowired
	private HomeworkMapper homeworkMapper;
	@Autowired
	private KmtExerciseMapperService kmtExerciseMapperService;
	@Autowired
	private IBanbenDetailService banbenService;
	@Autowired
	private LearningGrowthReportMapper learningGrowthReportMapper;
	@Autowired
    private BaseSearchService baseSearchService;
	@Autowired
	private LibVipMapper libVipMapper;
	@Autowired
	private KmtKnowledgeMapper kmtKnowledgeMapper;
	@Autowired
	private KnowledgeVersion knowledgeVersion;
	@Autowired
	private TokenManager tokenManager;
	@Autowired
	private HomeworkRankingService rankingService;

	/**
	 * 初始化学情统计
	 */
	@Override
	public void initDataService() {
		conditionDao.deleteAll();
		calByDate(null);
	}
	/**
	 * 每天执行，只增加昨天的数据
	 * @param dateOffset 日期偏移量，如：-1表示使用前一天的数据，0表示使用当天的数据
	 */
	@Override
	public void dailyUpdateService(int dateOffset) {
		// 检查上一天是否有数据，没有则执行init
		if(Strings.isNullOrEmpty(conditionDao.queryLastDay()) ) {
			logger.info("学情初始化");
			initDataService();
		} else {
			logger.info("学情每日更新");
			calByDate(DateUtil.dateToString(DateUtil.AddDay(new Date(), dateOffset)));
		}
	}

	/**
	 * 统计练习册答案批改结果中的错题数量和题目总量
	 *
	 * @param date 指定统计的时间，为空则统计所有
	 * @return 更新的记录数
	 */
	public Integer calAutoDataByDate(String date) {
		if(Strings.isNullOrEmpty(date)) {
			//遍历所有auto数据存入学情表
		}
		Integer startNum = 0;
		Integer size = 500;
		Integer resultNum = 0;
		while(true) {
			List<Map<String, Object>> situationList = conditionDao.queryAutoHwMoudleByDate(startNum, size, date);
			resultNum += saveLearningCondition(situationList);
			if(situationList.size() < size) {
				startNum += situationList.size();
				break;
			}
			startNum += size;
		}
		logger.info("auto数据共{}条，录入{}条", startNum, resultNum);
		return resultNum;
	}

	/**
	 * 统计手动匹配知识点批改结果中的错题数量和题目总量
	 *
	 * @param date 指定统计的时间，为空则统计所有
	 * @return 更新的记录数
	 */
	public Integer calManualDataByDate(String date) {
		if(Strings.isNullOrEmpty(date)) {
			//遍历所有manual数据存入学情表
		}
		Integer startNum = 0;
		Integer size = 500;
		Integer resultNum = 0;
		while(true) {
			List<Map<String, Object>> situationList = conditionDao.queryManualHwMoudleByDate(startNum, size, date);
			resultNum += saveLearningCondition(situationList);
			if(situationList.size() < size) {
				startNum += situationList.size();
				break;
			}
			startNum += size;
		}
		logger.info("manual数据共{}条，录入{}条", startNum, resultNum);
		return resultNum;
	}
	
	
	public void calByDate(String date) {
		Integer situationNum = calManualDataByDate(date);
		situationNum += calAutoDataByDate(date);
		//统计各个科目-->年级-->学期下的维度（此版本没有计入教材版本）
		statisticsDimension();
		//加入统计平均值
		Integer avgNum = statisticsDimensionAvg();
		logger.info("共统计{}条数据，共生成{}条平均数据", situationNum, avgNum);
	}

	@Override
	public Integer statisticsDimension() {
		//先检查redis中是否有值（暂时每天都更新redis中的维度数据）
//		boolean redisNotExists = false;
//		if(!redisUtil.exists(ConchConstants.SITUATION_DIMENSION_REDIS_KEY)) {
//			redisNotExists = true;
//		}
		redisUtil.del(ConchConstants.SITUATION_DIMENSION_REDIS_KEY);
		Integer num = 0;
		//年级
		for(GradeEnum gradeEnum : GradeEnum.values()) {
			//学期
			for(int i = 1; i < 3; i ++) {
				//科目
				for(String courseName : CourseConstant.subjectList) {
					//教材版本
					for(int j = 1; j < 6; j ++) {
						List<String> dimensionList = conditionDao.queryAllDimensionByGradeCourseSemes(gradeEnum.getGrade(), i, courseName, j);
						String field = ConchConstants.SITUATION_DIMENSION_REDIS_FIELD_PREFIX + gradeEnum.getIndex() + i + courseName + j ;
						redisUtil.hset(ConchConstants.SITUATION_DIMENSION_REDIS_KEY, field, JSON.toJSONString(dimensionList));
					}
//						redisUtil.expire(ConchConstants.SITUATION_DIMENSION_REDIS_KEY, 24 * 60 * 60);
//					}
				}
			}
		}
		return num;
	}
	
	public Integer statisticsDimensionAvg() {
		//studentId 为null时唯一索引失效，平均值每次重新计算
		conditionDao.deleteAllAvg();
		Integer num = 0;
		//年级
		for(GradeEnum gradeEnum : GradeEnum.values()) {
			//学期
			for(int i = 1; i < 3; i ++) {
				//科目
				for(String courseName : CourseConstant.subjectList) {
					//统计平均值，根据年纪、学期、科目
					List<Map<String, Object>> avgList = conditionDao.getAvgCountByDimension(gradeEnum.getGrade(), i, courseName);
					num += saveLearningCondition(avgList);
				}
			}
		}
		return num;
	}

	/**
	 * 按年级来计算学生数据
	 * @param vipId
	 * @param grade
	 */
	public Integer calByGrade(String vipId, String grade) {
		//科目遍历
		Integer num = 0;
		for(String courseName : CourseConstant.subjectList) {
			//自动批阅数据处理
			Integer autoConditions = autoHomework(vipId, grade, courseName);
			//手动批阅数据处理
			Integer manualConditions = manualHomework(vipId, grade,courseName);
			num += (autoConditions + manualConditions);
			logger.info("学生：{}，科目：{}，年级：{}，数量：{}", vipId, courseName, grade, (autoConditions + manualConditions));
		};
		return num;
	}
	
	public Integer manualHomework(String vipId, String grade, String courseName) {
		//知识维度
		List<Map<String, Object>> knowList = conditionDao.queryManualHomeworkKnowMoudle(vipId, grade, courseName);
		return saveLearningCondition(knowList, vipId, courseName, grade);
		//知识点掌握
//		List<DimesionCount> pointList = conditionDao.queryManualPointData(vipId, grade, courseName);
	}
	public Integer autoHomework(String vipId, String grade, String courseName){
		//知识维度
		List<Map<String, Object>> knowList = conditionDao.queryAutoHomeworkKnowMoudle(vipId, grade, courseName);
		return saveLearningCondition(knowList, vipId, courseName, grade);
//		List<DimesionCount> skillList = new ArrayList<DimesionCount>();
		//知识点掌握
//		List<DimesionCount> pointList = conditionDao.queryAutoPointData(vipId, grade, courseName);
	}

	/**
	 * 累加错题和题目总数，并计算正确率
	 */
	public Integer saveLearningCondition(List<Map<String, Object>> list) {
		Integer flag = 0;
		for(Map<String, Object> dimesionCount : list) {
			if(Strings.isNullOrEmpty((String)dimesionCount.get("dimension"))) {
				continue;
			}
			LearningSituation situation = new LearningSituation();
			situation.setCourseName((String)dimesionCount.get("coursename"));
			situation.setCreateTime(new Date());
			situation.setDimension((String)dimesionCount.get("dimension"));
			situation.setGrade((String)dimesionCount.get("grade"));
			situation.setStudentId((String)dimesionCount.get("vipid"));
			situation.setTotalCount(Integer.parseInt(String.valueOf(dimesionCount.get("totalcount") == null ? 0 : dimesionCount.get("totalcount"))));
			situation.setWrongCount(Integer.parseInt(String.valueOf(dimesionCount.get("wrongcount") == null ? 0 : dimesionCount.get("wrongcount"))));
			situation.setType(dimesionCount.get("type") != null ? (Integer)dimesionCount.get("type") : ConchConstants.SITUATION_TYPE_KONWLEDGE);
			situation.setId(CodecUtil.createUUID());
			situation.setRemark((String)dimesionCount.get("dimension"));
			situation.setSemester((Integer)dimesionCount.get("semester"));
			if(situation.getTotalCount() == 0) {
				situation.setDegreePercent(100);
			} else {
				Integer degree = new BigDecimal(situation.getWrongCount()).divide(new BigDecimal(situation.getTotalCount()), 2, BigDecimal.ROUND_HALF_UP).multiply(new BigDecimal(100)).intValue();
				situation.setDegreePercent(100 - degree);
			}
			situation.setSuggestId(getSuggestByDimension(situation.getDimension(), situation.getGrade(), situation.getWrongCount(), 
					situation.getTotalCount(), situation.getCourseName(), situation.getStudentId(), situation.getDegreePercent()));
			flag += conditionDao.insertOrUpdateLearnSituation(situation);
		};
		return flag;
	}
	
	@Deprecated
	public Integer saveLearningCondition(List<Map<String, Object>> dimesionCountList, String vipId, String courseName, String grade) {
		Integer flag = 0;
		for(Map<String, Object> dimesionCount : dimesionCountList) {
			if(Strings.isNullOrEmpty((String)dimesionCount.get("name"))) {
				continue;
			}
			LearningSituation situation = new LearningSituation();
			situation.setCourseName(courseName);
			situation.setCreateTime(new Date());
			situation.setDimension((String)dimesionCount.get("name"));
			situation.setGrade(grade);
			situation.setStudentId(vipId);
			situation.setTotalCount(Integer.parseInt(String.valueOf(dimesionCount.get("totalcount") == null ? 0 : dimesionCount.get("totalcount"))));
			situation.setWrongCount(Integer.parseInt(String.valueOf(dimesionCount.get("wrongcount") == null ? 0 : dimesionCount.get("wrongcount"))));
			situation.setType(dimesionCount.get("type") != null ? (Integer)dimesionCount.get("type") : ConchConstants.SITUATION_TYPE_KONWLEDGE);
			situation.setId(CodecUtil.createUUID());
			situation.setRemark((String)dimesionCount.get("name"));
			situation.setSemester((Integer)dimesionCount.get("semester"));
			Integer degree = new BigDecimal(situation.getWrongCount()).divide(new BigDecimal(situation.getTotalCount()), 2, BigDecimal.ROUND_HALF_UP).multiply(new BigDecimal(100)).intValue();
			situation.setDegreePercent(100 - degree);
			situation.setSuggestId(getSuggestByDimension((String)dimesionCount.get("name"), grade, situation.getWrongCount(), situation.getTotalCount(), courseName,
					vipId, situation.getDegreePercent()));
			flag += conditionDao.insertOrUpdateLearnSituation(situation);
		};
		return flag;
	}
	
	public String getSuggestByDimension(String dimension, String grade, Integer wrongCount, Integer totalCount, String courseName, String vipId, Integer degree ) {
		if(Strings.isNullOrEmpty(vipId)) {
			return "";
		}
		if(Strings.isNullOrEmpty(grade)) {
			grade = conditionDao.queryGradeByVipId(vipId);
		}
		String suggestStr = conditionDao.querySuggestByDimension(dimension, grade, degree, courseName, 1);
		return suggestStr;
	}
	@Override
	public List<String> getDimensionList(Integer grade, Integer semester, String courseName, Integer banben) {
		if(!redisUtil.exists(ConchConstants.SITUATION_DIMENSION_REDIS_KEY)) {
    		statisticsDimension();
    	}
    	String dimenListStr = redisUtil.hget(ConchConstants.SITUATION_DIMENSION_REDIS_KEY, 
    			ConchConstants.SITUATION_DIMENSION_REDIS_FIELD_PREFIX + grade + semester + courseName + banben);
    	return JSONArray.parseArray(dimenListStr, String.class);
	}

	@Override
	public int countDailyHomeworkData(Integer offset) {
		int totalCount = 0;

		if (offset != null) {
			if (offset >= 0) {
				//只能生成昨日及以前的数据
				return totalCount;
			}

			//统计指定日期的数据
			totalCount = this.countAndSaveDailyHomeworkData(offset);
			logger.info("统计指定日期作业数据，共生成{}条数据", totalCount);
		} else {
			//统计所有日期
			Date minCreateTime = homeworkMapper.findMinCreateTime();
			int days = DateUtil.daysBetween(minCreateTime, new Date());
			while (days > 0) {
				totalCount += this.countAndSaveDailyHomeworkData(days * -1);
				days --;
			}
		}

		return totalCount;
	}

	@Override
	public int countMonthlyHomeworkData(Integer offset, Boolean calculateScore, Boolean notify) {
		int totalCount = 0;

		if (offset != null) {
			if (offset >= 0) {
				//只能生成上一月及以前的数据
				return totalCount;
			}

			//统计指定月份的数据
			totalCount = this.countAndSaveMonthlyHomeworkData(offset, calculateScore, notify);
			logger.info("统计指定月份作业数据，共生成{}条数据", totalCount);
		} else {
			//统计所有月份
			Date minCreateTime = homeworkMapper.findMinCreateTime();
			Calendar bef = Calendar.getInstance();
			Calendar aft = Calendar.getInstance();
			bef.setTime(minCreateTime);
			aft.setTime(new Date());
			int result = aft.get(Calendar.MONTH) - bef.get(Calendar.MONTH);
			int month = (aft.get(Calendar.YEAR) - bef.get(Calendar.YEAR)) * 12;
			int months = Math.abs(month + result);
			while (months > 0) {
				totalCount += this.countAndSaveMonthlyHomeworkData(months * -1, calculateScore, notify);
				months --;
			}
		}

		return totalCount;
	}

	@Override
	public int pushWeeklyGrowthReportNotify() {
		final int[] pushCount = {0};

		LocalDate today = LocalDate.now();
		Date beginDateTime = DateUtil.getEndOfLastWeek(new Date());
		if (!Objects.equals(today.getDayOfWeek().getValue(), DayOfWeek.SUNDAY.getValue())) {
			beginDateTime = DateUtil.addDays(beginDateTime, -7);
		}
		Date endDateTime = DateUtil.getDateAfter(beginDateTime, 6);
		String beginDateTimeStr = DateUtil.format_yyyyMMdd(beginDateTime) + " 00:00:00";
		String endDateTimeStr = DateUtil.format_yyyyMMdd(endDateTime) + " 23:59:59";

		int pageNo = 1;
		int pageSize = 500;

		do {
			List<String> vipIdList = homeworkMapper.findVipIdNeedPushWeeklyNotify(new Page<>(pageNo, pageSize),
					beginDateTimeStr, endDateTimeStr);
			for (String vipId : vipIdList) {
				try {
					LibVip libVip = libVipService.getById(vipId);
					if (libVip == null) {
						logger.warn("推送周成长报告通知时，发现学生(vipId=[{}])不存在", vipId);
						continue;
					}
					String parentUserId = libVip.getUserId();
					User parentUser = userService.getById(parentUserId);
					if (parentUser == null) {
						logger.warn("推送周成长报告通知时，发现家长(userId=[{}])不存在", parentUserId);
						continue;
					}

					if (StringUtils.isNotBlank(parentUser.getAccount())) {
						//公众号推送家长
						String firstStr = String.format("尊敬的%s家长，您名下的孩子%s上周的学习成长报告出炉啦！",
								parentUser.getUserName(), libVip.getChildName());

						List<StudentWeeklyReport> studentWeeklyReportList =
								learningGrowthReportMapper.findWeeklyReportByVipIdAndBeginDate(vipId,
										DateUtil.format_yyyyMMdd(beginDateTime));

						int totalNum = 0;
						int goodNum = 0;
						int notFirmNum = 0;
						int notMasteredNum = 0;
						for (StudentWeeklyReport studentWeeklyReport : studentWeeklyReportList) {
							totalNum += Optional.ofNullable(studentWeeklyReport.getTotalKnowledgeNum()).orElse(0);
							goodNum += Optional.ofNullable(studentWeeklyReport.getMasterWellKnowledgeNum()).orElse(0);
							notFirmNum += Optional.ofNullable(studentWeeklyReport.getMasterInfirmlyKnowledgeNum()).orElse(0);
							notMasteredNum += Optional.ofNullable(studentWeeklyReport.getNotMasteredKnowledgeNum()).orElse(0);
						}
						String keyword2 = "";
						if (totalNum > 0) {
							keyword2 += String.format("本周一共学了%s个知识点", totalNum);
						}
						if (goodNum > 0) {
							keyword2 += goodNum + "个知识点掌握良好";
						}
						if (notFirmNum > 0) {
							if (keyword2.length() > 0) {
								keyword2 += "，";
							}
							keyword2 += notFirmNum + "个知识点掌握不牢固";
						}
						if (notMasteredNum > 0) {
							if (keyword2.length() > 0) {
								keyword2 += "，";
							}
							keyword2 += notMasteredNum + "个知识点未掌握";
						}
						String url = String.format("pages/learning_report/learning_report?status=1&vipId=%s", vipId);
						Miniprogram miniprogram = new Miniprogram();
						miniprogram.setAppid(WxUtil.map.get("jzdAppId"));
						miniprogram.setPagepath(url);
						Map<String, String> dataMap = new HashMap<>();
						dataMap.put("first", firstStr);
						dataMap.put("keyword1", libVip.getChildName());
						dataMap.put("keyword2", keyword2);
						SendTemplateShort sendTemplateShort = new SendTemplateShort(parentUser.getAccount(),
								"OPENTM415124923", null, dataMap, miniprogram);
						pushTemplateService.saveAndSendPushWxTemplate(sendTemplateShort);
					} else {
						logger.warn("家长(userId=[{}])openid为空", parentUserId);
					}

					//推送家长端app
					String content = String.format("尊敬的%s家长，您名下的孩子%s上周的学习成长报告已经生成，快来查看吧。",
							parentUser.getUserName(), libVip.getChildName());
					sconXingeService.asynPushGrowthReportThreadTask(parentUserId, vipId, content,
							"WEEKLY_GROWTH_REPORT_NOTICE");

					pushCount[0]++;
				} catch (Exception e) {
					logger.error("推送周报报通知失败，vipId=[{}]，e->{}", vipId, e.toString());
				}
			}

			if (vipIdList.size() < pageSize) {
				//数据数小于pageSize，已没有后续数据，跳出循环
				break;
			}

			pageNo ++;
		} while (true);

		logger.info("推送周成长报告通知：{}条", pushCount[0]);

		return pushCount[0];
	}

	@Override
	public int generateWeeklyReport(String beginDate, String endDate, Boolean calculateScore) {
		String beginDateTimeStr = beginDate + " 00:00:00";
		String endDateTimeStr = endDate + " 23:59:59";
		Date beginDateTime = DateUtil.parse_yyyyMMddHHmmss(beginDateTimeStr);
		Date endDateTime = DateUtil.parse_yyyyMMddHHmmss(endDateTimeStr);

		int pageNo = 1;
		int pageSize = 500;
		Date generateTime = new Date();
		int generateCount = 0;

		List<StudentWeeklyReport> studentWeeklyReportList;
		List<TrainingRecommendQuestion> trainingRecommendQuestionList = new ArrayList<>();
		TrainingRecommendQuestion trainingRecommendQuestion;
		do {
			studentWeeklyReportList = homeworkMapper.countWeeklyHomeworkData(new Page<>(pageNo, pageSize), beginDateTimeStr, endDateTimeStr);
			for (StudentWeeklyReport studentWeeklyReport : studentWeeklyReportList) {
				studentWeeklyReport.setId(IdWorker.generateId());
				studentWeeklyReport.setBeginDate(beginDate);
				studentWeeklyReport.setEndDate(endDate);
				studentWeeklyReport.setCreateTime(generateTime);

				// 计算作业正确率
				int accuracyRate = 0;
				if (studentWeeklyReport.getTotalCount() > 0) {
					accuracyRate = (studentWeeklyReport.getTotalCount() - studentWeeklyReport.getWrongCount()) * 100 / studentWeeklyReport.getTotalCount();
				}
				studentWeeklyReport.setAccuracyRate(accuracyRate);
				// 计算作业学习得分
				int add = 0;
				if(accuracyRate>=70 && accuracyRate<=79){
					add = 1;
				}else if(accuracyRate>=80 && accuracyRate<=89){
					add = 3;
				}else if(accuracyRate>=89){
					add = 5;
				}
				studentWeeklyReport.setAddIntegral(add);
				if (calculateScore) {
					rankingService.saveRankIntegral(studentWeeklyReport.getVipId(),add,4,studentWeeklyReport.getSubject());
				}
				List<KnowledgePointProportionDTO> knowledgePointProportionList =
						kmtExerciseMapperService.getKnowledgePointProportionList(studentWeeklyReport.getVipId(),
								studentWeeklyReport.getSubject(), beginDateTime, endDateTime);
				this.setKnowledgePointProportion(studentWeeklyReport, knowledgePointProportionList);

				//统计该周知识点
				List<ModuleDTO> moduleList = kmtExerciseMapperService.getModuleListV2(studentWeeklyReport.getVipId(),
						studentWeeklyReport.getSubject(), beginDateTime, endDateTime, null);
				int totalKnowledgeNum = 0;
				int masterWellKnowledgeNum = 0;
				int masterInfirmlyKnowledgeNum = 0;
				int notMasteredKnowledgeNum = 0;
				if (!moduleList.isEmpty()) {
					for (ModuleDTO moduleDTO : moduleList) {
						for (KnowledgePointMasteryDTO knowledgePointMasteryDTO : moduleDTO.getKnowledgePointMasteryList()) {
							totalKnowledgeNum ++;
							Double mastery = knowledgePointMasteryDTO.getMastery();
							if (mastery < 0.6) {
								//未掌握
								notMasteredKnowledgeNum ++;
							} else if (mastery < 0.85) {
								//掌握不牢固
								masterInfirmlyKnowledgeNum++;
							} else {
								//掌握良好
								masterWellKnowledgeNum ++;
							}
						}
					}
				}
				studentWeeklyReport.setTotalKnowledgeNum(totalKnowledgeNum);
				studentWeeklyReport.setMasterWellKnowledgeNum(masterWellKnowledgeNum);
				studentWeeklyReport.setMasterInfirmlyKnowledgeNum(masterInfirmlyKnowledgeNum);
				studentWeeklyReport.setNotMasteredKnowledgeNum(notMasteredKnowledgeNum);

				studentWeeklyReport.setModuleJson(JSON.toJSONString(moduleList));

				if (CollectionUtils.isNotEmpty(moduleList)) {
					// 生成提分训练
					trainingRecommendQuestion = this.generateTrainingRecommendQuestion(studentWeeklyReport, moduleList);
					if (trainingRecommendQuestion != null) {
						trainingRecommendQuestionList.add(trainingRecommendQuestion);
					}
				}
			}

			if (!studentWeeklyReportList.isEmpty()) {
				//周报记录存入数据库
				pagingHibernateJdbcDao.batchInsert(studentWeeklyReportList, pageSize);
				generateCount += studentWeeklyReportList.size();
			}

			if (CollectionUtils.isNotEmpty(trainingRecommendQuestionList)) {
				pagingHibernateJdbcDao.batchInsert(trainingRecommendQuestionList, 500);
				trainingRecommendQuestionList.clear();
			}

			if (studentWeeklyReportList.size() < pageSize) {
				//数据数小于pageSize，已没有后续数据，跳出循环
				break;
			}

			pageNo ++;
		} while (true);

		logger.info("{}至{}的周报生成完成，共{}条", beginDateTimeStr, endDateTimeStr, generateCount);

		return generateCount;
	}

	private TrainingRecommendQuestion generateTrainingRecommendQuestion(StudentWeeklyReport studentWeeklyReport,
																		List<ModuleDTO> moduleList) {
		if (CollectionUtils.isEmpty(moduleList)) {
			logger.warn("周报没有知识点studentWeeklyReportId={}", studentWeeklyReport.getId());
			return null;
		}

		List<KnowledgePointMasteryDTO> knowledgePointMasteryDTOList = new ArrayList<>();
		for (ModuleDTO moduleDTO : moduleList) {
			if (Objects.equals(1, moduleDTO.getType())) {
				knowledgePointMasteryDTOList.addAll(moduleDTO.getKnowledgePointMasteryList());
			}
		}
		knowledgePointMasteryDTOList.sort(Comparator.comparingInt(KnowledgePointMasteryDTO::getWrongCount).reversed());
		List<KnowHowDetailDTO> knowHowDetailList;
		List<String> tempKnowledgeIdList;
		Map<String, List<String>> knowledgeMap = new LinkedHashMap<>();
		List<String> excludedHowDetailIdList = tokenManager.getList(Constant.EXCLUDED_HOW_DETAIL_ID_OF_TRAIN_RECOMMEND, String.class);
		for (KnowledgePointMasteryDTO knowledgePointMasteryDTO : knowledgePointMasteryDTOList) {
			if (knowledgePointMasteryDTO.getMastery() == null) {
				continue;
			}
			knowHowDetailList = knowledgePointMasteryDTO.getKnowHowDetailList();
			if (CollectionUtils.isEmpty(knowHowDetailList)) {
				continue;
			}
			tempKnowledgeIdList = knowHowDetailList.stream()
					.map(KnowHowDetailDTO::getKnowHowDetailId)
					.distinct()
					.collect(Collectors.toList());
			if (CollectionUtils.isNotEmpty(excludedHowDetailIdList)) {
				tempKnowledgeIdList.removeAll(excludedHowDetailIdList);
			}
			if (tempKnowledgeIdList.isEmpty()) {
				continue;
			}
			knowledgeMap.put(knowledgePointMasteryDTO.getKnowledgeName(), tempKnowledgeIdList);
		}

		if (knowledgeMap.size() < 1) {
			return null;
		}

		// 调用AI接口，查询推荐题
		Map<String, Object> bodyMap = new HashMap<>();
		bodyMap.put("knowledge", knowledgeMap);
		bodyMap.put("subject", studentWeeklyReport.getSubject());
		bodyMap.put("quantity", 3);
		JSONObject queryResult = AIServiceUtils.queryTrainingRecommendedQuestions(bodyMap);
		if (queryResult == null) {
			redisUtil.sadd(Constant.TRAINING_RECOMMEND_GENERATE_FAILED_WEEKLY_REPORT_IDS, studentWeeklyReport.getId());
			return null;
		}
		JSONArray knowledgeJSONArray = queryResult.getJSONArray("knowledge");
		if (knowledgeJSONArray == null) {
			redisUtil.sadd(Constant.TRAINING_RECOMMEND_GENERATE_FAILED_WEEKLY_REPORT_IDS, studentWeeklyReport.getId());
			return null;
		}
		List<TrainRecommendKnowledgeDTO> knowledgeList = knowledgeJSONArray.toJavaList(TrainRecommendKnowledgeDTO.class);
		if (CollectionUtils.isNotEmpty(knowledgeList)) {
			knowledgeList = knowledgeList.stream()
					.filter(it -> CollectionUtils.isNotEmpty(it.getQuestionList()))
					.collect(Collectors.toList());
			int bigQuestionCount = 0;
			int smallQuestionCount = 0;
			for (TrainRecommendKnowledgeDTO trainRecommendKnowledgeDTO : knowledgeList) {
				bigQuestionCount += trainRecommendKnowledgeDTO.getQuestionList().size();
				for (TrainRecommendQuestionDTO trainRecommendQuestionDTO : trainRecommendKnowledgeDTO.getQuestionList()) {
					smallQuestionCount += trainRecommendQuestionDTO.getChildList().size();
				}
			}

			TrainingRecommendQuestion trainingRecommendQuestion = new TrainingRecommendQuestion();
			trainingRecommendQuestion.setStudentWeeklyReportId(studentWeeklyReport.getId());
			trainingRecommendQuestion.setVipId(studentWeeklyReport.getVipId());
			trainingRecommendQuestion.setBeginDate(studentWeeklyReport.getBeginDate());
			trainingRecommendQuestion.setSubject(studentWeeklyReport.getSubject());
			try {
				// 为避免相关json解析异常导致上层调用方法异常产生bug，此处进行异常捕获并记录日志
				trainingRecommendQuestion.setKnowledgeListJson(JSON.toJSONString(knowledgeList));
			} catch (Exception e) {
				logger.error("studentWeeklyReportId=[{}], knowledgeList={}", studentWeeklyReport.getId(), knowledgeList);
				logger.error("生成题分训练时解析题目相关json字段异常", e);
				return null;
			}
			trainingRecommendQuestion.setBigQuestionCount(bigQuestionCount);
			trainingRecommendQuestion.setSmallQuestionCount(smallQuestionCount);
			trainingRecommendQuestion.setCreateTime(new Date());
			return trainingRecommendQuestion;
		}

		redisUtil.sadd(Constant.TRAINING_RECOMMEND_GENERATE_FAILED_WEEKLY_REPORT_IDS, studentWeeklyReport.getId());
		return null;
	}

	@Override
	public int prepareGenerateWeeklyReport(Integer offset, Boolean calculateScore) {
		int totalCount = 0;

		Date standardDate = DateUtil.getEndOfLastWeek(new Date());
		LocalDate today = LocalDate.now();
		if (Objects.equals(today.getDayOfWeek().getValue(), DayOfWeek.SUNDAY.getValue())) {
			standardDate = DateUtil.addDays(standardDate, 7);
		}

		if (offset != null) {
			if (offset >= 0) {
				//只能以周为单位，生成上一周及以前的数据
				return totalCount;
			}

			//统计指定周的周报
			Date beginDateTime = DateUtil.addDays(standardDate, offset * 7);
			Date endDateTime = DateUtil.addDays(beginDateTime, 6);
			totalCount = this.generateWeeklyReport(DateUtil.format_yyyyMMdd(beginDateTime), DateUtil.format_yyyyMMdd(endDateTime), calculateScore);
		} else {
			//统计所有日期
			String beginDateOfThisWeek = DateUtil.format_yyyyMMdd(standardDate);
			Date minCreateTime = homeworkMapper.findMinCreateTime();
			LocalDate minCreateDate = minCreateTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
			Date beginDateTime = DateUtil.getEndOfLastWeek(minCreateTime);
			if (Objects.equals(minCreateDate.getDayOfWeek().getValue(), DayOfWeek.SUNDAY.getValue())) {
				beginDateTime = DateUtil.addDays(beginDateTime, 7);
			}
			if (beginDateOfThisWeek.compareTo(DateUtil.format_yyyyMMdd(beginDateTime)) <= 0) {
				//已是当前一周，不再统计生成，跳出循环
				return totalCount;
			}
			Date endDateTime = DateUtil.addDays(beginDateTime, 6);
			boolean isContinue = true;
			while (isContinue) {
				totalCount += this.generateWeeklyReport(DateUtil.format_yyyyMMdd(beginDateTime), DateUtil.format_yyyyMMdd(endDateTime), calculateScore);

				beginDateTime = DateUtil.addDays(beginDateTime, 7);
				endDateTime = DateUtil.addDays(beginDateTime, 6);
				if (beginDateOfThisWeek.compareTo(DateUtil.format_yyyyMMdd(beginDateTime)) <= 0) {
					//已是当前一周，不再统计生成，跳出循环
					isContinue = false;
				}
			}
		}

		return totalCount;
	}

	@Override
	public int updateStudentWeeklyReportKnowledgeSituation() {
		int updateCount = 0;
		int pageNo = 1;
		int pageSize = 500;
		List<StudentWeeklyReport> studentWeeklyReportList;

		do {
			studentWeeklyReportList = learningGrowthReportMapper.findWeeklyReport4UpdateKnowledgeSituation(new Page<>(pageNo, pageSize));

			for (StudentWeeklyReport studentWeeklyReport : studentWeeklyReportList) {
				Date beginDateTime = DateUtil.parse_yyyyMMdd(studentWeeklyReport.getBeginDate());
				Date endDateTime = DateUtil.parse_yyyyMMdd(studentWeeklyReport.getEndDate());

				List<KnowledgePointProportionDTO> knowledgePointProportionList =
						kmtExerciseMapperService.getKnowledgePointProportionList(studentWeeklyReport.getVipId(),
								studentWeeklyReport.getSubject(), beginDateTime, endDateTime);
				this.setKnowledgePointProportion(studentWeeklyReport, knowledgePointProportionList);

				//统计该周知识点
				List<ModuleDTO> moduleList = kmtExerciseMapperService.getModuleListV2(studentWeeklyReport.getVipId(),
						studentWeeklyReport.getSubject(), beginDateTime, endDateTime, null);
				studentWeeklyReport.setModuleJson(JSON.toJSONString(moduleList));
			}

			if (!studentWeeklyReportList.isEmpty()) {
				pagingHibernateJdbcDao.batchUpdate(studentWeeklyReportList);
				updateCount += studentWeeklyReportList.size();
			}

			pageNo ++;
		} while (studentWeeklyReportList.size() >= pageSize);

		return updateCount;
	}

	/**
	 * 知识点超过7个或者存在占比小于4%时，需要放入其他
	 *
	 * @param studentWeeklyReport 周报
	 * @param knowledgePointProportionList 知识点占比列表
	 */
	private void setKnowledgePointProportion(StudentWeeklyReport studentWeeklyReport, List<KnowledgePointProportionDTO> knowledgePointProportionList) {
		boolean generateOthers = knowledgePointProportionList.size() > 7 ||
				knowledgePointProportionList.stream().anyMatch(it -> it.getProportion().compareTo(BigDecimal.valueOf(0.04)) < 0);

		if (generateOthers) {
			//超过8个知识点时，只展示前7个（占比高的），剩余的合并为一个命名为其他，并返回其中所有的知识点占比详情
			List<KnowledgePointProportionDTO> finalKnowledgePointProportionList = new LinkedList<>();
			List<KnowledgePointProportionDTO> otherKnowledgePointProportionList = new LinkedList<>();
			knowledgePointProportionList.sort((o1, o2) -> -o1.getProportion().compareTo(o2.getProportion()));
			KnowledgePointProportionDTO otherKnowledgePointProportionDTO = new KnowledgePointProportionDTO();
			otherKnowledgePointProportionDTO.setKnowledgeName("其他");
			BigDecimal otherKnowledgePointProportion = BigDecimal.ZERO;
			for (KnowledgePointProportionDTO knowledgePointProportionDTO : knowledgePointProportionList) {
				if (finalKnowledgePointProportionList.size() < 7 &&
						knowledgePointProportionDTO.getProportion().compareTo(BigDecimal.valueOf(0.04)) >= 0) {
					finalKnowledgePointProportionList.add(knowledgePointProportionDTO);
				} else {
					otherKnowledgePointProportion = otherKnowledgePointProportion.add(knowledgePointProportionDTO.getProportion());
					otherKnowledgePointProportionList.add(knowledgePointProportionDTO);
				}
			}
			otherKnowledgePointProportionDTO.setProportion(otherKnowledgePointProportion);
			finalKnowledgePointProportionList.add(otherKnowledgePointProportionDTO);
			finalKnowledgePointProportionList.sort((o1, o2) -> -o1.getProportion().compareTo(o2.getProportion()));

			studentWeeklyReport.setKnowledgePointProportionJson(JSON.toJSONString(finalKnowledgePointProportionList));
			studentWeeklyReport.setOtherKnowledgePointProportionJson(JSON.toJSONString(otherKnowledgePointProportionList));

			return;
		}

		studentWeeklyReport.setKnowledgePointProportionJson(JSON.toJSONString(knowledgePointProportionList));
	}

	@Override
	public int updateMonthlyReportKnowledgeSituation() {
		int updateCount = 0;
		int pageNo = 1;
		int pageSize = 500;
		List<HomeworkCount> homeworkCountList;

		do {
			homeworkCountList = homeworkMapper.findMonthlyReport(new Page<>(pageNo, pageSize));

			if (!homeworkCountList.isEmpty()) {
				for (HomeworkCount homeworkCount : homeworkCountList) {
					Date beginDateTimeOfTargetMonth = DateUtil.parse_yyyyMMdd(homeworkCount.getHomeworkMonth() + "-01");
					Date endDateTimeOfTargetMonth = DateUtil.getEndOfThisMonth(beginDateTimeOfTargetMonth);
					List<SubjectKnowledgeInfoDTOV2> subjectKnowledgeInfoDTOV2List = new LinkedList<>();
					subjectKnowledgeInfoDTOV2List.add(kmtExerciseMapperService.getSubjectKnowledgeInfoDTOV2(homeworkCount.getVipId(), "语文", beginDateTimeOfTargetMonth, endDateTimeOfTargetMonth));
					subjectKnowledgeInfoDTOV2List.add(kmtExerciseMapperService.getSubjectKnowledgeInfoDTOV2(homeworkCount.getVipId(), "数学", beginDateTimeOfTargetMonth, endDateTimeOfTargetMonth));
					subjectKnowledgeInfoDTOV2List.add(kmtExerciseMapperService.getSubjectKnowledgeInfoDTOV2(homeworkCount.getVipId(), "英语", beginDateTimeOfTargetMonth, endDateTimeOfTargetMonth));
					homeworkCount.setSubjectKnowledgeInfoJson(JSON.toJSONString(subjectKnowledgeInfoDTOV2List));
				}

				pagingHibernateJdbcDao.batchUpdate(homeworkCountList);

				updateCount += homeworkCountList.size();
			}

			pageNo ++;
		} while (homeworkCountList.size() >= pageSize);

		return updateCount;
	}

	@Override
	public int generateWrongQuestionKnowledge(String homeworkId) {
		return this.countAndSaveWrongQuestionKnowledge(homeworkId);
	}

	@Override
	public int updateWrongQuestionKnowledgeQuestionStatus() {
		int count = 0;

		int pageNo = 1;
		int pageSize = 500;

		List<KnowledgeWrongQuestionCount> knowledgeWrongQuestionCountList;
		List<KnowledgeWrongQuestionCount> tempKnowledgeWrongQuestionCountList = new ArrayList<>();
		SearchResponse searchResponse;
		do {
			knowledgeWrongQuestionCountList = homeworkMapper.findWrongQuestionKnowledgeCount(new Page<>(pageNo, pageSize));

			for (KnowledgeWrongQuestionCount knowledgeWrongQuestionCount : knowledgeWrongQuestionCountList) {
				//错题没有原题时，需要检查是否有练习册题目
				SearchRequest searchRequest = new SearchRequest();
				searchRequest.indices("kmt_exercise_question_know");
				searchRequest.source(new SearchSourceBuilder());
				BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
				searchRequest.source().query(boolQueryBuilder);
				searchRequest.source().size(0);
				boolQueryBuilder.must(new TermQueryBuilder("version", knowledgeWrongQuestionCount.getKnowledgeVersion()));
				boolQueryBuilder.must(new QueryStringQueryBuilder(knowledgeWrongQuestionCount.getHowDetailId()).field("how_details_id.keyword"));
				logger.debug(searchRequest.source().toString());
				searchResponse = baseSearchService.search(searchRequest, RequestOptions.DEFAULT);
				if (searchResponse != null && searchResponse.getHits().getTotalHits().value > 0) {
					knowledgeWrongQuestionCount.setQuestionStatus(2);
					tempKnowledgeWrongQuestionCountList.add(knowledgeWrongQuestionCount);
				}
			}

			if (knowledgeWrongQuestionCountList.size() >= 500) {
				pagingHibernateJdbcDao.batchUpdate(tempKnowledgeWrongQuestionCountList);
				tempKnowledgeWrongQuestionCountList.clear();
				count += knowledgeWrongQuestionCountList.size();
			}

			pageNo ++;
		} while (knowledgeWrongQuestionCountList.size() >= pageSize);

		if (knowledgeWrongQuestionCountList.size() > 0) {
			pagingHibernateJdbcDao.batchUpdate(tempKnowledgeWrongQuestionCountList);
			count += knowledgeWrongQuestionCountList.size();
		}

		return count;
	}

    @Override
    public int updateMasteryOfSemesterKnowledge() {
		logger.info("更新学期知识点掌握熟练度开始...");

		int updateCount = 0;
		int pageNo = 1;
		int pageSize = 500;
		List<Map<String, Object>> homeworkInfoMapList;

        //更新或生成每个学生每个学期各知识点掌握情况
        int yesterdaySemester;
        Date nowDateTime = new Date();
		Date yesterdayDateTime = DateUtil.getDateAfter(nowDateTime, -1);
		LocalDate yesterdayDate = LocalDate.now().minusDays(1);
		String homeworkDate = DateUtil.format_yyyyMMdd(yesterdayDateTime);
		if (yesterdayDate.isAfter(LocalDate.of(yesterdayDate.getYear(), 2, 14)) &&
				yesterdayDate.isBefore(LocalDate.of(yesterdayDate.getYear(), 8, 1))) {
			yesterdaySemester = 2;
		} else {
			yesterdaySemester = 1;
		}
        //指定list长度避免扩容
        List<KnowledgeProficiency> knowledgeProficiencyList = new ArrayList<>(pageSize);
		List<KnowledgeBaseDto> knowledgeVersionList;
		KnowledgeProficiency knowledgeProficiency4Update;
        do {
			homeworkInfoMapList = homeworkMapper.findHomeworkInfoYesterday(new Page<>(pageNo, pageSize), homeworkDate);
			for (Map<String, Object> libVipIdSchoolMap : homeworkInfoMapList) {
				String vipId = libVipIdSchoolMap.get("vip_id").toString();
				String subject = libVipIdSchoolMap.get("subject").toString();
				String grade = libVipIdSchoolMap.get("grade").toString();
				knowledgeVersionList = knowledgeVersion.getVersionListBySubject(subject);
				for (KnowledgeBaseDto knowledgeBaseDto : knowledgeVersionList) {
					knowledgeProficiency4Update = this.getKnowledgeProficiency4Update(vipId, subject, grade,
							yesterdaySemester, knowledgeBaseDto.getType(), homeworkDate);
					if (knowledgeProficiency4Update != null) {
						knowledgeProficiencyList.add(knowledgeProficiency4Update);
					}
				}
			}

			if (!knowledgeProficiencyList.isEmpty()) {
				pagingHibernateJdbcDao.batchUpdate(knowledgeProficiencyList);
				updateCount += knowledgeProficiencyList.size();
				knowledgeProficiencyList.clear();
			}

			pageNo ++;
		} while (homeworkInfoMapList.size() >= pageSize);

		logger.info("更新学期知识点掌握熟练度结束，共{}条记录", updateCount);

        return updateCount;
    }

    @Override
    public void updateKnowHowDetailsSortQuestionStatus() {
        int offset = 0;
        int pageSize = 500;

//         kmtKnowledgeMapper.updateHowDetailsSort();

        List<Map<String, Object>> knowHowDetailsSortMapList;
        List<String> knowHowDetailsSortIdList;
        SearchRequest searchRequest = new SearchRequest("kmt_exercise_question_know")
				.source(new SearchSourceBuilder()
						.aggregation(AggregationBuilders.terms("groupByHowDetailSortId")
								.field("how_detail_sort_id.keyword")
								.size(pageSize)));
        SearchResponse searchResponse;
		Map<String, List<Map<String, Object>>> knowHowDetailsSortGroupById;
		ParsedStringTerms groupByHowDetailSortId;
        do {
			knowHowDetailsSortMapList = kmtKnowledgeMapper.findKnowHowDetailsSortMap(offset, pageSize);

			if (CollectionUtils.isNotEmpty(knowHowDetailsSortMapList)) {
				knowHowDetailsSortIdList = knowHowDetailsSortMapList.stream()
						.map(it -> it.get("id").toString())
						.collect(Collectors.toList());
				searchRequest.source()
						.size(0)
						.query(QueryBuilders.boolQuery()
								.mustNot(QueryBuilders.regexpQuery("real_exam_type", ".*?4.*?"))
								.filter(QueryBuilders.termsQuery("how_detail_sort_id.keyword", knowHowDetailsSortIdList))
								.filter(QueryBuilders.termsQuery("deal_status", Arrays.asList(4, 5, 8, 9, 10, 11)))
								.filter(QueryBuilders.existsQuery("book_id")));
				searchResponse = baseSearchService.search(searchRequest, null);
				if (searchResponse != null) {
					knowHowDetailsSortGroupById = knowHowDetailsSortMapList.stream()
							.collect(Collectors.groupingBy(it -> it.get("id").toString()));
					groupByHowDetailSortId = searchResponse.getAggregations().get("groupByHowDetailSortId");
					for (Terms.Bucket groupByHowDetailSortIdBucket : groupByHowDetailSortId.getBuckets()) {
						knowHowDetailsSortGroupById.get(groupByHowDetailSortIdBucket.getKeyAsString())
								.get(0).put("question_status", 1);
					}

					baseSearchService.updateOrInsertBatch("zyb_know_how_details_sort", knowHowDetailsSortMapList);
				}

				offset += pageSize;
			}
		} while (knowHowDetailsSortMapList.size() >= pageSize);
    }

	@Override
	public List<Map<String, Object>> findKnowledgeIdAndNameAndModule(String subject, String grade, int yesterdaySemester, int version) {
		SearchRequest searchRequest = new SearchRequest("zyb_know_how_details_sort");

		SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
		searchRequest.source(searchSourceBuilder);

		BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
		boolQueryBuilder.must(new QueryStringQueryBuilder(subject).field("course_name.keyword"));
		boolQueryBuilder.must(new QueryStringQueryBuilder(grade).field("grade.keyword"));
		boolQueryBuilder.must(new QueryStringQueryBuilder(String.valueOf(yesterdaySemester)).field("semester"));
		boolQueryBuilder.must(new QueryStringQueryBuilder(String.valueOf(version)).field("type"));
		boolQueryBuilder.must(new TermQueryBuilder("question_status", 1));
		searchSourceBuilder.query(boolQueryBuilder);

		String moduleColumnName;
		if ("英语".equals(subject)) {
			moduleColumnName = "module";
		} else {
			moduleColumnName = "two_module";
		}
		String [] includeColumns = {"how_details_id", "alias", moduleColumnName, "unit_name"};
		searchSourceBuilder.fetchSource(includeColumns, ArrayUtils.EMPTY_STRING_ARRAY);

		searchSourceBuilder.sort("unit_num", SortOrder.ASC);
		searchSourceBuilder.sort("num", SortOrder.ASC);

		searchSourceBuilder.size(1000);

		List<Map<String, Object>> resultMapList = new ArrayList<>(1000);
		logger.debug("------------>{}", searchRequest.source().toString());
		SearchResponse searchResponse = baseSearchService.search(searchRequest, null);
		if (searchResponse != null) {
			Map<String, Object> resultMap;
			Map<String, Object> sourceAsMap;
			for (SearchHit searchHit : searchResponse.getHits().getHits()) {
				sourceAsMap = searchHit.getSourceAsMap();
				resultMap = new HashMap<>();
				resultMap.put("how_details_id", sourceAsMap.get("how_details_id"));
				resultMap.put("alias", sourceAsMap.get("alias"));
				resultMap.put("module", sourceAsMap.get(moduleColumnName));
				resultMap.put("unit_name", sourceAsMap.get("unit_name"));
				resultMapList.add(resultMap);
			}
		}

		return resultMapList;
	}

    @Override
    public void refreshHomeworkCount(String homeworkId) {
		logger.info("刷新每日作业(homeworkId=[{}])批改统计数据", homeworkId);
		if (StringUtils.isBlank(homeworkId)) {
			return;
		}

		HomeworkCount newHomeworkCount = homeworkMapper.countHomeworkDataByHomeworkId(homeworkId);
		if (newHomeworkCount == null) {
			return;
		}

		Date now = new Date();
		Integer totalCount = newHomeworkCount.getTotalCount();
		Integer wrongCount = newHomeworkCount.getWrongCount();
		HomeworkCount oldHomeworkCount = homeworkMapper.findHomeworkCountByHomeworkId(homeworkId);
		if (oldHomeworkCount != null) {
			oldHomeworkCount.setWrongCount(wrongCount);
			oldHomeworkCount.setTotalCount(totalCount);
		} else {
			oldHomeworkCount = newHomeworkCount;
			oldHomeworkCount.setDataType(1);
			oldHomeworkCount.setCreateTime(now);
		}
		oldHomeworkCount.setUpdateTime(now);
		int accuracyRate = BigDecimal.valueOf((totalCount - wrongCount) * 100.0 / totalCount)
				.setScale(0, BigDecimal.ROUND_DOWN).intValue();
		oldHomeworkCount.setAccuracyRate(accuracyRate);
		pagingHibernateJdbcDao.batchUpdate(Collections.singletonList(oldHomeworkCount));
	}

	@Override
	public int regenerateTrainingRecommendQuestion(String studentWeeklyReportId) {
		if (StringUtils.isBlank(studentWeeklyReportId)) {
			return 0;
		}

		StudentWeeklyReport studentWeeklyReport = learningGrowthReportMapper.findWeeklyReportById(studentWeeklyReportId);
		if (studentWeeklyReport == null) {
			return 0;
		}

		TrainingRecommendQuestion newTrainingRecommendQuestion = this.generateTrainingRecommendQuestion(studentWeeklyReport,
				JSON.parseArray(studentWeeklyReport.getModuleJson(), ModuleDTO.class));
		if (newTrainingRecommendQuestion == null) {
			return 0;
		}
		TrainingRecommendQuestion oldTrainingRecommendQuestion = learningGrowthReportMapper.findTrainingRecommendQuestionByStudentWeeklyReportId(studentWeeklyReportId);
		if (oldTrainingRecommendQuestion != null) {
			oldTrainingRecommendQuestion.setKnowledgeListJson(newTrainingRecommendQuestion.getKnowledgeListJson());
			oldTrainingRecommendQuestion.setBigQuestionCount(newTrainingRecommendQuestion.getBigQuestionCount());
			oldTrainingRecommendQuestion.setSmallQuestionCount(newTrainingRecommendQuestion.getSmallQuestionCount());
			oldTrainingRecommendQuestion.setCreateTime(new Date());
		} else {
			oldTrainingRecommendQuestion = newTrainingRecommendQuestion;
		}
		pagingHibernateJdbcDao.batchUpdate(Collections.singletonList(oldTrainingRecommendQuestion));

		return 1;
	}

	@Override
	public List<KnowledgeProficiency> getKnowledgeProficiencyList(String vipId, int yesterdaySemester,
																  String homeworkDate) {

		int version;
		List<Map<String, Object>> knowledgeIdAndNameMapList;
		List<String> knowledgeIdList;
		List<Map<String, Object>> knowledgeWrongAndTotalQuestionCountMapList;
		List<Map<String, Object>> knowledgeRightQuestionWithExerciseCountMapList;
		KnowledgeProficiency knowledgeProficiency;
		List<KnowledgeProficiency> knowledgeProficiencyList = new LinkedList<>();
		KnowledgePointMasteryDTO knowledgePointMasteryDTO;
		List<KnowledgePointMasteryDTO> knowledgePointMasteryDTOList;
		List<KnowledgeBaseDto> knowledgeVersionList;
		Date nowDateTime = new Date();
		for (String subject : CourseConstant.MAIN_SUBJECT_LIST) {
			knowledgeVersionList = knowledgeVersion.getVersionListBySubject(subject);
			for (KnowledgeBaseDto knowledgeBaseDto : knowledgeVersionList) {
				version = knowledgeBaseDto.getType();
				for (String grade : Constant.GRADE_LIST) {
					for (Integer semester : Constant.SEMESTER_LIST) {
						if (yesterdaySemester != 0 && yesterdaySemester != semester) {
							continue;
						}
						knowledgeIdAndNameMapList = kmtKnowledgeMapper.findKnowledgeIdAndNameAndModule(subject, grade, semester,
								version);
						knowledgeProficiency = learningGrowthReportMapper.findSemesterReview(vipId, subject,
								grade, semester, version);
						if (knowledgeProficiency == null) {
							//初次生成此学期回顾知识点数据
							knowledgeProficiency = new KnowledgeProficiency();
							knowledgeProficiency.setVipId(vipId);
							knowledgeProficiency.setSubject(subject);
							knowledgeProficiency.setGrade(grade);
							knowledgeProficiency.setSemester(semester);
							knowledgeProficiency.setKnowledgeVersion(version);
							knowledgeProficiency.setKnowledgePointMasteryJson("[]");
							knowledgeProficiency.setCreateTime(nowDateTime);
						}
						knowledgePointMasteryDTOList = JSON.parseArray(knowledgeProficiency.getKnowledgePointMasteryJson(),
								KnowledgePointMasteryDTO.class);
						if (!knowledgeIdAndNameMapList.isEmpty()) {
							knowledgeIdList = knowledgeIdAndNameMapList.stream()
									.map(it -> it.get("how_details_id").toString())
									.distinct()
									.collect(Collectors.toList());
							knowledgeWrongAndTotalQuestionCountMapList =
									kmtKnowledgeMapper.countKnowledgeWrongAndTotalQuestion(vipId, subject, grade,
											semester, version, knowledgeIdList, homeworkDate);
							knowledgeRightQuestionWithExerciseCountMapList =
									kmtKnowledgeMapper.countKnowledgeRightQuestionWithExercise(vipId, subject,
											grade, semester, version, knowledgeIdList, homeworkDate);
							Map<String, List<Map<String, Object>>> knowledgeGroupByName = knowledgeIdAndNameMapList.stream()
									.collect(Collectors.groupingBy(it -> it.get("alias").toString()));
							for (Map.Entry<String, List<Map<String, Object>>> knowledgeEntry : knowledgeGroupByName.entrySet()) {
								Optional<KnowledgePointMasteryDTO> knowledgePointMasteryDTOOptional = knowledgePointMasteryDTOList.stream()
										.filter(it -> Objects.equals(it.getKnowledgeName(), knowledgeEntry.getKey()))
										.findFirst();
								if (knowledgePointMasteryDTOOptional.isPresent()) {
									knowledgePointMasteryDTO = knowledgePointMasteryDTOOptional.get();
								} else {
									knowledgePointMasteryDTO = new KnowledgePointMasteryDTO();
									knowledgePointMasteryDTO.setKnowledgeName(knowledgeEntry.getKey());
									knowledgePointMasteryDTOList.add(knowledgePointMasteryDTO);
								}
								List<String> howDetailsIdList = knowledgeEntry.getValue().stream()
										.map(it -> it.get("how_details_id").toString())
										.distinct()
										.collect(Collectors.toList());
								for (String howDetailsId : howDetailsIdList) {
									Optional<KnowHowDetailDTO> knowHowDetailDTOOptional = knowledgePointMasteryDTO.getKnowHowDetailList().stream()
											.filter(it -> Objects.equals(it.getKnowHowDetailId(), howDetailsId))
											.findFirst();
									if (!knowHowDetailDTOOptional.isPresent()) {
										KnowHowDetailDTO knowHowDetailDTO = new KnowHowDetailDTO();
										knowHowDetailDTO.setKnowHowDetailId(howDetailsId);
										knowHowDetailDTO.setKnowledgeVersion(String.valueOf(version));
										knowledgePointMasteryDTO.getKnowHowDetailList().add(knowHowDetailDTO);
									}
								}
								int wrongCount = knowledgeWrongAndTotalQuestionCountMapList.stream()
										.filter(it -> howDetailsIdList.contains(it.get("how_detail_id").toString()))
										.mapToInt(it -> Integer.parseInt(it.get("wrong_count").toString()))
										.sum();
								int totalCount = knowledgeWrongAndTotalQuestionCountMapList.stream()
										.filter(it -> howDetailsIdList.contains(it.get("how_detail_id").toString()))
										.mapToInt(it -> Integer.parseInt(it.get("total_count").toString()))
										.sum();
								int rightCount = knowledgeRightQuestionWithExerciseCountMapList.stream()
										.filter(it -> howDetailsIdList.contains(it.get("how_details_id").toString()))
										.mapToInt(it -> Integer.parseInt(it.get("right_count").toString()))
										.sum();
								knowledgePointMasteryDTO.setWrongCount(knowledgePointMasteryDTO.getWrongCount() + wrongCount);
								knowledgePointMasteryDTO.setTotalCount(knowledgePointMasteryDTO.getTotalCount() + totalCount + rightCount);
							}
							knowledgeProficiency.setKnowledgePointMasteryJson(JSON.toJSONString(knowledgePointMasteryDTOList));
						} else {
							//无相关知识点
							knowledgeProficiency.setKnowledgePointMasteryJson("[]");
						}
						knowledgeProficiency.setUpdateTime(nowDateTime);

						knowledgeProficiencyList.add(knowledgeProficiency);
					}
				}
			}
		}
		return knowledgeProficiencyList;
	}

	/**
	 * 获取新知识点掌握熟练度数据用于更新
	 * @param vipId 学生id
	 * @param subject 科目
	 * @param grade 年级
	 * @param yesterdaySemester 昨日所属学期（1上；2下）
	 * @param knowledgeVersion 知识点版本
	 * @param homeworkDate 昨日日期
	 * @return 知识点掌握熟练度数据
	 */
	private KnowledgeProficiency getKnowledgeProficiency4Update(String vipId, String subject, String grade,
																int yesterdaySemester, int knowledgeVersion, String homeworkDate) {

		KnowledgeProficiency knowledgeProficiency = learningGrowthReportMapper.findSemesterReview(vipId, subject, grade,
				yesterdaySemester, knowledgeVersion);
		Date nowDateTime = new Date();
		if (knowledgeProficiency != null &&
				Objects.equals(DateUtil.format_yyyyMMdd(knowledgeProficiency.getUpdateTime()), DateUtil.format_yyyyMMdd(nowDateTime))) {
			//已是最新数据不需要更新
			return null;
		}

		List<Map<String, Object>> knowledgeIdAndNameAndModuleMapList = this.findKnowledgeIdAndNameAndModule(subject,
				grade, yesterdaySemester, knowledgeVersion);
		boolean isUpdate = true;
		if (knowledgeProficiency == null) {
			//初次生成此学期回顾知识点数据
			knowledgeProficiency = new KnowledgeProficiency();
			knowledgeProficiency.setVipId(vipId);
			knowledgeProficiency.setSubject(subject);
			knowledgeProficiency.setGrade(grade);
			knowledgeProficiency.setSemester(yesterdaySemester);
			knowledgeProficiency.setKnowledgeVersion(knowledgeVersion);
			knowledgeProficiency.setNewKnowledgePointMasteryJson("[]");
			knowledgeProficiency.setCreateTime(nowDateTime);
			isUpdate = false;
		} else if (StringUtils.isBlank(knowledgeProficiency.getNewKnowledgePointMasteryJson())) {
			//旧版本的统计数据，重新生成新版本的数据
			knowledgeProficiency.setNewKnowledgePointMasteryJson("[]");
			isUpdate = false;
		}
		if (!knowledgeIdAndNameAndModuleMapList.isEmpty()) {
			List<String> knowledgeIdList = knowledgeIdAndNameAndModuleMapList.stream()
					.map(it -> it.get("how_details_id").toString())
					.distinct()
					.collect(Collectors.toList());
			List<Map<String, Object>> knowledgeWrongAndTotalQuestionCountMapList =
					kmtKnowledgeMapper.countKnowledgeWrongAndTotalQuestion(vipId, subject, grade, yesterdaySemester,
							knowledgeVersion, knowledgeIdList, isUpdate ? homeworkDate : "");
			List<Map<String, Object>> knowledgeRightQuestionWithExerciseCountMapList =
					kmtKnowledgeMapper.countKnowledgeRightQuestionWithExercise(vipId, subject, grade, yesterdaySemester,
							knowledgeVersion, knowledgeIdList, isUpdate ? homeworkDate : "");
			Map<String, List<Map<String, Object>>> knowledgeGroupByModuleMap = knowledgeIdAndNameAndModuleMapList.stream()
					.collect(Collectors.groupingBy(it -> it.get("unit_name").toString()));
			ModuleDTO moduleDTO;
			List<ModuleDTO> moduleDTOList = JSON.parseArray(knowledgeProficiency.getNewKnowledgePointMasteryJson(), ModuleDTO.class);
			Map<String, List<Map<String, Object>>> knowledgeGroupByNameMap;
			KnowledgePointMasteryDTO knowledgePointMasteryDTO;
			List<KnowledgePointMasteryDTO> knowledgePointMasteryDTOList;
			for (Map.Entry<String, List<Map<String, Object>>> knowledgeGroupByModuleEntry : knowledgeGroupByModuleMap.entrySet()) {
				Optional<ModuleDTO> moduleDTOOptional = moduleDTOList.stream()
						.filter(it -> Objects.equals(it.getModule(), knowledgeGroupByModuleEntry.getKey()))
						.findFirst();
				if (moduleDTOOptional.isPresent()) {
					moduleDTO = moduleDTOOptional.get();
				} else {
					moduleDTO = new ModuleDTO();
					moduleDTO.setModule(knowledgeGroupByModuleEntry.getKey());
					moduleDTOList.add(moduleDTO);
				}

				// 把组内同名知识点归为一组
				knowledgeGroupByNameMap = knowledgeGroupByModuleEntry.getValue().stream()
						.collect(Collectors.groupingBy(it -> it.get("alias").toString(), LinkedHashMap::new, Collectors.toList()));

				// 计算每组知识点掌握情况
				knowledgePointMasteryDTOList = moduleDTO.getKnowledgePointMasteryList();
				for (Map.Entry<String, List<Map<String, Object>>> knowledgeEntry : knowledgeGroupByNameMap.entrySet()) {
					Optional<KnowledgePointMasteryDTO> knowledgePointMasteryDTOOptional = knowledgePointMasteryDTOList.stream()
							.filter(it -> Objects.equals(it.getKnowledgeName(), knowledgeEntry.getKey()))
							.findFirst();
					if (knowledgePointMasteryDTOOptional.isPresent()) {
						knowledgePointMasteryDTO = knowledgePointMasteryDTOOptional.get();
					} else {
						knowledgePointMasteryDTO = new KnowledgePointMasteryDTO();
						knowledgePointMasteryDTO.setKnowledgeName(knowledgeEntry.getKey());
						knowledgePointMasteryDTOList.add(knowledgePointMasteryDTO);
					}
					List<String> howDetailsIdList = knowledgeEntry.getValue().stream()
							.map(it -> it.get("how_details_id").toString())
							.distinct()
							.collect(Collectors.toList());
					for (String howDetailsId : howDetailsIdList) {
						Optional<KnowHowDetailDTO> knowHowDetailDTOOptional = knowledgePointMasteryDTO.getKnowHowDetailList().stream()
								.filter(it -> Objects.equals(it.getKnowHowDetailId(), howDetailsId))
								.findFirst();
						if (!knowHowDetailDTOOptional.isPresent()) {
							KnowHowDetailDTO knowHowDetailDTO = new KnowHowDetailDTO();
							knowHowDetailDTO.setKnowHowDetailId(howDetailsId);
							knowHowDetailDTO.setKnowledgeVersion(String.valueOf(knowledgeVersion));
							knowledgePointMasteryDTO.getKnowHowDetailList().add(knowHowDetailDTO);
						}
					}
					int wrongCount = knowledgeWrongAndTotalQuestionCountMapList.stream()
							.filter(it -> howDetailsIdList.contains(it.get("how_detail_id").toString()))
							.mapToInt(it -> Integer.parseInt(it.get("wrong_count").toString()))
							.sum();
					int totalCount = knowledgeWrongAndTotalQuestionCountMapList.stream()
							.filter(it -> howDetailsIdList.contains(it.get("how_detail_id").toString()))
							.mapToInt(it -> Integer.parseInt(it.get("total_count").toString()))
							.sum();
					int rightCount = knowledgeRightQuestionWithExerciseCountMapList.stream()
							.filter(it -> howDetailsIdList.contains(it.get("how_details_id").toString()))
							.mapToInt(it -> Integer.parseInt(it.get("right_count").toString()))
							.sum();
					knowledgePointMasteryDTO.setWrongCount(knowledgePointMasteryDTO.getWrongCount() + wrongCount);
					knowledgePointMasteryDTO.setTotalCount(knowledgePointMasteryDTO.getTotalCount() + totalCount + rightCount);
				}
			}

			knowledgeProficiency.setNewKnowledgePointMasteryJson(JSON.toJSONString(moduleDTOList));
		} else {
			//无相关知识点
			knowledgeProficiency.setNewKnowledgePointMasteryJson("[]");
		}
		knowledgeProficiency.setUpdateTime(nowDateTime);

		return knowledgeProficiency;
	}

	private int countAndSaveWrongQuestionKnowledge(String homeworkId) {
		int count = 0;

		int pageSize = 500;

		List<KnowledgeWrongQuestionCount> knowledgeWrongQuestionCountList;
		Set<String> homeworkIdSet = new HashSet<>(pageSize);
		SearchResponse searchResponse;
		Date generateTime = new Date();

		// 更新错题知识点统计数据之前，先清除旧数据
		if (StringUtils.isBlank(homeworkId)) {
			homeworkMapper.deleteWrongQuestionKnowledgeCountWhichNeedUpdate();
		} else {
			homeworkMapper.deleteWrongQuestionKnowledgeCountByHomeworkId(homeworkId);
		}

		do {
			knowledgeWrongQuestionCountList = homeworkMapper.countWrongQuestionGroupByKnowledge(homeworkId, pageSize);

			for (KnowledgeWrongQuestionCount knowledgeWrongQuestionCount : knowledgeWrongQuestionCountList) {
				if (knowledgeWrongQuestionCount.getQuestionStatus() < 1) {
					//错题没有原题时，需要检查是否有练习册题目
					SearchRequest searchRequest = new SearchRequest();
					searchRequest.indices("kmt_exercise_question_know");
					searchRequest.source(new SearchSourceBuilder());
					BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
					searchRequest.source().query(boolQueryBuilder);
					searchRequest.source().size(0);
					boolQueryBuilder.must(new TermQueryBuilder("version", knowledgeWrongQuestionCount.getKnowledgeVersion()));
					boolQueryBuilder.must(new QueryStringQueryBuilder(knowledgeWrongQuestionCount.getHowDetailId()).field("how_details_id.keyword"));
					logger.debug(searchRequest.source().toString());
					searchResponse = baseSearchService.search(searchRequest, RequestOptions.DEFAULT);
					if (searchResponse != null && searchResponse.getHits().getTotalHits().value > 0) {
						knowledgeWrongQuestionCount.setQuestionStatus(2);
					}
				} else {
					knowledgeWrongQuestionCount.setQuestionStatus(1);
				}
				knowledgeWrongQuestionCount.setCreateTime(generateTime);

				homeworkIdSet.add(knowledgeWrongQuestionCount.getHomeworkId());
			}

			if (!knowledgeWrongQuestionCountList.isEmpty()) {
				// 新的统计数据存入数据库
				pagingHibernateJdbcDao.batchInsert(knowledgeWrongQuestionCountList, pageSize);

				count += knowledgeWrongQuestionCountList.size();
			}
		} while (knowledgeWrongQuestionCountList.size() >= pageSize);

		// 更新是否需要更新错题知识点统计数据字段
		if (StringUtils.isBlank(homeworkId) && CollectionUtils.isNotEmpty(homeworkIdSet)) {
			homeworkMapper.updateNeedUpdateWrongQuestionKnowledgeCount(homeworkIdSet, 0);
			homeworkIdSet.clear();
		}

		return count;
	}

	/**
	 * 统计指定时间的作业数据并存入数据库
	 * @param offset 时间偏移量
	 * @param calculateScore
	 * @param needPush 是否需要推送通知给学生家长
	 * @return 生成数据数量
	 */
	private int countAndSaveMonthlyHomeworkData(Integer offset, Boolean calculateScore, boolean needPush) {
		Date countTime = new Date();
		int generateCount = 0;

		Date tempDateTime = DateUtil.addMonths(new Date(), offset);
		String targetMonthStr = DateUtil.format(tempDateTime.getTime(), "yyyy-MM");
		Date beginDateTimeOfTargetMonth = DateUtil.getBeginOfThisMonth(tempDateTime);
		String beginDateTimeStr = DateUtil.format_yyyyMMdd(beginDateTimeOfTargetMonth) + " 00:00:00";
		Date endDateTimeOfTargetMonth = DateUtil.getEndOfThisMonth(tempDateTime);
		String endDateTimeStr = DateUtil.format_yyyyMMdd(endDateTimeOfTargetMonth) + " 23:59:59";
		String lastDataMonth = DateUtil.format(DateUtil.addDays(beginDateTimeOfTargetMonth, -1).getTime(), "yyyy-MM");

		int pageNo = 1;
		int pageSize = 500;
		List<HomeworkCount> homeworkCountList;
		List<HomeworkCount> homeworkCount2DbList = new LinkedList<>();

		do {
			homeworkCountList = homeworkMapper.countMonthlyHomeworkData(new Page<>(pageNo, pageSize), beginDateTimeStr, endDateTimeStr);
			homeworkCountList.forEach(homeworkCount -> {
				Integer totalCount = homeworkCount.getTotalCount();
				Integer wrongCount = homeworkCount.getWrongCount();
				if (totalCount > 0) {
					homeworkCount.setHomeworkTime(null);
					homeworkCount.setHomeworkMonth(targetMonthStr);
					int accuracyRate = new BigDecimal((totalCount - wrongCount) * 100.0 / totalCount)
							.setScale(0, BigDecimal.ROUND_DOWN).intValue();
					homeworkCount.setAccuracyRate(accuracyRate);

					HomeworkCount lastMonthHomeworkCount = homeworkMapper.findByVipIdAndMonth(homeworkCount.getVipId(), homeworkCount.getGrade(), lastDataMonth);
					if (lastMonthHomeworkCount != null) {
						homeworkCount.setIncreasedScore(accuracyRate - lastMonthHomeworkCount.getAccuracyRate());
					} else {
						homeworkCount.setIncreasedScore(0);
					}

					int add = 0;
					int increasedScore = homeworkCount.getIncreasedScore();
					if(increasedScore>=1 && increasedScore<=5){
						add = 1;
					}else if(increasedScore>=6 && increasedScore<=10){
						add = 2;
					}else if(increasedScore>=11 && increasedScore<=15){
						add = 3;
					}else if(increasedScore>=16 && increasedScore<=20){
						add = 4;
					}else if(increasedScore>=20){
						add = 5;
					}
					homeworkCount.setAddIntegral(add);
					if (calculateScore) {
						rankingService.saveRankIntegral(homeworkCount.getVipId(),add,5,"");
					}
					List<SubjectKnowledgeInfoDTOV2> subjectKnowledgeInfoDTOV2List = new LinkedList<>();
					subjectKnowledgeInfoDTOV2List.add(kmtExerciseMapperService.getSubjectKnowledgeInfoDTOV2(homeworkCount.getVipId(), "语文", beginDateTimeOfTargetMonth, endDateTimeOfTargetMonth));
					subjectKnowledgeInfoDTOV2List.add(kmtExerciseMapperService.getSubjectKnowledgeInfoDTOV2(homeworkCount.getVipId(), "数学", beginDateTimeOfTargetMonth, endDateTimeOfTargetMonth));
					subjectKnowledgeInfoDTOV2List.add(kmtExerciseMapperService.getSubjectKnowledgeInfoDTOV2(homeworkCount.getVipId(), "英语", beginDateTimeOfTargetMonth, endDateTimeOfTargetMonth));
					homeworkCount.setSubjectKnowledgeInfoJson(JSON.toJSONString(subjectKnowledgeInfoDTOV2List));

					homeworkCount.setDataType(2);
					homeworkCount.setUpdateTime(countTime);
					homeworkCount.setCreateTime(countTime);
					homeworkCount2DbList.add(homeworkCount);
				}
			});

			if (homeworkCount2DbList.size() > 0) {
				//统计记录存入数据库
				pagingHibernateJdbcDao.batchInsert(homeworkCount2DbList, pageSize);
				generateCount += homeworkCount2DbList.size();
				homeworkCount2DbList.clear();
			}

			if (homeworkCountList.size() < pageSize) {
				//数据数小于pageSize，已没有后续数据，跳出循环
				break;
			}

			pageNo ++;
		} while (true);

		logger.info("时间偏移量：{}，月份：{}，生成月统计数据：{}条", offset, targetMonthStr, generateCount);

		//更新超越了多少多少比例的同级学生
		this.updateTranscendedStudentProportion(targetMonthStr);
		logger.info("更新{}的月统计数据的transcended_student_proportion完成", targetMonthStr);

		if (needPush) {
			try {
				//推送月成长报告通知
				int pushCount = this.pushMonthlyGrowthReportNotify(targetMonthStr);
				logger.info("共推送{}条月成长报告通知", pushCount);
			} catch (Exception e) {
				logger.error("统计学生月统计数据后，推送通知出现异常", e);
			}
		}

		return generateCount;
	}

	@Override
	public int updateTranscendedStudentProportion(String targetMonthStr) {
		int updateCount = 0;
		int pageNo = 1;
		int pageSize = 500;

		List<HomeworkCount> homeworkCountList;
		Map<String, Object> othersAndAccuracyRateLessThanSelfCountMap;
		do {
			homeworkCountList = homeworkMapper.findMonthlyReportByMonth(new Page<>(pageNo, pageSize), targetMonthStr);

			for (HomeworkCount homeworkCount : homeworkCountList) {
				othersAndAccuracyRateLessThanSelfCountMap =
						homeworkMapper.countOthersAndAccuracyRateLessThan(homeworkCount.getVipId(),
								homeworkCount.getAccuracyRate(), homeworkCount.getGrade(), homeworkCount.getHomeworkMonth());
				if (othersAndAccuracyRateLessThanSelfCountMap != null) {
					int othersCount = Integer.parseInt(Optional.ofNullable(othersAndAccuracyRateLessThanSelfCountMap
							.get("othersCount")).orElse("0").toString());
					if (othersCount > 0) {
						int accuracyRateLessThanCount =
								Integer.parseInt(Optional.ofNullable(othersAndAccuracyRateLessThanSelfCountMap
										.get("accuracyRateLessThanCount")).orElse("0").toString());
						BigDecimal transcendedStudentProportion =
								BigDecimal.valueOf(accuracyRateLessThanCount * 1.0 / othersCount)
										.setScale(2, BigDecimal.ROUND_DOWN);
						homeworkCount.setTranscendedStudentProportion(transcendedStudentProportion);
					}
				}
			}

			if (!homeworkCountList.isEmpty()) {
				pagingHibernateJdbcDao.batchUpdate(homeworkCountList);
				updateCount += homeworkCountList.size();
			}

			pageNo ++;
		} while (homeworkCountList.size() >= pageSize);

		logger.info("更新{}月月报中的transcended_student_proportion的数据完成，共{}条记录", targetMonthStr, updateCount);
		return updateCount;
	}

	@Override
	public int pushMonthlyGrowthReportNotify(String month) {
		final int[] pushCount = {0};

		int pageNo = 1;
		int pageSize = 500;
		Date beginDateTime;
		try {
			beginDateTime = DateUtil.parseDate(month + "-01", "yyyy-MM-01");
		} catch (ParseException e) {
			logger.warn("推送月成长报告时月份参数错误，month=[{}]", month);
			return 0;
		}
		String simpleMonthStr = DateUtil.format(beginDateTime.getTime(), "M");

		do {
			List<String> vipIdList = homeworkMapper.findVipIdNeedPushMonthlyNotify(new Page<>(pageNo, pageSize), month);
			for (String vipId : vipIdList) {
				try {
					LibVip libVip = libVipService.getById(vipId);
					if (libVip == null) {
						logger.warn("推送月成长报告通知时，发现学生(vipId=[{}])不存在", vipId);
						continue;
					}
					String parentUserId = libVip.getUserId();
					User parentUser = userService.getById(parentUserId);
					if (parentUser == null) {
						logger.warn("推送月成长报告通知时，发现家长(userId=[{}])不存在", parentUserId);
						continue;
					}

					if (StringUtils.isNotBlank(parentUser.getAccount())) {
						//公众号推送家长
						String firstStr = String.format("尊敬的%s家长，%s月的学习成长报告出炉啦！", libVip.getChildName(), simpleMonthStr);

						LearningGrowthReportReq req = new LearningGrowthReportReq();
						req.setVipId(vipId);
						HomeworkCount homeworkCount = kmtExerciseMapperService.findMonthHomeworkCount(req.getVipId(), month);
						if (homeworkCount == null) {
							continue;
						}
						String transcendedStudentProportion = homeworkCount.getTranscendedStudentProportion().multiply(BigDecimal.valueOf(100)).intValue() + "%";
						Integer increasedScore = homeworkCount.getIncreasedScore();
						String keyword2;
						if (increasedScore > 0) {
							keyword2 = String.format("%s月得分超过了%s同年级学生，同比上个月提高了%s分，继续加油!", simpleMonthStr, transcendedStudentProportion, increasedScore);
						} else if (increasedScore < 0) {
							keyword2 = String.format("%s月得分超过了%s同年级学生，同比上个月降低了%s分，继续加油!", simpleMonthStr, transcendedStudentProportion, increasedScore * -1);
						} else {
							keyword2 = String.format("%s月得分超过了%s同年级学生，同比上个月无变化，继续加油!", simpleMonthStr, transcendedStudentProportion);
						}
						String url = String.format("pages/learning_report/learning_report?status=2&vipId=%s", vipId);
						Miniprogram miniprogram = new Miniprogram();
						miniprogram.setAppid(WxUtil.map.get("jzdAppId"));
						miniprogram.setPagepath(url);
						Map<String, String> dataMap = new HashMap<>();
						dataMap.put("first", firstStr);
						dataMap.put("keyword1", libVip.getChildName());
						dataMap.put("keyword2", keyword2);
						SendTemplateShort sendTemplateShort = new SendTemplateShort(parentUser.getAccount(), "OPENTM415124923", null, dataMap, miniprogram);
						pushTemplateService.saveAndSendPushWxTemplate(sendTemplateShort);
					} else {
						logger.warn("家长(userId=[{}])openid为空", parentUserId);
					}

					//推送家长端app
					String content = String.format("尊敬的%s家长，您名下的孩子%s上月的学习成长报告已经生成，快来查看吧。", parentUser.getUserName(), libVip.getChildName());
					sconXingeService.asynPushGrowthReportThreadTask(parentUserId, vipId, content, "MONTHLY_GROWTH_REPORT_NOTICE");
				} catch (Exception e) {
					logger.error("推送月报通知失败，vipId=[{}]，e->{}", vipId, e.toString());
				}

				pushCount[0]++;
			}

			if (vipIdList.size() < pageSize) {
				//数据数小于pageSize，已没有后续数据，跳出循环
				break;
			}

			pageNo ++;
		} while (true);

		logger.info("推送月成长报告通知：{}条", pushCount[0]);

		return pushCount[0];
	}

	/**
	 * 统计指定时间的作业数据并存入数据库
	 * @param offset 时间偏移量
	 * @return 生成数据数量
	 */
	private int countAndSaveDailyHomeworkData(Integer offset) {
		Date countTime = new Date();
		int generateCount = 0;

		Date targetDateTime = DateUtil.addDays(new Date(), offset);
		String homeworkMonth = DateUtil.format(targetDateTime.getTime(), "yyyy-MM");
		String targetDateStr = DateUtil.format_yyyyMMdd(targetDateTime);
		String beginDateTimeStr = targetDateStr + " 00:00:00";
		String endDateTimeStr = targetDateStr + " 23:59:59";

		int pageNo = 1;
		int pageSize = 500;
		List<HomeworkCount> homeworkCountList;
		List<HomeworkCount> homeworkCount2DbList = new LinkedList<>();

		do {
			homeworkCountList = homeworkMapper.countHomeworkData(new Page<>(pageNo, pageSize), beginDateTimeStr, endDateTimeStr);
			homeworkCountList.forEach(homeworkCount -> {
				Integer totalCount = homeworkCount.getTotalCount();
				Integer wrongCount = homeworkCount.getWrongCount();
				if (totalCount > 0) {
					homeworkCount.setUpdateTime(countTime);
					homeworkCount.setCreateTime(countTime);
					int accuracyRate = new BigDecimal((totalCount - wrongCount) * 100.0 / totalCount)
							.setScale(0, BigDecimal.ROUND_DOWN).intValue();
					homeworkCount.setAccuracyRate(accuracyRate);
					homeworkCount.setDataType(1);
					homeworkCount.setHomeworkMonth(homeworkMonth);
					homeworkCount2DbList.add(homeworkCount);
				}
			});

			if (homeworkCount2DbList.size() > 0) {
				//统计记录存入数据库
				pagingHibernateJdbcDao.batchInsert(homeworkCount2DbList, pageSize);
				generateCount += homeworkCount2DbList.size();
				homeworkCount2DbList.clear();
			}

			if (homeworkCountList.size() < pageSize) {
				//数据数小于pageSize，已没有后续数据，跳出循环
				break;
			}

			pageNo ++;
		} while (true);

		logger.info("时间偏移量：{}，生成日统计数据：{}条", offset, generateCount);
		return generateCount;
	}

}
