package org.springblade.modules.martial.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springblade.core.log.exception.ServiceException;
import org.springblade.modules.martial.mapper.*;
import org.springblade.modules.martial.pojo.dto.AthleteOrderDTO;
import org.springblade.modules.martial.pojo.dto.MoveAthletesDTO;
import org.springblade.modules.martial.pojo.entity.*;
import org.springblade.modules.martial.service.*;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 编排方案服务实现类
 *
 * @author BladeX
 */
@Slf4j
@Service
@AllArgsConstructor
public class MartialSchedulePlanServiceImpl extends ServiceImpl<MartialSchedulePlanMapper, MartialSchedulePlan>
		implements IMartialSchedulePlanService {

	private final MartialScheduleSlotMapper slotMapper;
	private final MartialScheduleAthleteSlotMapper athleteSlotMapper;
	private final MartialScheduleConflictMapper conflictMapper;
	private final MartialScheduleAdjustmentLogMapper adjustmentLogMapper;
	private final IMartialCompetitionService competitionService;
	private final IMartialProjectService projectService;
	private final IMartialVenueService venueService;
	private final IMartialAthleteService athleteService;
	private final IMartialRegistrationOrderService registrationOrderService;

	/**
	 * 自动编排算法
	 */
	@Override
	@Transactional(rollbackFor = Exception.class)
	public MartialSchedulePlan autoSchedule(Long competitionId) {
		log.info("开始自动编排，赛事ID: {}", competitionId);

		// 1. 加载赛事基础数据
		MartialCompetition competition = competitionService.getById(competitionId);
		if (competition == null) {
			throw new ServiceException("赛事不存在");
		}

		// 2. 加载所有项目
		List<MartialProject> projects = projectService.list(
			new QueryWrapper<MartialProject>().eq("competition_id", competitionId)
		);
		if (projects == null || projects.isEmpty()) {
			throw new ServiceException("该赛事没有配置项目");
		}

		// 3. 加载所有场地
		List<MartialVenue> venues = venueService.list(
			new QueryWrapper<MartialVenue>().eq("competition_id", competitionId)
		);
		if (venues == null || venues.isEmpty()) {
			throw new ServiceException("该赛事没有配置场地");
		}

		// 4. 项目排序（集体项目优先）
		projects.sort((a, b) -> {
			// 集体项目优先
			Integer typeA = a.getType() != null ? a.getType() : 1;
			Integer typeB = b.getType() != null ? b.getType() : 1;
			if (!typeA.equals(typeB)) {
				// 3=集体 > 2=双人 > 1=个人
				return typeB.compareTo(typeA);
			}
			// 同类型按项目名称排序
			return a.getProjectName().compareTo(b.getProjectName());
		});

		// 5. 创建编排方案
		MartialSchedulePlan plan = new MartialSchedulePlan();
		plan.setCompetitionId(competitionId);
		plan.setPlanName(competition.getCompetitionName() + "-自动编排方案");
		plan.setPlanType(1); // 1-自动生成
		plan.setStatus(0); // 0-草稿
		plan.setStartTime(competition.getCompetitionStartTime());
		plan.setEndTime(competition.getCompetitionEndTime());
		plan.setVenueCount(venues.size());
		plan.setTimeSlotDuration(30); // 默认30分钟一个时间槽
		plan.setTotalMatches(0);
		plan.setConflictCount(0);
		this.save(plan);

		// 6. 生成时间槽列表（从比赛开始到结束，每30分钟一个槽）
		List<TimeSlot> timeSlots = generateTimeSlots(
			competition.getCompetitionStartTime(),
			competition.getCompetitionEndTime(),
			30
		);

		// 7. 初始化编排矩阵（场地 x 时间槽）
		ScheduleMatrix matrix = new ScheduleMatrix(timeSlots, venues);

		// 8. 逐个项目分配
		int totalMatches = 0;
		for (MartialProject project : projects) {
			// 获取该项目的所有报名运动员
			List<MartialAthlete> athletes = getProjectAthletes(competitionId, project.getId());
			if (athletes.isEmpty()) {
				log.warn("项目 {} 没有报名运动员，跳过", project.getProjectName());
				continue;
			}

			// 计算需要的时间槽数量
			int athleteCount = athletes.size();
			int slotDuration = project.getEstimatedDuration() != null ? project.getEstimatedDuration() : 10;
			int slotsNeeded = (int) Math.ceil((double) (athleteCount * slotDuration) / 30);

			// 寻找可用的连续时间槽
			boolean assigned = false;
			for (MartialVenue venue : venues) {
				for (int i = 0; i <= timeSlots.size() - slotsNeeded; i++) {
					if (canAssign(matrix, project, athletes, timeSlots.subList(i, i + slotsNeeded), venue)) {
						// 分配成功
						assign(matrix, plan.getId(), project, athletes, timeSlots.subList(i, i + slotsNeeded), venue);
						totalMatches += athletes.size();
						assigned = true;
						break;
					}
				}
				if (assigned) break;
			}

			if (!assigned) {
				log.warn("项目 {} 无法找到合适的时间槽，可能需要增加场地或延长比赛时间", project.getProjectName());
			}
		}

		// 9. 更新编排方案统计信息
		plan.setTotalMatches(totalMatches);
		this.updateById(plan);

		// 10. 冲突检测
		List<MartialScheduleConflict> conflicts = detectConflicts(plan.getId());
		plan.setConflictCount(conflicts.size());
		this.updateById(plan);

		log.info("自动编排完成，方案ID: {}, 总场次: {}, 冲突数: {}", plan.getId(), totalMatches, conflicts.size());
		return plan;
	}

	/**
	 * 检查是否可以分配
	 */
	private boolean canAssign(ScheduleMatrix matrix, MartialProject project,
							   List<MartialAthlete> athletes, List<TimeSlot> timeSlots, MartialVenue venue) {
		// 检查场地是否在这些时间槽都空闲
		for (TimeSlot slot : timeSlots) {
			if (matrix.isVenueOccupied(venue, slot)) {
				return false;
			}
		}

		// 检查运动员是否有冲突
		for (MartialAthlete athlete : athletes) {
			for (TimeSlot slot : timeSlots) {
				if (matrix.isAthleteOccupied(athlete, slot)) {
					return false;
				}
			}
		}

		return true;
	}

	/**
	 * 分配项目到时间槽
	 */
	private void assign(ScheduleMatrix matrix, Long planId, MartialProject project,
						List<MartialAthlete> athletes, List<TimeSlot> timeSlots, MartialVenue venue) {
		// 为每个时间槽创建记录
		for (TimeSlot timeSlot : timeSlots) {
			MartialScheduleSlot slot = new MartialScheduleSlot();
			slot.setPlanId(planId);
			slot.setVenueId(venue.getId());
			slot.setSlotDate(timeSlot.getDate());
			slot.setStartTime(timeSlot.getStartTime());
			slot.setEndTime(timeSlot.getEndTime());
			slot.setDuration(30);
			slot.setProjectId(project.getId());
			slot.setCategory(project.getCategory());
			slot.setSortOrder(0);
			slot.setStatus(0); // 未开始
			slotMapper.insert(slot);

			// 标记矩阵占用
			matrix.occupy(venue, timeSlot, project);
		}

		// 将运动员分配到第一个时间槽（可以后续调整）
		MartialScheduleSlot firstSlot = slotMapper.selectOne(
			new QueryWrapper<MartialScheduleSlot>()
				.eq("plan_id", planId)
				.eq("venue_id", venue.getId())
				.eq("project_id", project.getId())
				.orderByAsc("start_time")
				.last("LIMIT 1")
		);

		for (int i = 0; i < athletes.size(); i++) {
			MartialAthlete athlete = athletes.get(i);
			MartialScheduleAthleteSlot athleteSlot = new MartialScheduleAthleteSlot();
			athleteSlot.setSlotId(firstSlot.getId());
			athleteSlot.setAthleteId(athlete.getId());
			athleteSlot.setAppearanceOrder(i + 1);
			athleteSlot.setCheckInStatus(0);
			athleteSlot.setPerformanceStatus(0);
			athleteSlot.setIsAdjusted(0);
			athleteSlotMapper.insert(athleteSlot);

			// 标记运动员占用
			for (TimeSlot timeSlot : timeSlots) {
				matrix.occupyAthlete(athlete, timeSlot);
			}
		}
	}

	/**
	 * 生成时间槽列表
	 */
	private List<TimeSlot> generateTimeSlots(LocalDateTime startTime, LocalDateTime endTime, int durationMinutes) {
		List<TimeSlot> slots = new ArrayList<>();
		LocalDateTime current = startTime;

		while (current.isBefore(endTime)) {
			LocalDateTime slotEnd = current.plusMinutes(durationMinutes);
			if (slotEnd.isAfter(endTime)) {
				slotEnd = endTime;
			}

			TimeSlot slot = new TimeSlot();
			slot.setDate(current.toLocalDate());
			slot.setStartTime(current.toLocalTime());
			slot.setEndTime(slotEnd.toLocalTime());
			slots.add(slot);

			current = slotEnd;
		}

		return slots;
	}

	/**
	 * 获取项目的所有报名运动员
	 */
	private List<MartialAthlete> getProjectAthletes(Long competitionId, Long projectId) {
		// 通过报名订单关联查询
		return athleteService.list(
			new QueryWrapper<MartialAthlete>()
				.eq("competition_id", competitionId)
				.apply("EXISTS (SELECT 1 FROM martial_registration_order o " +
					"WHERE o.athlete_id = martial_athlete.id " +
					"AND o.project_id = {0} " +
					"AND o.order_status = 1)", projectId)
		);
	}

	/**
	 * 冲突检测
	 */
	@Override
	public List<MartialScheduleConflict> detectConflicts(Long planId) {
		List<MartialScheduleConflict> conflicts = new ArrayList<>();

		// 1. 检测运动员时间冲突
		conflicts.addAll(detectAthleteTimeConflicts(planId));

		// 2. 检测场地冲突
		conflicts.addAll(detectVenueConflicts(planId));

		// 保存冲突记录
		for (MartialScheduleConflict conflict : conflicts) {
			conflict.setPlanId(planId);
			conflict.setIsResolved(0);
			conflictMapper.insert(conflict);
		}

		return conflicts;
	}

	/**
	 * 检测运动员时间冲突
	 */
	private List<MartialScheduleConflict> detectAthleteTimeConflicts(Long planId) {
		List<MartialScheduleConflict> conflicts = new ArrayList<>();

		// 查询所有运动员-时间槽关联
		List<MartialScheduleAthleteSlot> athleteSlots = athleteSlotMapper.selectList(
			new QueryWrapper<MartialScheduleAthleteSlot>()
				.apply("slot_id IN (SELECT id FROM martial_schedule_slot WHERE plan_id = {0})", planId)
		);

		// 按运动员ID分组
		Map<Long, List<MartialScheduleAthleteSlot>> athleteMap = athleteSlots.stream()
			.collect(Collectors.groupingBy(MartialScheduleAthleteSlot::getAthleteId));

		// 检测每个运动员的时间冲突
		for (Map.Entry<Long, List<MartialScheduleAthleteSlot>> entry : athleteMap.entrySet()) {
			Long athleteId = entry.getKey();
			List<MartialScheduleAthleteSlot> slots = entry.getValue();

			if (slots.size() <= 1) continue;

			// 获取每个slot的时间信息
			for (int i = 0; i < slots.size(); i++) {
				for (int j = i + 1; j < slots.size(); j++) {
					MartialScheduleSlot slot1 = slotMapper.selectById(slots.get(i).getSlotId());
					MartialScheduleSlot slot2 = slotMapper.selectById(slots.get(j).getSlotId());

					// 检查时间重叠
					if (slot1.getSlotDate().equals(slot2.getSlotDate()) &&
						timeOverlaps(slot1.getStartTime(), slot1.getEndTime(),
							slot2.getStartTime(), slot2.getEndTime())) {

						MartialScheduleConflict conflict = new MartialScheduleConflict();
						conflict.setConflictType(1); // 时间冲突
						conflict.setSeverity(2); // 错误级别
						conflict.setEntityType("athlete");
						conflict.setEntityId(athleteId);
						conflict.setConflictDescription(
							String.format("运动员ID=%d在%s %s和%s时间段重叠",
								athleteId, slot1.getSlotDate(),
								slot1.getStartTime(), slot2.getStartTime())
						);
						conflicts.add(conflict);
					}
				}
			}
		}

		return conflicts;
	}

	/**
	 * 检测场地冲突
	 */
	private List<MartialScheduleConflict> detectVenueConflicts(Long planId) {
		List<MartialScheduleConflict> conflicts = new ArrayList<>();

		// 查询所有时间槽
		List<MartialScheduleSlot> slots = slotMapper.selectList(
			new QueryWrapper<MartialScheduleSlot>().eq("plan_id", planId)
		);

		// 按场地分组
		Map<Long, List<MartialScheduleSlot>> venueMap = slots.stream()
			.collect(Collectors.groupingBy(MartialScheduleSlot::getVenueId));

		// 检测每个场地的时间冲突
		for (Map.Entry<Long, List<MartialScheduleSlot>> entry : venueMap.entrySet()) {
			Long venueId = entry.getKey();
			List<MartialScheduleSlot> venueSlots = entry.getValue();

			for (int i = 0; i < venueSlots.size(); i++) {
				for (int j = i + 1; j < venueSlots.size(); j++) {
					MartialScheduleSlot slot1 = venueSlots.get(i);
					MartialScheduleSlot slot2 = venueSlots.get(j);

					if (slot1.getSlotDate().equals(slot2.getSlotDate()) &&
						timeOverlaps(slot1.getStartTime(), slot1.getEndTime(),
							slot2.getStartTime(), slot2.getEndTime())) {

						MartialScheduleConflict conflict = new MartialScheduleConflict();
						conflict.setConflictType(2); // 场地冲突
						conflict.setSeverity(3); // 致命级别
						conflict.setEntityType("venue");
						conflict.setEntityId(venueId);
						conflict.setConflictDescription(
							String.format("场地ID=%d在%s %s和%s时间段有多个项目",
								venueId, slot1.getSlotDate(),
								slot1.getStartTime(), slot2.getStartTime())
						);
						conflicts.add(conflict);
					}
				}
			}
		}

		return conflicts;
	}

	/**
	 * 检查时间是否重叠
	 */
	private boolean timeOverlaps(LocalTime start1, LocalTime end1, LocalTime start2, LocalTime end2) {
		return start1.isBefore(end2) && start2.isBefore(end1);
	}

	/**
	 * 检测移动运动员的冲突
	 */
	@Override
	public List<MartialScheduleConflict> checkMoveConflicts(MoveAthletesDTO moveDTO) {
		List<MartialScheduleConflict> conflicts = new ArrayList<>();

		MartialScheduleSlot toSlot = slotMapper.selectById(moveDTO.getToSlotId());
		if (toSlot == null) {
			throw new ServiceException("目标时间槽不存在");
		}

		// 检查每个运动员是否在目标时间段有冲突
		for (Long athleteId : moveDTO.getAthleteIds()) {
			// 查询该运动员的所有时间槽
			List<MartialScheduleAthleteSlot> athleteSlots = athleteSlotMapper.selectList(
				new QueryWrapper<MartialScheduleAthleteSlot>().eq("athlete_id", athleteId)
			);

			for (MartialScheduleAthleteSlot as : athleteSlots) {
				if (as.getSlotId().equals(moveDTO.getFromSlotId())) {
					continue; // 跳过源时间槽
				}

				MartialScheduleSlot existingSlot = slotMapper.selectById(as.getSlotId());
				if (existingSlot != null &&
					existingSlot.getSlotDate().equals(toSlot.getSlotDate()) &&
					timeOverlaps(existingSlot.getStartTime(), existingSlot.getEndTime(),
						toSlot.getStartTime(), toSlot.getEndTime())) {

					MartialScheduleConflict conflict = new MartialScheduleConflict();
					conflict.setConflictType(1); // 时间冲突
					conflict.setSeverity(2);
					conflict.setEntityType("athlete");
					conflict.setEntityId(athleteId);
					conflict.setConflictDescription(
						String.format("运动员ID=%d在%s %s已有安排",
							athleteId, toSlot.getSlotDate(), toSlot.getStartTime())
					);
					conflicts.add(conflict);
				}
			}
		}

		return conflicts;
	}

	/**
	 * 移动运动员
	 */
	@Override
	@Transactional(rollbackFor = Exception.class)
	public Boolean moveAthletes(MoveAthletesDTO moveDTO) {
		// 1. 冲突检测
		List<MartialScheduleConflict> conflicts = checkMoveConflicts(moveDTO);
		if (!conflicts.isEmpty()) {
			throw new ServiceException("存在冲突，无法移动: " + conflicts.get(0).getConflictDescription());
		}

		// 2. 执行移动
		for (Long athleteId : moveDTO.getAthleteIds()) {
			// 查找原记录
			MartialScheduleAthleteSlot oldSlot = athleteSlotMapper.selectOne(
				new QueryWrapper<MartialScheduleAthleteSlot>()
					.eq("slot_id", moveDTO.getFromSlotId())
					.eq("athlete_id", athleteId)
			);

			if (oldSlot != null) {
				// 删除原记录
				athleteSlotMapper.deleteById(oldSlot.getId());

				// 创建新记录
				MartialScheduleAthleteSlot newSlot = new MartialScheduleAthleteSlot();
				newSlot.setSlotId(moveDTO.getToSlotId());
				newSlot.setAthleteId(athleteId);
				newSlot.setAppearanceOrder(oldSlot.getAppearanceOrder());
				newSlot.setCheckInStatus(oldSlot.getCheckInStatus());
				newSlot.setPerformanceStatus(oldSlot.getPerformanceStatus());
				newSlot.setIsAdjusted(1); // 标记为已调整
				newSlot.setAdjustNote(moveDTO.getReason());
				athleteSlotMapper.insert(newSlot);
			}
		}

		// 3. 记录调整日志
		logAdjustment(moveDTO);

		return true;
	}

	/**
	 * 调整出场顺序
	 */
	@Override
	@Transactional(rollbackFor = Exception.class)
	public Boolean updateAppearanceOrder(Long slotId, List<AthleteOrderDTO> newOrder) {
		for (AthleteOrderDTO orderDTO : newOrder) {
			athleteSlotMapper.update(
				null,
				new UpdateWrapper<MartialScheduleAthleteSlot>()
					.eq("slot_id", slotId)
					.eq("athlete_id", orderDTO.getAthleteId())
					.set("appearance_order", orderDTO.getOrder())
					.set("is_adjusted", 1)
			);
		}
		return true;
	}

	/**
	 * 确认并发布方案
	 */
	@Override
	@Transactional(rollbackFor = Exception.class)
	public Boolean confirmAndPublishPlan(Long planId) {
		MartialSchedulePlan plan = this.getById(planId);
		if (plan == null) {
			throw new ServiceException("编排方案不存在");
		}

		// 检查是否有未解决的冲突
		long unsolvedConflicts = conflictMapper.selectCount(
			new QueryWrapper<MartialScheduleConflict>()
				.eq("plan_id", planId)
				.eq("is_resolved", 0)
		);

		if (unsolvedConflicts > 0) {
			throw new ServiceException("还有 " + unsolvedConflicts + " 个未解决的冲突，无法发布");
		}

		// 更新状态为已发布
		plan.setStatus(2);
		plan.setPublishedTime(LocalDateTime.now());
		this.updateById(plan);

		return true;
	}

	/**
	 * 解决冲突
	 */
	@Override
	@Transactional(rollbackFor = Exception.class)
	public Boolean resolveConflicts(Long planId, List<MartialScheduleConflict> conflicts) {
		for (MartialScheduleConflict conflict : conflicts) {
			conflict.setIsResolved(1);
			conflictMapper.updateById(conflict);
		}
		return true;
	}

	/**
	 * 记录调整日志
	 */
	private void logAdjustment(MoveAthletesDTO moveDTO) {
		MartialScheduleSlot fromSlot = slotMapper.selectById(moveDTO.getFromSlotId());
		MartialScheduleSlot toSlot = slotMapper.selectById(moveDTO.getToSlotId());

		MartialScheduleAdjustmentLog log = new MartialScheduleAdjustmentLog();
		log.setPlanId(fromSlot.getPlanId());
		log.setActionType("move");
		log.setReason(moveDTO.getReason());
		log.setActionTime(LocalDateTime.now());

		adjustmentLogMapper.insert(log);
	}

	/**
	 * 时间槽内部类
	 */
	private static class TimeSlot {
		private LocalDate date;
		private LocalTime startTime;
		private LocalTime endTime;

		public LocalDate getDate() {
			return date;
		}

		public void setDate(LocalDate date) {
			this.date = date;
		}

		public LocalTime getStartTime() {
			return startTime;
		}

		public void setStartTime(LocalTime startTime) {
			this.startTime = startTime;
		}

		public LocalTime getEndTime() {
			return endTime;
		}

		public void setEndTime(LocalTime endTime) {
			this.endTime = endTime;
		}
	}

	/**
	 * 编排矩阵内部类
	 */
	private static class ScheduleMatrix {
		private final Map<String, Set<Long>> venueOccupancy = new HashMap<>();
		private final Map<String, Set<Long>> athleteOccupancy = new HashMap<>();

		public ScheduleMatrix(List<TimeSlot> timeSlots, List<MartialVenue> venues) {
			// 初始化矩阵
		}

		public boolean isVenueOccupied(MartialVenue venue, TimeSlot slot) {
			String key = venue.getId() + "-" + slot.getDate() + "-" + slot.getStartTime();
			return venueOccupancy.containsKey(key);
		}

		public boolean isAthleteOccupied(MartialAthlete athlete, TimeSlot slot) {
			String key = athlete.getId() + "-" + slot.getDate() + "-" + slot.getStartTime();
			return athleteOccupancy.containsKey(key);
		}

		public void occupy(MartialVenue venue, TimeSlot slot, MartialProject project) {
			String key = venue.getId() + "-" + slot.getDate() + "-" + slot.getStartTime();
			venueOccupancy.computeIfAbsent(key, k -> new HashSet<>()).add(project.getId());
		}

		public void occupyAthlete(MartialAthlete athlete, TimeSlot slot) {
			String key = athlete.getId() + "-" + slot.getDate() + "-" + slot.getStartTime();
			athleteOccupancy.computeIfAbsent(key, k -> new HashSet<>()).add(athlete.getId());
		}
	}

}
