package com.cku.oa.showRefereeDelegate.service;

import com.cku.core.ZAErrorCode;
import com.cku.core.ZAException;
import com.cku.oa.showRefereeDelegate.comparator.RefereeLevelOneComparator;
import com.cku.oa.showRefereeDelegate.comparator.RefereeLevelThreeComparator;
import com.cku.oa.showRefereeDelegate.comparator.RefereeLevelTwoComparator;
import com.cku.oa.showRefereeDelegate.entity.*;
import com.cku.oa.showRefereeDelegate.vo.ShowRefereeVo;
import com.cku.util.JSONUtils;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.thinkgem.jeesite.common.utils.DateUtils;
import com.thinkgem.jeesite.common.utils.StringUtils;
import com.thinkgem.jeesite.common.utils.excel.ExportExcel;
import com.thinkgem.jeesite.common.utils.excel.ImportExcel;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.ui.Model;
import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;
import java.util.function.*;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 犬展裁判委派service
 *
 * @author yuanshuai
 * @date 2021/2/25 13:55
 */
@Service
@Transactional(readOnly = true)
public class ShowRefereeDelegateService {

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

	@Autowired
	private ShowRefereeDelegateLogService showRefereeDelegateLogService;

	/**
	 * 犬展裁判导入模板
	 *
	 * @author yuanshuai
	 * @date 2021/2/25 13:59
	 */
	public void importFileTemplate(HttpServletResponse response) throws IOException {
		String fileName = "犬展裁判导入模板.xlsx";
		List<Show> showList = Lists.newArrayList();
		Show show = new Show();
		show.setCode("202110");
		show.setCount(6);
		show.setRegionStr("华北");
		show.setStartDate(new Date());
		show.setEndDate(new Date());
		show.setCity("石家庄");
		show.setProvince("河北");
		showList.add(show);
		List<Referee> refereeList = Lists.newArrayList();
		Referee referee = new Referee();
		referee.setName("张三");
		referee.setLevelStr("A");
		referee.setJoinCount(6);
		referee.setLeaveStr("2021-06-26&2021-07-11,2021-06-05&2021-06-20");
		referee.setNewFlag("如是新进审查员则填N，否则为空");
		refereeList.add(referee);
		ExportExcel exportExcel = new ExportExcel("犬展导入模板", Show.class, 2);
		exportExcel.setDataList(showList, 0);
		exportExcel.addSheet("裁判导入模板", Referee.class);
		exportExcel.setDataList(refereeList, 1);
		exportExcel.write(response, fileName).dispose();
	}

	/**
	 * 导入犬展裁判信息
	 *
	 * @author yuanshuai
	 * @date 2021/2/25 17:38
	 */
	public void importShowReferee(MultipartFile file, Model model) throws InterruptedException, IOException, InvalidFormatException, IllegalAccessException, InstantiationException {
		ImportExcel showIe = new ImportExcel(file, 1, 0);
		List<Show> showList = showIe.getDataList(Show.class);
		ImportExcel refereeIe = new ImportExcel(file, 1, 1);
		List<Referee> refereeList = refereeIe.getDataList(Referee.class);
		//处理导入数据
		buildShowRefereeParam(showList, refereeList);
		//处理已经委派过的犬展数据
		List<Show> alreadyDelegateShowList = buildAlreadyDelegateShow();
		//执行委派
		for (int i = 0; i < 1000; i++) {
			try {
				prepareData(showList, refereeList, alreadyDelegateShowList);
				delegateReferee(showList, refereeList, alreadyDelegateShowList);
				break;
			} catch (ZAException e) {
				logger.debug("第" + (i + 1) + "次执行失败：" + e.getMessage());
				if (i == 999) {
					throw e;
				}
			}
		}
		//随机委派子犬展裁判
		randomChildShowReferee(showList);
		//生成视图对象
		buildVo(showList, model);
		//返回犬展信息为导出做准备
		List<ShowExport> exportList = buildShowExportList(showList);
		model.addAttribute("exportList", JSONUtils.toJSON(exportList));
		Thread.sleep(5000);
	}

	/**
	 * 处理已经委派过的记录
	 *
	 * @author yuanshuai
	 * @date 2021/3/17 11:21
	 */
	private List<Show> buildAlreadyDelegateShow() {
		//查询历史委派记录
		ShowRefereeDelegateLog search = new ShowRefereeDelegateLog();
		//过滤历史数据，2023年数据从2023-01-01开始计算
		search.setStartDate(DateUtils.parseDate("2025-01-01"));
		List<ShowRefereeDelegateLog> delegateLogList = showRefereeDelegateLogService.findList(search);
		//封装成犬展对象
		return delegateLogList.stream().map(Show::new).collect(Collectors.toList());
	}

	/**
	 * 随机子犬展裁判
	 *
	 * @param showList
	 */
	private void randomChildShowReferee(List<Show> showList) {
		showList.forEach(show -> Collections.shuffle(show.getRefereeList()));
	}

	/**
	 * 生成返回视图对象
	 *
	 * @param showList
	 * @param model
	 * @author yuanshuai
	 * @date 2021/2/26 16:32
	 */
	private void buildVo(List<Show> showList, Model model) {
		List<ShowRefereeVo> voList = showList.stream().map(show -> {
			ShowRefereeVo vo = new ShowRefereeVo(show);
			vo.setReferee(show.getRefereeList().stream().map(Referee::getName).collect(Collectors.joining(",")));
			return vo;
		}).collect(Collectors.toList());
		model.addAttribute("showList", voList);
	}

	/**
	 * 导出委派结果
	 *
	 * @author yuanshuai
	 * @date 2021/2/26 16:32
	 */
	private void exportResult(List<ShowExport> resultList, HttpServletResponse response) throws IOException {
		String fileName = "犬展裁判委派结果.xlsx";
		ExportExcel exportExcel = new ExportExcel("犬展裁判委派结果", ShowExport.class, 2);
		exportExcel.setDataList(resultList, 0);
		exportExcel.write(response, fileName).dispose();
	}

	/**
	 * 构建导出数据
	 *
	 * @author yuanshuai
	 * @date 2021/2/26 17:49
	 */
	private List<ShowExport> buildShowExportList(List<Show> showList) {
		List<ShowExport> resultList = Lists.newArrayList();
		for (Show show : showList) {
			ShowExport export = new ShowExport(show);
			resultList.add(export);
		}
		return resultList;
	}

	/**
	 * 为后续委派清洗数据
	 *
	 * @author yuanshuai
	 * @date 2021/2/26 13:47
	 */
	private void prepareData(List<Show> showList, List<Referee> refereeList, List<Show> alreadyDelegateShowList) {
		//初始化
		showList.forEach(show -> {
			show.init();
			if (show.getRefereeList() != null) {
				show.getRefereeList().clear();
			}
		});
		refereeList.forEach(referee -> {
			referee.init();
			referee.setShows(null);
			referee.setDelegateCount(0);
			//生成历史参赛日期信息
			List<Date> existDate = alreadyDelegateShowList.stream()
					.filter(show -> show.getRefereeList().stream().anyMatch(oldReferee -> referee.getName().equals(oldReferee.getName())))
					.flatMap((Function<Show, Stream<Date>>) show -> DateUtils.getDayListBetweenTwoDays(show.getStartDate(), show.getEndDate()).stream())
					.collect(Collectors.toList());
			//处理请假日期
			if (!StringUtils.isBlank(referee.getLeaveStr())) {
				String[] leaveDateStrs = referee.getLeaveStr().split(",");
				List<Date> leaveDate = Stream.of(leaveDateStrs).flatMap((Function<String, Stream<Date>>) s -> {
					String[] dateStrs = s.split("&");
					if (dateStrs.length == 1) {
						return Stream.of(DateUtils.parseDate(dateStrs[0]));
					}
					return DateUtils.getDayListBetweenTwoDays(DateUtils.parseDate(dateStrs[0]), DateUtils.parseDate(dateStrs[1])).stream();
				}).collect(Collectors.toList());
				existDate = Stream.concat(existDate.stream(), leaveDate.stream()).collect(Collectors.toList());
			}

			referee.setExistDate(existDate);
			//计算历史城市信息
			Map<String, Long> existCity = alreadyDelegateShowList.stream()
					.filter(show -> show.getRefereeList().stream().anyMatch(oldReferee -> referee.getName().equals(oldReferee.getName())))
					.map(Show::getCity)
					.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
			referee.setExistCity(existCity);

			//计算历史大区信息
			Map<ShowRegionEnum, Long> existRegion = alreadyDelegateShowList.stream()
					.filter(show -> show.getRefereeList().stream().anyMatch(oldReferee -> referee.getName().equals(oldReferee.getName())))
					.map(Show::getRegion)
					.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
			referee.setExistRegion(existRegion);

			//处理会员不同期问题
			//康沂/王志炜
			if ("康沂".equals(referee.getName()) || "王志炜".equals(referee.getName())) {
				referee.setGroup("1");
			}
		});

		//清除重复大区
		removeDuplicateArea(refereeList);
	}

	/**
	 * 清除重复地区信息
	 *
	 * @author yuanshuai
	 * @date 2021/7/29 11:50
	 */
	private void removeDuplicateArea(List<Referee> refereeList) {
		//大区
		List<ShowRegionEnum> allAlreadyRegion = Lists.newArrayList();
		Map<ShowRegionEnum, Long> alreadyRegionMin = Maps.newHashMap();
		refereeList.forEach(r -> {
			if (r != null && r.getExistRegion() != null && r.getExistRegion().size() > 0) {
				r.getExistRegion().keySet().forEach(key -> {
					if (!allAlreadyRegion.contains(key)) {
						allAlreadyRegion.add(key);
					}
				});
			}
		});
		allAlreadyRegion.forEach(key -> {
			refereeList.forEach(r -> {
				if (!r.getExistRegion().containsKey(key)) {
					alreadyRegionMin.put(key, 0L);
				} else if (!alreadyRegionMin.containsKey(key)) {
					alreadyRegionMin.put(key, r.getExistRegion().get(key));
				} else {
					Long min = alreadyRegionMin.get(key);
					if (min != 0L) {
						Long target = r.getExistRegion().get(key);
						if (target < min) {
							alreadyRegionMin.put(key, target);
						}
					}
				}
			});
		});
		alreadyRegionMin.forEach((key, value) -> {
			if (value != 0L) {
				refereeList.forEach(r -> {
					r.getExistRegion().computeIfPresent(key, (k, v) -> v - value == 0 ? null : v - value);
				});
			}
		});

		//城市
		List<String> allAlreadyCity = Lists.newArrayList();
		Map<String, Long> alreadyCityMin = Maps.newHashMap();
		refereeList.forEach(r -> {
			if (r != null && r.getExistCity() != null && r.getExistCity().size() > 0) {
				r.getExistCity().keySet().forEach(key -> {
					if (!allAlreadyCity.contains(key)) {
						allAlreadyCity.add(key);
					}
				});
			}
		});
		allAlreadyCity.forEach(key -> {
			refereeList.forEach(r -> {
				if (!r.getExistCity().containsKey(key)) {
					alreadyCityMin.put(key, 0L);
				} else if (!alreadyCityMin.containsKey(key)) {
					alreadyCityMin.put(key, r.getExistCity().get(key));
				} else {
					Long min = alreadyCityMin.get(key);
					if (min != 0L) {
						Long target = r.getExistCity().get(key);
						if (target < min) {
							alreadyCityMin.put(key, target);
						}
					}
				}
			});
		});
		alreadyCityMin.forEach((key, value) -> {
			if (value != 0L) {
				refereeList.forEach(r -> {
					r.getExistCity().computeIfPresent(key, (k, v) -> v - value == 0 ? null : v - value);
				});
			}
		});
	}

	/**
	 * 构建犬展裁判数据
	 *
	 * @author yuanshuai
	 * @date 2021/2/25 17:39
	 */
	private void buildShowRefereeParam(List<Show> showList, List<Referee> refereeList) {
		Iterator<Show> showIterator = showList.iterator();
		while (showIterator.hasNext()) {
			Show show = showIterator.next();
			//过滤空数据
			if (StringUtils.isBlank(show.getCode())) {
				showIterator.remove();
				continue;
			}
			//解析大区信息
			show.setRegion(ShowRegionEnum.getShowRegionByNameCn(show.getRegionStr()));
			//初始化
			show.init();
		}
		Iterator<Referee> refereeIterator = refereeList.iterator();
		while (refereeIterator.hasNext()) {
			Referee referee = refereeIterator.next();
			//过滤空数据
			if (StringUtils.isBlank(referee.getName())) {
				refereeIterator.remove();
				continue;
			}
			//解析级别信息
			referee.setLevel(RefereeLevelEnum.getRefereeLevelByName(referee.getLevelStr()));
			//初始化
			referee.init();
		}
	}

	/**
	 * 根据规则委派裁判
	 *
	 * @param refereeList 裁判列表
	 * @param showList    犬展列表
	 * @author yuanshuai
	 * @date 2021/2/3 15:52
	 */
	public static void delegateReferee(List<Show> showList, List<Referee> refereeList, List<Show> alreadyDelegateShowList) {
		// 所需裁判数量
		int needDelegateCount = 0;
		for (Show show : showList) {
			needDelegateCount += show.getCount();
		}

		//判断人数是否符合要求
		int refereePoolNum = 0;
		for (Referee referee : refereeList) {
			refereePoolNum += referee.getJoinCount();
		}
		if (refereePoolNum < needDelegateCount) {
			throw new ZAException(ZAErrorCode.ZA_ERROR, "犬展所需人数大于申报裁判数量");
		}

		// 根据大区给犬展分组
		Map<ShowRegionEnum, List<Show>> showMap = new HashMap(1 << 3);
		for (Show show : showList) {
			List<Show> list;
			switch (show.getRegion().getCode()) {
				case 0:
					list = showMap.computeIfAbsent(ShowRegionEnum.NONE, k -> new ArrayList<>());
					break;
				case 1:
					list = showMap.computeIfAbsent(ShowRegionEnum.SouthChina, k -> new ArrayList<>());
					break;
				case 2:
					list = showMap.computeIfAbsent(ShowRegionEnum.NorthEast, k -> new ArrayList<>());
					break;
				case 3:
					list = showMap.computeIfAbsent(ShowRegionEnum.WestChina, k -> new ArrayList<>());
					break;
				case 4:
					list = showMap.computeIfAbsent(ShowRegionEnum.NorthChina, k -> new ArrayList<>());
					break;
				case 5:
					list = showMap.computeIfAbsent(ShowRegionEnum.EastChina, k -> new ArrayList<>());
					break;
				case 6:
					list = showMap.computeIfAbsent(ShowRegionEnum.CentralChina, k -> new ArrayList<>());
					break;
				default:
					throw new ZAException(ZAErrorCode.ZA_ERROR, "犬展信息有误：" + show.getCode() + "犬展大区为空");
			}
			list.add(show);
		}
		//根据大区中犬展数量生成大区排序
		List<ShowRegionEnum> regionList = showMap.entrySet().stream()
				.filter(entry -> !CollectionUtils.isEmpty(entry.getValue()))
				.sorted(Comparator.comparingInt(a -> ((Map.Entry<ShowRegionEnum, List<Show>>) a).getValue().size()).reversed())
				.map(Map.Entry::getKey)
				.collect(Collectors.toList());


		//委派裁判
		//循环次数
		int loopCount = 0;
		//已委派裁判数量
		int delegateCount = 0;
		//当前轮次委派数量
		int currentDelegateCount = 0;
		//排序等级
		int sortLevel = 3;
		//循环级别:2为只循环A级裁判，1为循环A、B级，0为A、B、C级别
		int loopLevel = 2;
		//条件校验级别:依次降级，0为不校验
		int conditionLevel = 5;
		//清理大区标记位
		boolean regionClear = false;
		//循环-直到委派裁判数量=实际需要裁判数量
		while (delegateCount < needDelegateCount) {
			//结束循环
			if (currentDelegateCount == 0 && loopLevel == 0 && conditionLevel == 0) {
				throw new ZAException(ZAErrorCode.ZA_ERROR, "排序数据过于极端导致无法排满犬展，请先重试。重试几次后均无法成功请联系技术部");
			}

			//校验循环条件，降级处理
			//如果上一轮循环没有派出裁判则降级进行下一轮处理
			//目前排序(sortLevel)不做降级处理
			if (loopCount != 0 && currentDelegateCount == 0) {
				if (loopLevel > 0) {
					loopLevel--;
				} else if (!regionClear) {
					//如果未分配犬展大区该裁判均已去过，则清空大区
					regionList.forEach(regionEnum -> {
						if (refereeList
								.stream()
								.filter(referee -> referee.getExistRegion().keySet()
										.stream()
										.filter(regionEnum::equals)
										.count() <= 0)
								.count() <= 0) {
							refereeList.forEach(referee -> {
								if (referee.getExistRegion().get(regionEnum) != null) {
									referee.getExistRegion().remove(regionEnum);
								}
							});
						}
					});
					regionClear = true;
				} else if (conditionLevel > 0) {
					conditionLevel--;
					loopLevel = 2;
					regionClear = false;
				}
			}


			//初始化当前轮次委派数量
			currentDelegateCount = 0;

			//根据级别获取排序方式
			//目前无用
			Comparator<Referee> comparator = null;
			switch (sortLevel) {
				case 1:
					comparator = new RefereeLevelOneComparator();
					break;
				case 2:
					comparator = new RefereeLevelTwoComparator();
					break;
				case 3:
					comparator = new RefereeLevelThreeComparator();
					break;
				default:
					break;
			}
			//重新生成随机序列
			for (Referee referee : refereeList) {
				referee.resetSort();
			}
			refereeList.sort(comparator);


			//循环裁判
			for (int i = 0; i < refereeList.size(); i++) {
				Referee referee = refereeList.get(i);
				//裁判已无出场次数
				if (referee.getJoinCount() - referee.getDelegateCount() <= 0) {
					continue;
				}
				//裁判级别不符合循环级别
				if (loopLevel == 2) {
					if (referee.getLevel().getCode() == RefereeLevelEnum.B.getCode() || referee.getLevel().getCode() == RefereeLevelEnum.C.getCode()) {
						continue;
					}
				}
				if (loopLevel == 1) {
					if (referee.getLevel().getCode() == RefereeLevelEnum.C.getCode()) {
						continue;
					}
				}

				//根据大区数量委派一轮
				for (int j = 0; j < showMap.size(); j++) {
					ShowRegionEnum region = regionList.get(j);
					List<Show> showPool = showMap.get(region);
					Collections.shuffle(showPool);
					//顺序获取犬展信息
					//是否选出可以执裁犬展
					for (int k = 0; k < Math.min(referee.getJoinCount() - referee.getDelegateCount(), showPool.size()); k++) {
						Show show = showPool.get(k);
						if (show.getRefereeList() == null) {
							show.setRefereeList(new ArrayList<>());
						}

						//硬性要求
						//判断日期是否重复
						List<Date> contestDateList = DateUtils.getDayListBetweenTwoDays(show.getStartDate(), show.getEndDate());
						if (referee.getExistDate() == null) {
							referee.setExistDate(new ArrayList<>());
						} else {
							if (!Collections.disjoint(referee.getExistDate(), contestDateList)) {
								continue;
							}
						}

						//硬性要求
						//一场不能超过两个新进审查员
						if ("N".equals(referee.getNewFlag())) {
							long newCount = show.getRefereeList().stream().filter(r -> "N".equals(r.getNewFlag())).count();
							if (newCount >= 2) {
								continue;
							}
						}

						//裁判组不同期
						if (conditionLevel > 0) {
							if (StringUtils.isNotBlank(referee.getGroup())) {
								List<Referee> targetList = refereeList.stream()
										.filter(target -> referee.getGroup().equals(target.getGroup()))
										.filter(target -> !referee.getName().equals(target.getName()))
										.collect(Collectors.toList());
								if (targetList.size() > 0) {
									if (contestDateList.stream().anyMatch(contestDate -> targetList
											.stream()
											.anyMatch(target -> target.getExistDate()
													.stream()
													.anyMatch(targetDate -> targetDate.equals(contestDate))))) {
										continue;
									}
								}
							}
						}

						//判断重复度
						if (conditionLevel > 1) {
							//构建当前犬展裁判
							List<Referee> currentRefereeList = Stream.concat(show.getRefereeList().stream(), Stream.of(referee)).collect(Collectors.toList());
							//查询有此裁判的犬展
							if (Stream.concat(alreadyDelegateShowList.stream(), showList.stream()).anyMatch(targetShow -> {
								if (targetShow.getRefereeList() != null
										&& !targetShow.getCode().equals(show.getCode())
										&& targetShow.getRefereeList().contains(referee)) {
									List<Referee> targetRefereeList = targetShow.getRefereeList()
											.stream()
											.filter(currentRefereeList::contains)
											.collect(Collectors.toList());
									return targetRefereeList.size() > Math.floor(show.getCount() / 2);
								}
								return false;
							})) {
								continue;
							}
						}

						//判断裁判连续2周执裁
						if (conditionLevel > 2) {
							boolean before = false;
							boolean after = false;
							for (Date date : referee.getExistDate()) {
								if (!before && DateUtils.isBelongDayOffset(date, show.getStartDate(), -7)) {
									before = true;
									continue;
								}
								if (!after && DateUtils.isBelongDayOffset(date, show.getEndDate(), 7)) {
									after = true;
								}
							}
							if (before || after) {
								continue;
							}
						}

						//判断大区是否重复
						if (conditionLevel > 3) {
							if (referee.getExistRegion() == null) {
								referee.setExistRegion(Maps.newHashMap());
							} else {
								Long value = referee.getExistRegion().get(show.getRegion());
								if (value != null && value > 0) {
									continue;
								}
							}
						}

						//判断城市是否重复
						if (conditionLevel > 4) {
							if (referee.getExistCity() == null) {
								referee.setExistCity(Maps.newHashMap());
							} else {
								Long value = referee.getExistCity().get(show.getCity());
								if (value != null && value > 0) {
									continue;
								}
							}
						}



						//委派裁判
						show.getRefereeList().add(referee);
						referee.setShows((referee.getShows() == null ? "" : referee.getShows()) + show.getCode() + ",");
						//更新裁判出场次数
						referee.setDelegateCount(referee.getDelegateCount() + 1);
						//填充已使用大区
						if (show.getRegion() != ShowRegionEnum.NONE) {
							referee.getExistRegion().compute(show.getRegion(), (key, v) -> v == null ? 1 : v + 1);
						}
						//填充已使用城市
						referee.getExistCity().compute(show.getCity(), (key, v) -> v == null ? 1 : v + 1);
						//填充已使用日期
						referee.getExistDate().addAll(contestDateList);
						//更新标记位
						currentDelegateCount++;
						delegateCount++;

						//如果此犬展已经排满则从待排序队列中删除
						if (show.getRefereeList().size() >= show.getCount()) {
							showPool.remove(show);
						}
						break;
					}
					//如果此大区所有犬展均已排满则从待排序大区中删除
					if (showPool.size() == 0) {
						showMap.remove(region);
						regionList.remove(region);
					}
				}
			}
			//循环次数增加
			loopCount++;
		}


//		System.out.println("委派完毕：共循环" + loopCount + "次");
//		for (Show show : showList) {
//			System.out.println(show.getCode() + ":" + show.getCount());
//			for (Referee referee : show.getRefereeList()) {
//				System.out.print(referee.getName() + "(" + referee.getLevel() + "),");
//			}
//			System.out.println();
//		}
//		System.out.println("----------------------------------------------------------------");
//		for (Referee referee : refereeList) {
//			System.out.println(referee.getName() + ":" + referee.getDelegateCount() + "(" + referee.getJoinCount() + ")" + "\n" + referee.getShows());
//		}
	}

	/**
	 * 导出犬展裁判委派结果
	 *
	 * @author yuanshuai
	 * @date 2021/2/26 17:26
	 */
	public void exportShowReferee(List<ShowExport> showList, HttpServletResponse response) throws IOException {
		//导出结果
		exportResult(showList, response);
	}
}
