from rest_framework import status
from rest_framework.exceptions import ValidationError
from datetime import datetime

from django.db.models import Count, Q

from api.models import EsExamRequirement, EsExamRequirementStudent
from api.translation import __
from api.utils import log_critical_error, get_exception_detail
from api.views.admin.base import AdminApiBase


class ExamRequirementList(AdminApiBase):
	list_default_order_column = "id"

	def post(self, request):
		try:
			self.api_log_skip_outgoing_data = True
			data = self.get_data(request)
			return self.api_response(data=data)
		except ValidationError as e:
			first_message = e.detail["error"]
			errors = e.detail["errors"]
			return self.api_response(error=first_message, errors=errors, code=status.HTTP_400_BAD_REQUEST)
		except Exception as e:
			e_details = get_exception_detail(e)
			log_critical_error(user_id=None, descr=e_details["descr"], url=e_details["url"], trace=e_details["trace"])
			return self.api_response(error=__("message.internal_server_error"), code=status.HTTP_500_INTERNAL_SERVER_ERROR)

	def get_data(self, request):
		params = request.data
		exam_period_id = params.get("exam_period_id")

		query = (
			EsExamRequirement.objects
			.select_related("exam_period")
			.prefetch_related("invigilator_suitability", "location_suitability")
			.annotate(
				real_size=Count(
					"esexamrequirementstudent",
					filter=Q(esexamrequirementstudent__student__isnull=False),
					distinct=True,
				)
			)
		)

		if exam_period_id is not None:
			query = query.filter(exam_period_id=exam_period_id)

		objs = self.exam_requirement_listing_filter(request, query)
		pagination = self.exam_requirement_listing_pagination(request, objs)

		return_data = []

		for val in pagination["paginated_data"]:
			data = {}
			data["id"] = val.id
			data["exam_period_id"] = val.exam_period_id
			data["exam_period"] = val.exam_period.name if val.exam_period else None
			data["code"] = val.code
			data["name"] = val.name
			data["description"] = val.description
			data["planned_size"] = val.planned_size
			data["writing_time"] = val.writing_time
			data["fixed_start_date"] = val.fixed_start_date
			data["reading_time"] = val.reading_time
			data["fixed_start_time"] = val.fixed_start_time
			data["location_required"] = val.location_required
			data["exclusive_use"] = val.exclusive_use
			data["minimum_split_size"] = val.minimum_split_size
			data["earliest_start"] = val.earliest_start
			data["latest_end"] = val.latest_end
			data["invigilator_suitability"] = [
				{"id": suitability.id, "name": suitability.name}
				for suitability in val.invigilator_suitability.all()
			]
			data["location_suitability"] = [
				{"id": suitability.id, "name": suitability.name}
				for suitability in val.location_suitability.all()
			]
			data["real_size"] = val.real_size
			return_data.append(data)

		return {
			"data": return_data,
			"total": pagination["total"],
			"page": pagination["page"],
			"per_page": pagination["per_page"],
		}

	def exam_requirement_listing_filter(self, request, model):
		filters = request.data.get("filter")
		if not isinstance(filters, dict) or not filters:
			return model

		for key, val in filters.items():
			if val in (None, "", [], {}):
				continue

			match key:
				case "code" | "name" | "desc" | "description":
					field_name = "description" if key in ("desc", "description") else key
					model = model.filter(**{f"{field_name}__icontains": val})
				case "planned_size_min":
					try:
						model = model.filter(planned_size__gte=int(val))
					except (TypeError, ValueError):
						continue
				case "planned_size_max":
					try:
						model = model.filter(planned_size__lte=int(val))
					except (TypeError, ValueError):
						continue
				case "real_size_min":
					try:
						model = model.filter(real_size__gte=int(val))
					except (TypeError, ValueError):
						continue
				case "real_size_max":
					try:
						model = model.filter(real_size__lte=int(val))
					except (TypeError, ValueError):
						continue
				case "minimum_split_size_min":
					try:
						model = model.filter(minimum_split_size__gte=int(val))
					except (TypeError, ValueError):
						continue
				case "minimum_split_size_max":
					try:
						model = model.filter(minimum_split_size__lte=int(val))
					except (TypeError, ValueError):
						continue
				case "writing_time_from":
					try:
						model = model.filter(writing_time__gte=datetime.strptime(val, "%H:%M:%S").time())
					except (TypeError, ValueError):
						try:
							model = model.filter(writing_time__gte=datetime.strptime(val, "%H:%M").time())
						except (TypeError, ValueError):
							continue
				case "writing_time_to":
					try:
						model = model.filter(writing_time__lte=datetime.strptime(val, "%H:%M:%S").time())
					except (TypeError, ValueError):
						try:
							model = model.filter(writing_time__lte=datetime.strptime(val, "%H:%M").time())
						except (TypeError, ValueError):
							continue
				case "reading_time_from":
					try:
						model = model.filter(reading_time__gte=datetime.strptime(val, "%H:%M:%S").time())
					except (TypeError, ValueError):
						try:
							model = model.filter(reading_time__gte=datetime.strptime(val, "%H:%M").time())
						except (TypeError, ValueError):
							continue
				case "reading_time_to":
					try:
						model = model.filter(reading_time__lte=datetime.strptime(val, "%H:%M:%S").time())
					except (TypeError, ValueError):
						try:
							model = model.filter(reading_time__lte=datetime.strptime(val, "%H:%M").time())
						except (TypeError, ValueError):
							continue
				case "fixed_start_time_from":
					try:
						model = model.filter(fixed_start_time__gte=datetime.strptime(val, "%H:%M:%S").time())
					except (TypeError, ValueError):
						try:
							model = model.filter(fixed_start_time__gte=datetime.strptime(val, "%H:%M").time())
						except (TypeError, ValueError):
							continue
				case "fixed_start_time_to":
					try:
						model = model.filter(fixed_start_time__lte=datetime.strptime(val, "%H:%M:%S").time())
					except (TypeError, ValueError):
						try:
							model = model.filter(fixed_start_time__lte=datetime.strptime(val, "%H:%M").time())
						except (TypeError, ValueError):
							continue
				case "fixed_start_date_from":
					try:
						model = model.filter(fixed_start_date__gte=datetime.strptime(val, "%Y-%m-%d").date())
					except (TypeError, ValueError):
						continue
				case "fixed_start_date_to":
					try:
						model = model.filter(fixed_start_date__lte=datetime.strptime(val, "%Y-%m-%d").date())
					except (TypeError, ValueError):
						continue
				case "earliest_start_from":
					try:
						model = model.filter(earliest_start__gte=datetime.fromisoformat(val))
					except (TypeError, ValueError):
						continue
				case "earliest_start_to":
					try:
						model = model.filter(earliest_start__lte=datetime.fromisoformat(val))
					except (TypeError, ValueError):
						continue
				case "latest_end_from":
					try:
						model = model.filter(latest_end__gte=datetime.fromisoformat(val))
					except (TypeError, ValueError):
						continue
				case "latest_end_to":
					try:
						model = model.filter(latest_end__lte=datetime.fromisoformat(val))
					except (TypeError, ValueError):
						continue
				case "location_required" | "exclusive_use":
					if isinstance(val, str):
						normalized = val.strip().lower()
						if normalized in ("true", "1", "yes", "y"):
							model = model.filter(**{key: True})
						elif normalized in ("false", "0", "no", "n"):
							model = model.filter(**{key: False})
					else:
						model = model.filter(**{key: bool(val)})
				case "exam_period_id":
					if isinstance(val, (list, tuple, set)):
						model = model.filter(exam_period_id__in=list(val))
					else:
						model = model.filter(exam_period_id=val)
				case "invigilator_suitability":
					if isinstance(val, (list, tuple)):
						model = model.filter(invigilator_suitability__in=val)
					else:
						model = model.filter(invigilator_suitability=val)
				case "location_suitability":
					if isinstance(val, (list, tuple)):
						model = model.filter(location_suitability__in=val)
					else:
						model = model.filter(location_suitability=val)
				case _:
					continue
		return model

	def exam_requirement_listing_pagination(self, request, model):
		page, per_page = self.parse_pagination(request)
		sort_by = request.data.get("sort_by")
		order_by = request.data.get("order_by")

		total = model.count()

		if sort_by:
			sort_col = "description" if sort_by == "desc" else sort_by

			if str(order_by).lower() == "desc":
				ordering_string = f"-{sort_col}"
			else:
				ordering_string = sort_col
			model = model.order_by(ordering_string)
		else:
			model = model.order_by(self.list_default_order_column)

		if per_page != -1:
			start_index = (page - 1) * per_page
			end_index = start_index + per_page
			paginated_data = model[start_index:end_index]
		else:
			paginated_data = model

		return {
			"total": total,
			"paginated_data": paginated_data,
			"page": page,
			"per_page": per_page,
		}
