package org.springblade.modules.martial;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
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.springblade.modules.martial.service.impl.MartialSchedulePlanServiceImpl;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;

/**
 * 编排调度服务测试类
 *
 * @author BladeX
 */
@ExtendWith(MockitoExtension.class)
@DisplayName("编排调度功能测试")
public class MartialSchedulePlanServiceTest {

	@InjectMocks
	private MartialSchedulePlanServiceImpl schedulePlanService;

	@Mock
	private MartialSchedulePlanMapper schedulePlanMapper;

	@Mock
	private MartialScheduleSlotMapper slotMapper;

	@Mock
	private MartialScheduleAthleteSlotMapper athleteSlotMapper;

	@Mock
	private MartialScheduleConflictMapper conflictMapper;

	@Mock
	private MartialScheduleAdjustmentLogMapper adjustmentLogMapper;

	@Mock
	private IMartialCompetitionService competitionService;

	@Mock
	private IMartialProjectService projectService;

	@Mock
	private IMartialVenueService venueService;

	@Mock
	private IMartialAthleteService athleteService;

	@Mock
	private IMartialRegistrationOrderService registrationOrderService;

	private MartialCompetition testCompetition;
	private MartialProject testProject;
	private MartialVenue testVenue;
	private MartialAthlete testAthlete;
	private MartialSchedulePlan testPlan;

	@BeforeEach
	void setUp() {
		// 准备测试数据
		testCompetition = new MartialCompetition();
		testCompetition.setId(1L);
		testCompetition.setCompetitionName("2025年武术大赛");
		testCompetition.setCompetitionStartTime(LocalDateTime.of(2025, 12, 1, 9, 0));
		testCompetition.setCompetitionEndTime(LocalDateTime.of(2025, 12, 1, 18, 0));

		testProject = new MartialProject();
		testProject.setId(1L);
		testProject.setProjectName("长拳");
		testProject.setType(1); // 个人项目
		testProject.setEstimatedDuration(10); // 10分钟

		testVenue = new MartialVenue();
		testVenue.setId(1L);
		testVenue.setVenueName("A场地");

		testAthlete = new MartialAthlete();
		testAthlete.setId(1L);
		testAthlete.setPlayerName("张三");

		testPlan = new MartialSchedulePlan();
		testPlan.setId(1L);
		testPlan.setCompetitionId(1L);
		testPlan.setPlanName("测试编排方案");
		testPlan.setStatus(0);
		testPlan.setConflictCount(0);
	}

	@Test
	@DisplayName("测试自动编排 - 基本流程")
	void testAutoSchedule_BasicFlow() {
		// Given: 准备基础数据
		testCompetition = new MartialCompetition();
		testCompetition.setId(1L);
		testCompetition.setCompetitionName("2025年武术大赛");
		testCompetition.setCompetitionStartTime(LocalDateTime.of(2025, 12, 1, 9, 0));
		testCompetition.setCompetitionEndTime(LocalDateTime.of(2025, 12, 1, 18, 0));

		// 验证赛事数据加载正确
		assertNotNull(testCompetition);
		assertEquals("2025年武术大赛", testCompetition.getCompetitionName());
		assertNotNull(testCompetition.getCompetitionStartTime());
		assertNotNull(testCompetition.getCompetitionEndTime());

		// 验证时间范围合理
		assertTrue(testCompetition.getCompetitionEndTime().isAfter(testCompetition.getCompetitionStartTime()));
	}

	@Test
	@DisplayName("测试自动编排 - 赛事不存在")
	void testAutoSchedule_CompetitionNotFound() {
		// Given: 赛事不存在
		when(competitionService.getById(anyLong())).thenReturn(null);

		// Then: 应该抛出异常
		assertThrows(ServiceException.class, () -> {
			schedulePlanService.autoSchedule(999L);
		});
	}

	@Test
	@DisplayName("测试自动编排 - 没有配置项目")
	void testAutoSchedule_NoProjects() {
		// Given: 赛事存在但没有项目
		when(competitionService.getById(1L)).thenReturn(testCompetition);
		when(projectService.list(any(QueryWrapper.class))).thenReturn(new ArrayList<>());

		// Then: 应该抛出异常
		assertThrows(ServiceException.class, () -> {
			schedulePlanService.autoSchedule(1L);
		});
	}

	@Test
	@DisplayName("测试自动编排 - 没有配置场地")
	void testAutoSchedule_NoVenues() {
		// Given: 赛事和项目存在但没有场地
		when(competitionService.getById(1L)).thenReturn(testCompetition);

		List<MartialProject> projects = Arrays.asList(testProject);
		when(projectService.list(any(QueryWrapper.class))).thenReturn(projects);

		when(venueService.list(any(QueryWrapper.class))).thenReturn(new ArrayList<>());

		// Then: 应该抛出异常
		assertThrows(ServiceException.class, () -> {
			schedulePlanService.autoSchedule(1L);
		});
	}

	@Test
	@DisplayName("测试项目排序 - 集体项目优先")
	void testProjectSorting_GroupProjectFirst() {
		// Given: 3个项目，类型不同
		MartialProject individual = new MartialProject();
		individual.setProjectName("长拳");
		individual.setType(1); // 个人

		MartialProject pair = new MartialProject();
		pair.setProjectName("对练");
		pair.setType(2); // 双人

		MartialProject group = new MartialProject();
		group.setProjectName("集体太极");
		group.setType(3); // 集体

		List<MartialProject> projects = new ArrayList<>();
		projects.add(individual);
		projects.add(pair);
		projects.add(group);

		// When: 排序（集体优先）
		projects.sort((a, b) -> {
			Integer typeA = a.getType() != null ? a.getType() : 1;
			Integer typeB = b.getType() != null ? b.getType() : 1;
			if (!typeA.equals(typeB)) {
				return typeB.compareTo(typeA); // 降序，3 > 2 > 1
			}
			return a.getProjectName().compareTo(b.getProjectName());
		});

		// Then: 集体项目应该在最前面
		assertEquals(3, projects.get(0).getType());
		assertEquals(2, projects.get(1).getType());
		assertEquals(1, projects.get(2).getType());
	}

	@Test
	@DisplayName("测试冲突检测 - 运动员时间冲突")
	void testDetectConflicts_AthleteTimeConflict() {
		// Given: 同一运动员被分配到两个重叠的时间槽
		Long planId = 1L;

		// 创建两个时间槽
		MartialScheduleSlot slot1 = new MartialScheduleSlot();
		slot1.setId(1L);
		slot1.setPlanId(planId);
		slot1.setSlotDate(LocalDate.of(2025, 12, 1));
		slot1.setStartTime(LocalTime.of(9, 0));
		slot1.setEndTime(LocalTime.of(9, 30));

		MartialScheduleSlot slot2 = new MartialScheduleSlot();
		slot2.setId(2L);
		slot2.setPlanId(planId);
		slot2.setSlotDate(LocalDate.of(2025, 12, 1));
		slot2.setStartTime(LocalTime.of(9, 15)); // 与slot1重叠
		slot2.setEndTime(LocalTime.of(9, 45));

		// 创建运动员-时间槽关联
		MartialScheduleAthleteSlot as1 = new MartialScheduleAthleteSlot();
		as1.setSlotId(1L);
		as1.setAthleteId(1L);

		MartialScheduleAthleteSlot as2 = new MartialScheduleAthleteSlot();
		as2.setSlotId(2L);
		as2.setAthleteId(1L); // 同一运动员

		when(athleteSlotMapper.selectList(any(QueryWrapper.class)))
			.thenReturn(Arrays.asList(as1, as2));

		when(slotMapper.selectById(1L)).thenReturn(slot1);
		when(slotMapper.selectById(2L)).thenReturn(slot2);

		// When: 执行冲突检测
		List<MartialScheduleConflict> conflicts = schedulePlanService.detectConflicts(planId);

		// Then: 应该检测到冲突
		assertNotNull(conflicts);
		// 注意：实际检测需要完整的mock，这里只验证逻辑
	}

	@Test
	@DisplayName("测试时间重叠判断 - 重叠情况")
	void testTimeOverlaps_True() {
		// Given: 两个重叠的时间段
		LocalTime start1 = LocalTime.of(9, 0);
		LocalTime end1 = LocalTime.of(9, 30);
		LocalTime start2 = LocalTime.of(9, 15);
		LocalTime end2 = LocalTime.of(9, 45);

		// When: 判断是否重叠
		boolean overlaps = start1.isBefore(end2) && start2.isBefore(end1);

		// Then: 应该重叠
		assertTrue(overlaps);
	}

	@Test
	@DisplayName("测试时间重叠判断 - 不重叠情况")
	void testTimeOverlaps_False() {
		// Given: 两个不重叠的时间段
		LocalTime start1 = LocalTime.of(9, 0);
		LocalTime end1 = LocalTime.of(9, 30);
		LocalTime start2 = LocalTime.of(10, 0);
		LocalTime end2 = LocalTime.of(10, 30);

		// When: 判断是否重叠
		boolean overlaps = start1.isBefore(end2) && start2.isBefore(end1);

		// Then: 不应该重叠
		assertFalse(overlaps);
	}

	@Test
	@DisplayName("测试移动运动员 - 目标时间槽不存在")
	void testMoveAthletes_TargetSlotNotFound() {
		// Given: 目标时间槽不存在
		MoveAthletesDTO moveDTO = new MoveAthletesDTO();
		moveDTO.setAthleteIds(Arrays.asList(1L));
		moveDTO.setFromSlotId(1L);
		moveDTO.setToSlotId(999L);
		moveDTO.setReason("测试移动");

		when(slotMapper.selectById(999L)).thenReturn(null);

		// Then: 应该抛出异常
		assertThrows(ServiceException.class, () -> {
			schedulePlanService.checkMoveConflicts(moveDTO);
		});
	}

	@Test
	@DisplayName("测试移动运动员 - 数据准备正确")
	void testMoveAthletes_DataValidation() {
		// Given: 准备移动参数
		MoveAthletesDTO moveDTO = new MoveAthletesDTO();
		moveDTO.setAthleteIds(Arrays.asList(1L, 2L, 3L));
		moveDTO.setFromSlotId(1L);
		moveDTO.setToSlotId(2L);
		moveDTO.setReason("场地调整");

		// Then: 验证数据
		assertNotNull(moveDTO.getAthleteIds());
		assertEquals(3, moveDTO.getAthleteIds().size());
		assertEquals(1L, moveDTO.getFromSlotId());
		assertEquals(2L, moveDTO.getToSlotId());
		assertEquals("场地调整", moveDTO.getReason());
	}

	@Test
	@DisplayName("测试调整出场顺序 - 数据准备")
	void testUpdateAppearanceOrder_DataValidation() {
		// Given: 准备出场顺序调整数据
		List<AthleteOrderDTO> newOrder = new ArrayList<>();

		AthleteOrderDTO order1 = new AthleteOrderDTO();
		order1.setAthleteId(1L);
		order1.setOrder(3);

		AthleteOrderDTO order2 = new AthleteOrderDTO();
		order2.setAthleteId(2L);
		order2.setOrder(1);

		AthleteOrderDTO order3 = new AthleteOrderDTO();
		order3.setAthleteId(3L);
		order3.setOrder(2);

		newOrder.add(order1);
		newOrder.add(order2);
		newOrder.add(order3);

		// Then: 验证数据
		assertEquals(3, newOrder.size());
		assertEquals(3, newOrder.get(0).getOrder());
		assertEquals(1, newOrder.get(1).getOrder());
		assertEquals(2, newOrder.get(2).getOrder());
	}

	@Test
	@DisplayName("测试确认并发布 - 方案不存在")
	void testConfirmAndPublish_PlanNotFound() {
		// Given: 方案不存在
		testPlan = null;

		// Then: 验证方案为空
		assertNull(testPlan);
	}

	@Test
	@DisplayName("测试方案状态 - 草稿状态")
	void testPlanStatus_Draft() {
		// Given: 草稿状态的方案
		testPlan.setStatus(0);

		// Then: 验证状态
		assertEquals(0, testPlan.getStatus());
	}

	@Test
	@DisplayName("测试方案状态 - 已确认状态")
	void testPlanStatus_Confirmed() {
		// Given: 已确认状态的方案
		testPlan.setStatus(1);

		// Then: 验证状态
		assertEquals(1, testPlan.getStatus());
	}

	@Test
	@DisplayName("测试方案状态 - 已发布状态")
	void testPlanStatus_Published() {
		// Given: 已发布状态的方案
		testPlan.setStatus(2);
		testPlan.setPublishedTime(LocalDateTime.now());

		// Then: 验证状态
		assertEquals(2, testPlan.getStatus());
		assertNotNull(testPlan.getPublishedTime());
	}

	@Test
	@DisplayName("测试冲突类型 - 时间冲突")
	void testConflictType_TimeConflict() {
		// Given: 时间冲突
		MartialScheduleConflict conflict = new MartialScheduleConflict();
		conflict.setConflictType(1);
		conflict.setSeverity(2);
		conflict.setEntityType("athlete");
		conflict.setConflictDescription("运动员时间冲突");

		// Then: 验证冲突信息
		assertEquals(1, conflict.getConflictType());
		assertEquals(2, conflict.getSeverity());
		assertEquals("athlete", conflict.getEntityType());
	}

	@Test
	@DisplayName("测试冲突类型 - 场地冲突")
	void testConflictType_VenueConflict() {
		// Given: 场地冲突
		MartialScheduleConflict conflict = new MartialScheduleConflict();
		conflict.setConflictType(2);
		conflict.setSeverity(3);
		conflict.setEntityType("venue");
		conflict.setConflictDescription("场地超载");

		// Then: 验证冲突信息
		assertEquals(2, conflict.getConflictType());
		assertEquals(3, conflict.getSeverity());
		assertEquals("venue", conflict.getEntityType());
	}

	@Test
	@DisplayName("测试冲突解决状态")
	void testConflictResolution() {
		// Given: 未解决的冲突
		MartialScheduleConflict conflict = new MartialScheduleConflict();
		conflict.setIsResolved(0);

		// When: 标记为已解决
		conflict.setIsResolved(1);
		conflict.setResolveMethod("手动调整时间");

		// Then: 验证状态
		assertEquals(1, conflict.getIsResolved());
		assertEquals("手动调整时间", conflict.getResolveMethod());
	}

	@Test
	@DisplayName("测试编排方案完整性")
	void testSchedulePlanCompleteness() {
		// Given: 完整的编排方案
		testPlan.setTotalMatches(50);
		testPlan.setVenueCount(3);
		testPlan.setTimeSlotDuration(30);

		// Then: 验证所有字段
		assertNotNull(testPlan.getCompetitionId());
		assertNotNull(testPlan.getPlanName());
		assertEquals(50, testPlan.getTotalMatches());
		assertEquals(3, testPlan.getVenueCount());
		assertEquals(30, testPlan.getTimeSlotDuration());
	}

}
