from api.models import EsExamRequirementStudent
from api.services.exam_scheduler import schedule_exam_requirements, schedule_exam_requirements_direct
from api.tests.helpers import (
	SchedulerTestCase,
	activity_label,
	build_exam_world,
	post_signed,
	reload_activity,
)

SCHEDULE_DIRECT_URL = "/api/admin/exam-activity/schedule-direct"


class ExamActivityScheduleDirectHttpTests(SchedulerTestCase):
	def test_pins_requested_legal_slot(self):
		world = build_exam_world()
		slot = world.slot_for(3)
		response = post_signed(
			self.client,
			SCHEDULE_DIRECT_URL,
			{"id": [world.activity.id], "time_slot": slot},
		)
		self.assertEqual(response.status_code, 200)
		body = response.json()
		self.assertEqual(body["code"], 200)
		activity = reload_activity(world.activity)
		self.assertTrue(activity.is_scheduled)
		self.assertEqual(activity.time_slot, slot)
		self.assertEqual(activity.location_id, world.location.id)
		self.assertEqual(body["data"]["unscheduled_activities"], [])
		self.assertEqual(body["data"]["activities"][0]["time_slot"], slot)

	def test_illegal_slot_stays_unscheduled(self):
		world = build_exam_world()
		illegal_slot = world.monday_slot() + 1
		response = post_signed(
			self.client,
			SCHEDULE_DIRECT_URL,
			{"id": [world.activity.id], "time_slot": illegal_slot},
		)
		self.assertEqual(response.status_code, 200)
		activity = reload_activity(world.activity)
		self.assertFalse(activity.is_scheduled)
		self.assertIsNone(activity.time_slot)
		self.assertEqual(response.json()["data"]["unscheduled_activities"], [activity_label(activity)])


class ExamActivityScheduleDirectServiceTests(SchedulerTestCase):
	def _direct(self, time_slot, *activities):
		return schedule_exam_requirements_direct(
			[activity.id for activity in activities],
			time_slot,
			self.user.id,
		)

	def test_rejects_slot_that_is_not_a_legal_session_datetime(self):
		world = build_exam_world()
		result = self._direct(world.monday_slot() + 1, world.activity)
		activity = reload_activity(world.activity)
		self.assertFalse(activity.is_scheduled)
		self.assertEqual(result["data"]["unscheduled_activities"], [activity_label(activity)])

	def test_same_time_anchor_mismatch_conflicts(self):
		world = build_exam_world(is_partition=True)
		_req2, activity2 = world.add_exam()
		world.link_same_time(world.requirement, activity2.exam_requirement)
		world.mark_scheduled(activity2, time_slot=world.slot_for(2))
		result = self._direct(world.monday_slot(), world.activity)
		activity = reload_activity(world.activity)
		self.assertFalse(activity.is_scheduled)
		self.assertEqual(result["data"]["unscheduled_activities"], [activity_label(activity)])

	def test_same_time_shared_students_conflict(self):
		world = build_exam_world(is_partition=True)
		student = world.add_student()
		_req2, activity2 = world.add_exam()
		EsExamRequirementStudent.objects.create(exam_requirement=activity2.exam_requirement, student=student)
		world.link_same_time(world.requirement, activity2.exam_requirement)
		result = self._direct(world.monday_slot(), world.activity, activity2)
		self.assertFalse(reload_activity(world.activity).is_scheduled)
		self.assertFalse(reload_activity(activity2).is_scheduled)
		self.assertEqual(
			set(result["data"]["unscheduled_activities"]),
			{activity_label(world.activity), activity_label(activity2)},
		)

	def test_packs_same_location_cohort(self):
		world = build_exam_world(is_partition=True, planned_size=10)
		_req2, activity2 = world.add_exam(planned_size=10)
		world.link_same_location(world.requirement, activity2.exam_requirement)
		result = self._direct(world.monday_slot(), world.activity, activity2)
		first = reload_activity(world.activity)
		second = reload_activity(activity2)
		self.assertTrue(first.is_scheduled)
		self.assertTrue(second.is_scheduled)
		self.assertEqual(first.time_slot, world.monday_slot())
		self.assertEqual(second.time_slot, world.monday_slot())
		self.assertEqual(first.location_id, second.location_id)
		self.assertEqual(result["data"]["unscheduled_activities"], [])

	def test_exclusive_use_with_location_required_peer_fails(self):
		world = build_exam_world(is_partition=True, exclusive_use=True, planned_size=1)
		_req2, activity2 = world.add_exam(planned_size=1, exclusive_use=False)
		world.link_same_time(world.requirement, activity2.exam_requirement)
		world.link_same_location(world.requirement, activity2.exam_requirement)
		result = self._direct(world.monday_slot(), world.activity, activity2)
		self.assertFalse(reload_activity(world.activity).is_scheduled)
		self.assertFalse(reload_activity(activity2).is_scheduled)
		self.assertEqual(
			set(result["data"]["unscheduled_activities"]),
			{activity_label(world.activity), activity_label(activity2)},
		)

	def test_skips_already_scheduled_and_schedules_others_at_slot(self):
		world = build_exam_world(is_partition=True)
		_req2, activity2 = world.add_exam()
		world.mark_scheduled(world.activity, time_slot=world.slot_for(4))
		result = self._direct(world.monday_slot(), world.activity, activity2)
		skipped = reload_activity(world.activity)
		scheduled = reload_activity(activity2)
		self.assertEqual(skipped.time_slot, world.slot_for(4))
		self.assertTrue(scheduled.is_scheduled)
		self.assertEqual(scheduled.time_slot, world.monday_slot())
		self.assertEqual(result["data"]["skipped_activities"], [activity_label(skipped)])
		self.assertEqual(result["data"]["unscheduled_activities"], [])

	def test_fixed_slot_mismatch_from_sibling_conflicts(self):
		world = build_exam_world()
		sibling = world.add_activity()
		world.mark_scheduled(sibling, time_slot=world.slot_for(1))
		result = self._direct(world.monday_slot(), world.activity)
		activity = reload_activity(world.activity)
		self.assertFalse(activity.is_scheduled)
		self.assertEqual(result["data"]["unscheduled_activities"], [activity_label(activity)])

	def test_auto_then_direct_other_activity_at_same_slot(self):
		world = build_exam_world(is_partition=True)
		_req2, activity2 = world.add_exam()
		schedule_exam_requirements([world.activity.id], self.user.id)
		result = self._direct(world.monday_slot(), activity2)
		self.assertTrue(reload_activity(activity2).is_scheduled)
		self.assertEqual(reload_activity(activity2).time_slot, world.monday_slot())
		self.assertEqual(result["data"]["unscheduled_activities"], [])
