from datetime import datetime, timedelta

from api.models import (
	TtActivity,
	TtActivityLocation,
	TtActivityStaff,
	TtActivityWeek,
	TtStudentSetActivity,
	TtWeek,
	TtWeekPatternWeek,
)

SLOT_MINUTES = 30
SLOT_SECONDS = SLOT_MINUTES * 60
EPOCH_START = datetime(1970, 1, 1)
MINUTE_PER_SLOT = SLOT_MINUTES


def _slot_to_datetime(exam_period, time_slot):
	if time_slot is None:
		return None
	return EPOCH_START + timedelta(seconds=int(time_slot) * SLOT_SECONDS)


def windows_overlap(left, right):
	if left is None or right is None:
		return False
	if left["scheduled_day"] != right["scheduled_day"]:
		return False
	return left["start_minutes"] < right["end_minutes"] and right["start_minutes"] < left["end_minutes"]


def tt_activity_busy_window(activity):
	if not activity or activity.scheduled_day is None or not activity.scheduled_start_time:
		return None

	duration = activity.duration
	if not duration and activity.slot_required is not None:
		duration = int(activity.slot_required) * MINUTE_PER_SLOT
	if not duration:
		return None

	start_minutes = activity.scheduled_start_time.hour * 60 + activity.scheduled_start_time.minute
	return {
		"scheduled_day": int(activity.scheduled_day),
		"start_minutes": start_minutes,
		"end_minutes": start_minutes + int(duration),
	}


def exam_slot_busy_window(time_slot, duration_slots, exam_period=None):
	start_dt = _slot_to_datetime(exam_period, time_slot)
	if start_dt is None:
		return None

	start_minutes = start_dt.hour * 60 + start_dt.minute
	return {
		"scheduled_day": start_dt.weekday(),
		"start_minutes": start_minutes,
		"end_minutes": start_minutes + int(duration_slots) * MINUTE_PER_SLOT,
		"activity_date": start_dt.date(),
	}


def datetime_busy_window(start_dt, duration_minutes):
	if not start_dt or not duration_minutes:
		return None

	start_minutes = start_dt.hour * 60 + start_dt.minute
	return {
		"scheduled_day": start_dt.weekday(),
		"start_minutes": start_minutes,
		"end_minutes": start_minutes + int(duration_minutes),
		"activity_date": start_dt.date(),
	}


def tt_week_for_date(activity_date):
	if not activity_date:
		return None

	return (
		TtWeek.objects.filter(
			start_date__lte=activity_date,
			start_date__gte=activity_date - timedelta(days=6),
		)
		.order_by("start_date")
		.first()
	)


def tt_activity_week_ids(activity):
	if not activity:
		return set()

	if activity.week_pattern_id:
		return set(
			TtWeekPatternWeek.objects.filter(
				week_pattern_id=activity.week_pattern_id,
			).values_list("week_id", flat=True)
		)

	return set(
		TtActivityWeek.objects.filter(activity_id=activity.id).values_list("week_id", flat=True)
	)


def _tt_activity_conflicts_exam_window(activities, exam_window):
	if not exam_window:
		return False

	week = tt_week_for_date(exam_window.get("activity_date"))
	week_id = week.id if week else None

	for activity in activities:
		if not activity.scheduled:
			continue

		tt_window = tt_activity_busy_window(activity)
		if not windows_overlap(tt_window, exam_window):
			continue

		activity_week_ids = tt_activity_week_ids(activity)
		if week_id is not None and activity_week_ids and week_id not in activity_week_ids:
			continue

		return True

	return False


def _scheduled_tt_activities_for_ids(activity_ids):
	if not activity_ids:
		return TtActivity.objects.none()

	return TtActivity.objects.filter(id__in=activity_ids, scheduled=1)


def tt_location_conflict(es_location, time_slot, duration_slots, exam_period=None):
	if not es_location or not es_location.tt_id:
		return False

	exam_window = exam_slot_busy_window(time_slot, duration_slots, exam_period)
	if not exam_window:
		return False

	activity_ids = TtActivityLocation.objects.filter(
		location_tt_id=es_location.tt_id,
	).values_list("activity_id", flat=True)
	return _tt_activity_conflicts_exam_window(
		_scheduled_tt_activities_for_ids(activity_ids),
		exam_window,
	)


def tt_staff_conflict(staff_tt_ids, time_slot, duration_slots, exam_period=None):
	staff_tt_ids = [staff_tt_id for staff_tt_id in (staff_tt_ids or []) if staff_tt_id]
	if not staff_tt_ids:
		return False

	exam_window = exam_slot_busy_window(time_slot, duration_slots, exam_period)
	if not exam_window:
		return False

	activity_ids = TtActivityStaff.objects.filter(
		staff_tt_id__in=staff_tt_ids,
	).values_list("activity_id", flat=True)
	return _tt_activity_conflicts_exam_window(
		_scheduled_tt_activities_for_ids(activity_ids),
		exam_window,
	)


def tt_student_groups_conflict(student_group_tt_ids, time_slot, duration_slots, exam_period=None):
	student_group_tt_ids = [
		student_group_tt_id
		for student_group_tt_id in (student_group_tt_ids or [])
		if student_group_tt_id
	]
	if not student_group_tt_ids:
		return False

	exam_window = exam_slot_busy_window(time_slot, duration_slots, exam_period)
	if not exam_window:
		return False

	activity_ids = TtStudentSetActivity.objects.filter(
		student_set_tt_id__in=student_group_tt_ids,
	).values_list("activity_id", flat=True)
	return _tt_activity_conflicts_exam_window(
		_scheduled_tt_activities_for_ids(activity_ids),
		exam_window,
	)


def tt_staff_conflict_at(staff_tt_ids, start_dt, duration_minutes):
	staff_tt_ids = [staff_tt_id for staff_tt_id in (staff_tt_ids or []) if staff_tt_id]
	if not staff_tt_ids:
		return False

	exam_window = datetime_busy_window(start_dt, duration_minutes)
	if not exam_window:
		return False

	activity_ids = TtActivityStaff.objects.filter(
		staff_tt_id__in=staff_tt_ids,
	).values_list("activity_id", flat=True)
	return _tt_activity_conflicts_exam_window(
		_scheduled_tt_activities_for_ids(activity_ids),
		exam_window,
	)
