from rest_framework import status
from rest_framework.exceptions import ValidationError
from datetime import datetime
from django.db.models import Exists, OuterRef, CharField
from django.db.models.functions import Cast
from api.models import EsExamPeriodUnavailability

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


def _parse_filter_time(val):
    for fmt in ("%H:%M:%S", "%H:%M"):
        try:
            return datetime.strptime(val, fmt).time()
        except (TypeError, ValueError):
            continue
    return None


class ExamPeriodList(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):
        exam_periods = (
            EsExamPeriod.objects
            .prefetch_related("esexamperiodunavailability_set")
            .all()
        )

        exam_periods = self.exam_period_listing_filter(request, exam_periods)
        pagination = self.exam_period_listing_pagination(request, exam_periods)

        return_data=[]

        for val in pagination["paginated_data"]:
            data={}
            data["id"] = val.id
            data["code"] = val.code
            data["name"] = val.name
            data["start_date"] = val.start_date
            data["end_date"] = val.end_date
            data["start_time"] = val.start_time
            data["end_time"] = val.end_time
            data["status"] = val.status
            data["status_text"] = __("attr.exam_period_status." + str(val.status))
            unavailable_dates = [rp.unavailable_date for rp in val.esexamperiodunavailability_set.order_by("unavailable_date").all()]
            data['unavailable_dates'] = unavailable_dates

            for i in range(1,11):
                extra_data_field = f"extra_data_{i}"
                data[extra_data_field] = getattr(val,extra_data_field,None)

            return_data.append(data)

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

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

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

            match key:
                case "code" | "name":
                    model = model.filter(**{f"{key}__icontains": val})
                case "unavailable_dates":
                    subquery = EsExamPeriodUnavailability.objects.filter(
                        exam_period_id=OuterRef('id')
                    ).annotate(
                        _txt=Cast('unavailable_date', output_field=CharField())
                    ).filter(_txt__icontains=val)
                    model = model.filter(Exists(subquery))
                case "start_date_from":
                    try:
                        model = model.filter(start_date__gte=datetime.strptime(val, "%Y-%m-%d").date())
                    except (TypeError, ValueError):
                        pass
                case "start_date_to":
                    try:
                        model = model.filter(start_date__lte=datetime.strptime(val, "%Y-%m-%d").date())
                    except (TypeError, ValueError):
                        pass
                case "end_date_from":
                    try:
                        model = model.filter(end_date__gte=datetime.strptime(val, "%Y-%m-%d").date())
                    except (TypeError, ValueError):
                        pass
                case "end_date_to":
                    try:
                        model = model.filter(end_date__lte=datetime.strptime(val, "%Y-%m-%d").date())
                    except (TypeError, ValueError):
                        pass
                case "start_time_from" | "start_time_to" | "end_time_from" | "end_time_to":
                    parsed_time = _parse_filter_time(val)
                    if parsed_time is None:
                        pass
                    elif key == "start_time_from":
                        model = model.filter(start_time__gte=parsed_time)
                    elif key == "start_time_to":
                        model = model.filter(start_time__lte=parsed_time)
                    elif key == "end_time_from":
                        model = model.filter(end_time__gte=parsed_time)
                    else:
                        model = model.filter(end_time__lte=parsed_time)
                case "status":
                    if isinstance(val, (list, tuple, set)):
                        model = model.filter(status__in=list(val))
                    else:
                        model = model.filter(status=val)
                case _:
                    continue

        return model.distinct()

    def exam_period_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:
            if sort_by == "unavailable_dates":
                from django.db.models import Min
                model = model.annotate(_first_unavail=Min("esexamperiodunavailability__unavailable_date"))
                sort_col = "_first_unavail"
            else:
                sort_col = 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,
        }
