/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package org.springblade.modules.martial.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.joda.time.DateTime;
import org.springblade.core.tool.utils.Func;
import org.springblade.modules.martial.mapper.*;
import org.springblade.modules.martial.pojo.entity.*;
import org.springblade.modules.martial.service.IMartialScheduleArrangeService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

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

/**
 * 赛程自动编排服务实现类
 *
 * @author BladeX
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class MartialScheduleArrangeServiceImpl implements IMartialScheduleArrangeService {

	private final MartialScheduleStatusMapper scheduleStatusMapper;
	private final MartialScheduleGroupMapper scheduleGroupMapper;
	private final MartialScheduleDetailMapper scheduleDetailMapper;
	private final MartialScheduleParticipantMapper scheduleParticipantMapper;
	private final MartialAthleteMapper athleteMapper;
	private final MartialCompetitionMapper competitionMapper;
	private final MartialVenueMapper venueMapper;
	private final MartialProjectMapper projectMapper;

	@Override
	public List<Long> getUnlockedCompetitions() {
		// 查询所有未锁定的赛事(schedule_status != 2)
		LambdaQueryWrapper<MartialScheduleStatus> wrapper = new LambdaQueryWrapper<>();
		wrapper.ne(MartialScheduleStatus::getScheduleStatus, 2)
			.eq(MartialScheduleStatus::getIsDeleted, 0);

		List<MartialScheduleStatus> statusList = scheduleStatusMapper.selectList(wrapper);
		return statusList.stream()
			.map(MartialScheduleStatus::getCompetitionId)
			.collect(Collectors.toList());
	}

	@Override
	@Transactional(rollbackFor = Exception.class)
	public void autoArrange(Long competitionId) {
		log.info("开始自动编排赛程, competitionId: {}", competitionId);

		try {
			// 1. 检查赛事状态
			MartialScheduleStatus status = getOrCreateScheduleStatus(competitionId);
			if (status.getScheduleStatus() == 2) {
				log.info("赛事已锁定,跳过自动编排, competitionId: {}", competitionId);
				return;
			}

			// 2. 加载赛事信息
			MartialCompetition competition = competitionMapper.selectById(competitionId);
			if (competition == null) {
				log.error("赛事不存在, competitionId: {}", competitionId);
				return;
			}

			// 3. 加载场地列表
			List<MartialVenue> venues = loadVenues(competitionId);
			if (venues.isEmpty()) {
				log.warn("赛事没有配置场地, competitionId: {}", competitionId);
				return;
			}

			// 4. 加载参赛者列表
			List<MartialAthlete> athletes = loadAthletes(competitionId);
			if (athletes.isEmpty()) {
				log.warn("赛事没有参赛者, competitionId: {}", competitionId);
				return;
			}

			// 5. 生成时间段网格
			List<TimeSlot> timeSlots = generateTimeSlots(competition);

			// 6. 自动分组
			List<ScheduleGroupData> groups = autoGroupParticipants(athletes);

			// 7. 验证容量是否足够
			validateCapacity(groups, venues, timeSlots);

			// 8. 分配场地和时间段(轮询均匀分配)
			assignVenueAndTimeSlot(groups, venues, timeSlots);

			// 8. 清空旧的编排数据
			clearOldScheduleData(competitionId);

			// 9. 保存新的编排结果
			saveScheduleData(competitionId, groups);

			// 10. 更新编排状态
			status.setScheduleStatus(1);
			status.setLastAutoScheduleTime(LocalDateTime.now());
			status.setTotalGroups(groups.size());
			status.setTotalParticipants(athletes.size());
			status.setUpdateTime(new Date());
			scheduleStatusMapper.updateById(status);

			log.info("自动编排完成, competitionId: {}, 分组数: {}, 参赛人数: {}",
				competitionId, groups.size(), athletes.size());

		} catch (Exception e) {
			log.error("自动编排失败, competitionId: {}", competitionId, e);
			throw new RuntimeException("自动编排失败: " + e.getMessage(), e);
		}
	}

	@Override
	@Transactional(rollbackFor = Exception.class)
	public void saveAndLock(Long competitionId, String userId) {
		log.info("保存并锁定编排, competitionId: {}, userId: {}", competitionId, userId);

		MartialScheduleStatus status = getOrCreateScheduleStatus(competitionId);
		status.setScheduleStatus(2);
		status.setLockedTime(LocalDateTime.now());
		status.setLockedBy(userId);
		status.setUpdateTime(new Date());

		scheduleStatusMapper.updateById(status);

		log.info("编排已保存并锁定, competitionId: {}", competitionId);
	}

	@Override
	public Map<String, Object> getScheduleResult(Long competitionId) {
		// 获取编排状态
		MartialScheduleStatus status = getOrCreateScheduleStatus(competitionId);

		// 获取分组列表
		LambdaQueryWrapper<MartialScheduleGroup> groupWrapper = new LambdaQueryWrapper<>();
		groupWrapper.eq(MartialScheduleGroup::getCompetitionId, competitionId)
			.eq(MartialScheduleGroup::getIsDeleted, 0)
			.orderByAsc(MartialScheduleGroup::getDisplayOrder);

		List<MartialScheduleGroup> groups = scheduleGroupMapper.selectList(groupWrapper);

		// 构建前端需要的数据结构
		List<Map<String, Object>> scheduleGroups = new ArrayList<>();

		for (MartialScheduleGroup group : groups) {
			Map<String, Object> groupData = new HashMap<>();
			groupData.put("id", group.getId());
			groupData.put("groupName", group.getGroupName());
			groupData.put("projectType", group.getProjectType());
			groupData.put("displayOrder", group.getDisplayOrder());
			groupData.put("totalParticipants", group.getTotalParticipants());
			groupData.put("totalTeams", group.getTotalTeams());

			// 获取该分组的场地时间段详情
			LambdaQueryWrapper<MartialScheduleDetail> detailWrapper = new LambdaQueryWrapper<>();
			detailWrapper.eq(MartialScheduleDetail::getScheduleGroupId, group.getId())
				.eq(MartialScheduleDetail::getIsDeleted, 0);
			List<MartialScheduleDetail> details = scheduleDetailMapper.selectList(detailWrapper);

			List<Map<String, Object>> scheduleDetails = new ArrayList<>();
			for (MartialScheduleDetail detail : details) {
				Map<String, Object> detailData = new HashMap<>();
				detailData.put("venueId", detail.getVenueId());
				detailData.put("venueName", detail.getVenueName());
				detailData.put("scheduleDate", detail.getScheduleDate());
				detailData.put("timeSlot", detail.getTimeSlot());
				detailData.put("timePeriod", detail.getTimePeriod());
				scheduleDetails.add(detailData);
			}
			groupData.put("scheduleDetails", scheduleDetails);

			// 获取该分组的参赛者
			LambdaQueryWrapper<MartialScheduleParticipant> participantWrapper = new LambdaQueryWrapper<>();
			participantWrapper.eq(MartialScheduleParticipant::getScheduleGroupId, group.getId())
				.eq(MartialScheduleParticipant::getIsDeleted, 0);
			List<MartialScheduleParticipant> participants = scheduleParticipantMapper.selectList(participantWrapper);

			if (group.getProjectType() == 2) {
				// 集体项目:按单位分组
				Map<String, List<MartialScheduleParticipant>> orgGroupMap = participants.stream()
					.collect(Collectors.groupingBy(MartialScheduleParticipant::getOrganization));

				List<Map<String, Object>> organizationGroups = new ArrayList<>();
				for (Map.Entry<String, List<MartialScheduleParticipant>> entry : orgGroupMap.entrySet()) {
					Map<String, Object> orgGroup = new HashMap<>();
					orgGroup.put("organization", entry.getKey());

					List<Map<String, Object>> orgParticipants = new ArrayList<>();
					for (MartialScheduleParticipant p : entry.getValue()) {
						Map<String, Object> pData = new HashMap<>();
						pData.put("playerName", p.getPlayerName());
						orgParticipants.add(pData);
					}
					orgGroup.put("participants", orgParticipants);

					// 获取该单位的场地时间段
					orgGroup.put("scheduleDetails", scheduleDetails);

					organizationGroups.add(orgGroup);
				}
				groupData.put("organizationGroups", organizationGroups);

			} else {
				// 个人项目:直接列出参赛者
				List<Map<String, Object>> individualParticipants = new ArrayList<>();
				for (MartialScheduleParticipant p : participants) {
					Map<String, Object> pData = new HashMap<>();
					pData.put("id", p.getParticipantId());
					pData.put("organization", p.getOrganization());
					pData.put("playerName", p.getPlayerName());

					// 获取该参赛者的场地时间段
					LambdaQueryWrapper<MartialScheduleDetail> pDetailWrapper = new LambdaQueryWrapper<>();
					pDetailWrapper.eq(MartialScheduleDetail::getId, p.getScheduleDetailId())
						.eq(MartialScheduleDetail::getIsDeleted, 0)
						.last("LIMIT 1");
					MartialScheduleDetail pDetail = scheduleDetailMapper.selectOne(pDetailWrapper);

					if (pDetail != null) {
						Map<String, Object> scheduleDetail = new HashMap<>();
						scheduleDetail.put("venueId", pDetail.getVenueId());
						scheduleDetail.put("venueName", pDetail.getVenueName());
						scheduleDetail.put("scheduleDate", pDetail.getScheduleDate());
						scheduleDetail.put("timeSlot", pDetail.getTimeSlot());
						pData.put("scheduleDetail", scheduleDetail);
					}

					individualParticipants.add(pData);
				}
				groupData.put("participants", individualParticipants);
			}

			scheduleGroups.add(groupData);
		}

		// 构建返回结果
		Map<String, Object> result = new HashMap<>();
		result.put("scheduleStatus", status.getScheduleStatus());
		result.put("lastAutoScheduleTime", status.getLastAutoScheduleTime());
		result.put("totalGroups", status.getTotalGroups());
		result.put("totalParticipants", status.getTotalParticipants());
		result.put("scheduleGroups", scheduleGroups);

		return result;
	}

	// ==================== 私有辅助方法 ====================

	private MartialScheduleStatus getOrCreateScheduleStatus(Long competitionId) {
		LambdaQueryWrapper<MartialScheduleStatus> wrapper = new LambdaQueryWrapper<>();
		wrapper.eq(MartialScheduleStatus::getCompetitionId, competitionId)
			.eq(MartialScheduleStatus::getIsDeleted, 0)
			.last("LIMIT 1");

		MartialScheduleStatus status = scheduleStatusMapper.selectOne(wrapper);

		if (status == null) {
			status = new MartialScheduleStatus();
			status.setCompetitionId(competitionId);
			status.setScheduleStatus(0);
			status.setTotalGroups(0);
			status.setTotalParticipants(0);
			status.setCreateTime(new Date());
			scheduleStatusMapper.insert(status);
		}

		return status;
	}

	private List<MartialVenue> loadVenues(Long competitionId) {
		LambdaQueryWrapper<MartialVenue> wrapper = new LambdaQueryWrapper<>();
		wrapper.eq(MartialVenue::getCompetitionId, competitionId)
			.eq(MartialVenue::getIsDeleted, 0);
		return venueMapper.selectList(wrapper);
	}

	private List<MartialAthlete> loadAthletes(Long competitionId) {
		LambdaQueryWrapper<MartialAthlete> wrapper = new LambdaQueryWrapper<>();
		wrapper.eq(MartialAthlete::getCompetitionId, competitionId)
			.eq(MartialAthlete::getIsDeleted, 0);
		return athleteMapper.selectList(wrapper);
	}

	private List<TimeSlot> generateTimeSlots(MartialCompetition competition) {
		List<TimeSlot> timeSlots = new ArrayList<>();

		LocalDateTime startTime = competition.getCompetitionStartTime();
		LocalDateTime endTime = competition.getCompetitionEndTime();

		if (startTime == null || endTime == null) {
			log.warn("赛事时间信息不完整, 使用默认时间段");
			return timeSlots;
		}

		LocalDate currentDate = startTime.toLocalDate();
		LocalDate endDate = endTime.toLocalDate();

		while (!currentDate.isAfter(endDate)) {
			// 上午时段 (08:00-12:00, 共240分钟)
			TimeSlot morning = new TimeSlot();
			morning.setDate(currentDate);
			morning.setPeriod("morning");
			morning.setStartTime("08:30");
			morning.setCapacity(480); // 上午480分钟(8小时,允许并发/灵活安排)
			timeSlots.add(morning);

			// 下午时段 (13:00-18:00, 共300分钟)
			TimeSlot afternoon = new TimeSlot();
			afternoon.setDate(currentDate);
			afternoon.setPeriod("afternoon");
			afternoon.setStartTime("13:30");
			afternoon.setCapacity(480); // 下午480分钟(8小时,允许并发/灵活安排)
			timeSlots.add(afternoon);

			currentDate = currentDate.plusDays(1);
		}

		return timeSlots;
	}

	private List<ScheduleGroupData> autoGroupParticipants(List<MartialAthlete> athletes) {
		List<ScheduleGroupData> groups = new ArrayList<>();
		int displayOrder = 1;

		// 先加载所有项目信息(用于获取项目类型和名称)
		Set<Long> projectIds = athletes.stream()
			.map(MartialAthlete::getProjectId)
			.filter(id -> id != null)
			.collect(Collectors.toSet());

		Map<Long, MartialProject> projectMap = new HashMap<>();
		for (Long projectId : projectIds) {
			MartialProject project = projectMapper.selectById(projectId);
			if (project != null) {
				projectMap.put(projectId, project);
			}
		}

		// 分离集体和个人项目(根据项目表的type字段: 1=个人, 2=双人, 3=集体)
		List<MartialAthlete> teamAthletes = athletes.stream()
			.filter(a -> {
				MartialProject project = projectMap.get(a.getProjectId());
				return project != null && (project.getType() == 2 || project.getType() == 3);
			})
			.collect(Collectors.toList());

		List<MartialAthlete> individualAthletes = athletes.stream()
			.filter(a -> {
				MartialProject project = projectMap.get(a.getProjectId());
				return project != null && project.getType() == 1;
			})
			.collect(Collectors.toList());

		// 集体项目分组:按"项目ID_组别"分组
		Map<String, List<MartialAthlete>> teamGroupMap = teamAthletes.stream()
			.collect(Collectors.groupingBy(a ->
				a.getProjectId() + "_" + Func.toStr(a.getCategory(), "未分组")
			));

		for (Map.Entry<String, List<MartialAthlete>> entry : teamGroupMap.entrySet()) {
			List<MartialAthlete> members = entry.getValue();
			if (members.isEmpty()) continue;

			MartialAthlete first = members.get(0);
			MartialProject project = projectMap.get(first.getProjectId());

			// 统计队伍数(按单位分组)
			long teamCount = members.stream()
				.map(MartialAthlete::getOrganization)
				.filter(org -> org != null && !org.isEmpty())
				.distinct()
				.count();

			// 跳过没有项目信息的分组
			if (project == null) {
				log.warn("项目不存在, projectId: {}, 跳过该分组", first.getProjectId());
				continue;
			}

			ScheduleGroupData group = new ScheduleGroupData();
			String projectName = project.getProjectName();
			group.setGroupName(projectName + " " + (first.getCategory() != null ? first.getCategory() : "未分组"));
			group.setProjectId(first.getProjectId());
			group.setProjectType(project.getType() == 3 ? 2 : 1); // type=3映射为projectType=2(集体)
			group.setDisplayOrder(displayOrder++);
			group.setTotalParticipants(members.size());
			group.setTotalTeams((int) teamCount);
			group.setAthletes(members);

			// 计算预计时长:使用项目的 estimatedDuration
			// 如果项目设置了时长,使用 队伍数 × 项目时长
			// 否则使用默认计算: 队伍数 × 5分钟 + 间隔时间
			int duration;
			if (project.getEstimatedDuration() != null && project.getEstimatedDuration() > 0) {
				duration = (int) teamCount * project.getEstimatedDuration();
				log.debug("集体项目 '{}': 使用项目时长 {}分钟/队, {}队, 总计{}分钟",
					projectName, project.getEstimatedDuration(), teamCount, duration);
			} else {
				duration = (int) teamCount * 5 + Math.max(0, (int) teamCount - 1) * 2;
				log.debug("集体项目 '{}': 使用默认时长, {}队, 总计{}分钟", projectName, teamCount, duration);
			}
			group.setEstimatedDuration(duration);

			groups.add(group);
		}

		// 个人项目分组:按"项目ID_组别"分组
		Map<String, List<MartialAthlete>> individualGroupMap = individualAthletes.stream()
			.collect(Collectors.groupingBy(a ->
				a.getProjectId() + "_" + Func.toStr(a.getCategory(), "未分组")
			));

		for (Map.Entry<String, List<MartialAthlete>> entry : individualGroupMap.entrySet()) {
			List<MartialAthlete> members = entry.getValue();
			if (members.isEmpty()) continue;

			MartialAthlete first = members.get(0);
			MartialProject project = projectMap.get(first.getProjectId());

			// 跳过没有项目信息的分组
			if (project == null) {
				log.warn("项目不存在, projectId: {}, 跳过该分组", first.getProjectId());
				continue;
			}

			String projectName = project.getProjectName();
			String categoryName = first.getCategory() != null ? first.getCategory() : "未分组";

			// 计算单人时长
			int durationPerPerson = 5; // 默认5分钟/人
			if (project.getEstimatedDuration() != null && project.getEstimatedDuration() > 0) {
				durationPerPerson = project.getEstimatedDuration();
			}

			// 自动拆分大组:如果人数过多,拆分成多个小组
			// 改进策略:根据人数动态拆分,确保能充分利用所有时间槽
			// 目标:让分组数量接近可用时间槽数量,实现均匀分配
			int maxPeoplePerGroup;
			if (members.size() <= 35) {
				// 35人以内不拆分
				maxPeoplePerGroup = members.size();
			} else {
				// 超过35人,按每组30-35人拆分
				maxPeoplePerGroup = 33; // 每组33人左右
			}

			if (members.size() <= maxPeoplePerGroup) {
				// 人数不多,不需要拆分
				ScheduleGroupData group = new ScheduleGroupData();
				group.setGroupName(projectName + " " + categoryName);
				group.setProjectId(first.getProjectId());
				group.setProjectType(1);
				group.setDisplayOrder(displayOrder++);
				group.setTotalParticipants(members.size());
				group.setAthletes(members);

				int duration = members.size() * durationPerPerson;
				group.setEstimatedDuration(duration);

				log.debug("个人项目 '{}': {}人, {}分钟/人, 总计{}分钟",
					projectName + " " + categoryName, members.size(), durationPerPerson, duration);

				groups.add(group);
			} else {
				// 人数太多,需要拆分成多个小组
				int totalPeople = members.size();
				int numSubGroups = (int) Math.ceil((double) totalPeople / maxPeoplePerGroup);

				log.info("个人项目 '{}' 有{}人,将拆分成{}个小组(每组最多{}人)",
					projectName + " " + categoryName, totalPeople, numSubGroups, maxPeoplePerGroup);

				for (int i = 0; i < numSubGroups; i++) {
					int startIdx = i * maxPeoplePerGroup;
					int endIdx = Math.min(startIdx + maxPeoplePerGroup, totalPeople);
					List<MartialAthlete> subGroupMembers = members.subList(startIdx, endIdx);

					ScheduleGroupData group = new ScheduleGroupData();
					group.setGroupName(projectName + " " + categoryName + " 第" + (i + 1) + "组");
					group.setProjectId(first.getProjectId());
					group.setProjectType(1);
					group.setDisplayOrder(displayOrder++);
					group.setTotalParticipants(subGroupMembers.size());
					group.setAthletes(new ArrayList<>(subGroupMembers));

					int duration = subGroupMembers.size() * durationPerPerson;
					group.setEstimatedDuration(duration);

					log.debug("  拆分子组 '第{}组': {}人, 总计{}分钟",
						i + 1, subGroupMembers.size(), duration);

					groups.add(group);
				}
			}
		}

		return groups;
	}

	/**
	 * 验证时间窗口容量是否足够容纳所有分组
	 */
	private void validateCapacity(List<ScheduleGroupData> groups,
								   List<MartialVenue> venues,
								   List<TimeSlot> timeSlots) {
		// 计算总需求时长
		int totalDuration = groups.stream()
			.mapToInt(ScheduleGroupData::getEstimatedDuration)
			.sum();

		// 计算总可用容量
		int totalCapacity = venues.size() * timeSlots.size() * (timeSlots.isEmpty() ? 0 : timeSlots.get(0).getCapacity());

		log.info("=== 容量验证 ===");
		log.info("分组总需求时长: {} 分钟", totalDuration);
		log.info("总可用容量: {} 分钟 ({}个场地 × {}个时段 × {}分钟/时段)",
			totalCapacity, venues.size(), timeSlots.size(),
			timeSlots.isEmpty() ? 0 : timeSlots.get(0).getCapacity());

		if (totalDuration > totalCapacity) {
			String errorMsg = String.format(
				"时间窗口容量不足! 需要 %d 分钟, 但只有 %d 分钟可用 (缺口: %d 分钟)",
				totalDuration, totalCapacity, totalDuration - totalCapacity
			);
			log.error(errorMsg);
			throw new RuntimeException(errorMsg);
		}

		double utilizationRate = totalCapacity > 0 ? (totalDuration * 100.0 / totalCapacity) : 0;
		log.info("预计容量利用率: {}%", (int)utilizationRate);

		if (utilizationRate > 90) {
			log.warn("⚠️ 容量利用率超过90%,可能导致分配困难,建议增加场地或延长比赛时间");
		}
	}

	private void assignVenueAndTimeSlot(List<ScheduleGroupData> groups,
										 List<MartialVenue> venues,
										 List<TimeSlot> timeSlots) {
		log.info("=== 开始分配场地和时间段 ===");
		log.info("场地数量: {}, 时间段数量: {}, 分组数量: {}", venues.size(), timeSlots.size(), groups.size());

		// 创建所有槽位(场地 × 时间段组合)
		List<SlotInfo> slots = new ArrayList<>();
		int timeSlotIndex = 0; // 时间段索引,对应前端的 timeSlotIndex
		for (TimeSlot timeSlot : timeSlots) { // 先遍历时间段
			for (MartialVenue venue : venues) { // 再遍历场地
				SlotInfo slot = new SlotInfo();
				slot.timeSlotIndex = timeSlotIndex; // 同一时间段的所有场地,共享相同的 timeSlotIndex
				slot.venueId = venue.getId();
				slot.venueName = venue.getVenueName();
				slot.date = timeSlot.getDate();
				slot.timeSlot = timeSlot.getStartTime();
				slot.period = timeSlot.getPeriod();
				slot.capacity = timeSlot.getCapacity();
				slot.currentLoad = 0;
				slots.add(slot);
			}
			timeSlotIndex++; // 每个时间段结束后,索引+1
		}

		log.info("总共初始化了 {} 个场地×时间段组合", slots.size());

		// 按预计时长降序排序(先安排时间长的)
		groups.sort((a, b) -> b.getEstimatedDuration() - a.getEstimatedDuration());

		// 使用轮询算法进行均匀分配
		int assignedCount = 0;
		int currentSlotIndex = 0;

		for (ScheduleGroupData group : groups) {
			SlotInfo bestSlot = null;
			int attempts = 0;
			int maxAttempts = slots.size();

			// 第一轮:从当前槽位开始轮询,寻找第一个容量足够的槽位
			int startIndex = currentSlotIndex;
			while (attempts < maxAttempts) {
				SlotInfo slot = slots.get(currentSlotIndex);

				// 检查容量是否足够
				if (slot.currentLoad + group.getEstimatedDuration() <= slot.capacity) {
					bestSlot = slot;
					break;
				}

				// 尝试下一个槽位
				currentSlotIndex = (currentSlotIndex + 1) % slots.size();
				attempts++;
			}

			// 第二轮:如果没有找到容量足够的槽位,选择负载最小的槽位(允许超载)
			if (bestSlot == null) {
				log.warn("分组 '{}' 需要{}分钟,超过单个槽位容量{}分钟,将选择负载最小的槽位(允许超载)",
					group.getGroupName(), group.getEstimatedDuration(), slots.get(0).capacity);

				bestSlot = slots.stream()
					.min((a, b) -> Integer.compare(a.currentLoad, b.currentLoad))
					.orElse(slots.get(0));

				// 设置currentSlotIndex到bestSlot的位置
				for (int i = 0; i < slots.size(); i++) {
					if (slots.get(i) == bestSlot) {
						currentSlotIndex = i;
						break;
					}
				}
			}

			// 分配到选定的槽位
			group.setAssignedVenueId(bestSlot.venueId);
			group.setAssignedVenueName(bestSlot.venueName);
			group.setAssignedDate(bestSlot.date);
			group.setAssignedTimeSlot(bestSlot.timeSlot);
			group.setAssignedTimeSlotIndex(bestSlot.timeSlotIndex); // 保存时间段索引
			group.setAssignedTimePeriod(bestSlot.period);

			// 更新槽位负载
			bestSlot.currentLoad += group.getEstimatedDuration();
			assignedCount++;

			log.info("分组 '{}' 分配到: 场地ID={}, 场地名={}, 日期={}, 时间段={}, 预计时长={}分钟, 新负载={}分钟 ({}%)",
				group.getGroupName(), bestSlot.venueId, bestSlot.venueName, bestSlot.date, bestSlot.timeSlot,
				group.getEstimatedDuration(), bestSlot.currentLoad,
				(int)(bestSlot.currentLoad * 100.0 / bestSlot.capacity));

			// 移动到下一个槽位(轮询)
			currentSlotIndex = (currentSlotIndex + 1) % slots.size();
		}

		log.info("=== 分配完成: {}/{} 个分组成功分配 ===", assignedCount, groups.size());

		// 输出每个槽位的负载情况
		log.info("=== 各槽位负载统计 ===");
		for (SlotInfo slot : slots) {
			if (slot.currentLoad > 0) {
				log.info("场地={}, 日期={}, 时段={}, 负载={}/{}分钟 ({}%)",
					slot.venueName, slot.date, slot.timeSlot, slot.currentLoad, slot.capacity,
					(int)(slot.currentLoad * 100.0 / slot.capacity));
			}
		}
	}

	// 槽位信息内部类
	private static class SlotInfo {
		int timeSlotIndex; // 时间段索引 (0=第1天上午, 1=第1天下午, 2=第2天上午, ...)
		Long venueId;
		String venueName;
		LocalDate date;
		String timeSlot;
		String period;
		int capacity;
		int currentLoad;
	}

	private void clearOldScheduleData(Long competitionId) {
		// 删除旧的编排数据
		LambdaQueryWrapper<MartialScheduleGroup> groupWrapper = new LambdaQueryWrapper<>();
		groupWrapper.eq(MartialScheduleGroup::getCompetitionId, competitionId);

		// 先查询出所有分组ID,然后再删除
		List<Long> groupIds = scheduleGroupMapper.selectList(groupWrapper).stream()
			.map(MartialScheduleGroup::getId)
			.collect(Collectors.toList());

		// 删除参赛者关联(必须在删除分组之前)
		if (groupIds != null && !groupIds.isEmpty()) {
			LambdaQueryWrapper<MartialScheduleParticipant> participantWrapper = new LambdaQueryWrapper<>();
			participantWrapper.in(MartialScheduleParticipant::getScheduleGroupId, groupIds);
			scheduleParticipantMapper.delete(participantWrapper);
		}

		// 删除场地时间段详情
		LambdaQueryWrapper<MartialScheduleDetail> detailWrapper = new LambdaQueryWrapper<>();
		detailWrapper.eq(MartialScheduleDetail::getCompetitionId, competitionId);
		scheduleDetailMapper.delete(detailWrapper);

		// 最后删除分组
		scheduleGroupMapper.delete(groupWrapper);
	}

	private void saveScheduleData(Long competitionId, List<ScheduleGroupData> groups) {
		for (ScheduleGroupData groupData : groups) {
			// 保存分组
			MartialScheduleGroup group = new MartialScheduleGroup();
			group.setCompetitionId(competitionId);
			group.setGroupName(groupData.getGroupName());
			group.setProjectId(groupData.getProjectId());
			group.setProjectName(groupData.getGroupName());
			group.setProjectType(groupData.getProjectType());
			group.setDisplayOrder(groupData.getDisplayOrder());
			group.setTotalParticipants(groupData.getTotalParticipants());
			group.setTotalTeams(groupData.getTotalTeams());
			group.setEstimatedDuration(groupData.getEstimatedDuration());
			group.setCreateTime(new Date());
			scheduleGroupMapper.insert(group);

			Long groupId = group.getId();

			// 保存场地时间段详情
			if (groupData.getAssignedVenueId() != null) {
				MartialScheduleDetail detail = new MartialScheduleDetail();
				detail.setScheduleGroupId(groupId);
				detail.setCompetitionId(competitionId);
				detail.setVenueId(groupData.getAssignedVenueId());
				detail.setVenueName(groupData.getAssignedVenueName());
				detail.setScheduleDate(groupData.getAssignedDate());
				detail.setTimePeriod(groupData.getAssignedTimePeriod());
				detail.setTimeSlot(groupData.getAssignedTimeSlot());
				detail.setTimeSlotIndex(groupData.getAssignedTimeSlotIndex()); // 保存时间段索引
				detail.setEstimatedDuration(groupData.getEstimatedDuration());
				detail.setParticipantCount(groupData.getTotalParticipants());
				detail.setCreateTime(new Date());
				scheduleDetailMapper.insert(detail);

				Long detailId = detail.getId();

				// 保存参赛者关联
				int order = 1;
				for (MartialAthlete athlete : groupData.getAthletes()) {
					MartialScheduleParticipant participant = new MartialScheduleParticipant();
					participant.setScheduleDetailId(detailId);
					participant.setScheduleGroupId(groupId);
					participant.setParticipantId(athlete.getId());
					participant.setOrganization(athlete.getTeamName() != null && !athlete.getTeamName().isEmpty() ? athlete.getTeamName() : athlete.getOrganization());
					participant.setPlayerName(athlete.getPlayerName());
					participant.setProjectName(groupData.getGroupName());
					participant.setCategory(athlete.getCategory());
					participant.setPerformanceOrder(order++);
					participant.setScheduleStatus("draft"); // 初始状态为草稿
					participant.setCreateTime(new Date());
					scheduleParticipantMapper.insert(participant);
				}
			}
		}
	}

	// ==================== 内部数据类 ====================

	private static class TimeSlot {
		private LocalDate date;
		private String period; // morning/afternoon
		private String startTime; // 08:30/13:30
		private Integer capacity; // 容量(分钟)

		// Getters and Setters
		public LocalDate getDate() {
			return date;
		}

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

		public String getPeriod() {
			return period;
		}

		public void setPeriod(String period) {
			this.period = period;
		}

		public String getStartTime() {
			return startTime;
		}

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

		public Integer getCapacity() {
			return capacity;
		}

		public void setCapacity(Integer capacity) {
			this.capacity = capacity;
		}
	}

	private static class ScheduleGroupData {
		private String groupName;
		private Long projectId;
		private Integer projectType;
		private Integer displayOrder;
		private Integer totalParticipants;
		private Integer totalTeams;
		private Integer estimatedDuration;
		private List<MartialAthlete> athletes;

		// 分配结果
		private Long assignedVenueId;
		private String assignedVenueName;
		private LocalDate assignedDate;
		private String assignedTimePeriod;
		private String assignedTimeSlot;
		private Integer assignedTimeSlotIndex; // 时间段索引

		// Getters and Setters
		public String getGroupName() {
			return groupName;
		}

		public void setGroupName(String groupName) {
			this.groupName = groupName;
		}

		public Long getProjectId() {
			return projectId;
		}

		public void setProjectId(Long projectId) {
			this.projectId = projectId;
		}

		public Integer getProjectType() {
			return projectType;
		}

		public void setProjectType(Integer projectType) {
			this.projectType = projectType;
		}

		public Integer getDisplayOrder() {
			return displayOrder;
		}

		public void setDisplayOrder(Integer displayOrder) {
			this.displayOrder = displayOrder;
		}

		public Integer getTotalParticipants() {
			return totalParticipants;
		}

		public void setTotalParticipants(Integer totalParticipants) {
			this.totalParticipants = totalParticipants;
		}

		public Integer getTotalTeams() {
			return totalTeams;
		}

		public void setTotalTeams(Integer totalTeams) {
			this.totalTeams = totalTeams;
		}

		public Integer getEstimatedDuration() {
			return estimatedDuration;
		}

		public void setEstimatedDuration(Integer estimatedDuration) {
			this.estimatedDuration = estimatedDuration;
		}

		public List<MartialAthlete> getAthletes() {
			return athletes;
		}

		public void setAthletes(List<MartialAthlete> athletes) {
			this.athletes = athletes;
		}

		public Long getAssignedVenueId() {
			return assignedVenueId;
		}

		public void setAssignedVenueId(Long assignedVenueId) {
			this.assignedVenueId = assignedVenueId;
		}

		public String getAssignedVenueName() {
			return assignedVenueName;
		}

		public void setAssignedVenueName(String assignedVenueName) {
			this.assignedVenueName = assignedVenueName;
		}

		public LocalDate getAssignedDate() {
			return assignedDate;
		}

		public void setAssignedDate(LocalDate assignedDate) {
			this.assignedDate = assignedDate;
		}

		public String getAssignedTimePeriod() {
			return assignedTimePeriod;
		}

		public void setAssignedTimePeriod(String assignedTimePeriod) {
			this.assignedTimePeriod = assignedTimePeriod;
		}

		public String getAssignedTimeSlot() {
			return assignedTimeSlot;
		}

		public void setAssignedTimeSlot(String assignedTimeSlot) {
			this.assignedTimeSlot = assignedTimeSlot;
		}

		public Integer getAssignedTimeSlotIndex() {
			return assignedTimeSlotIndex;
		}

		public void setAssignedTimeSlotIndex(Integer assignedTimeSlotIndex) {
			this.assignedTimeSlotIndex = assignedTimeSlotIndex;
		}
	}

}
