import datetime
from collections import defaultdict

from django.db.models import Q, Count
from django.db.models.functions import Substr
from rest_framework import serializers, status

from api.models import TtDepartment, TtZone, TtAvailability, TtStartPreference, TtUsagePreference, TtTag, TtSetting, \
    TtModule, TtAcademicTerm, TtActivityType, TtStaff, TtLocation, TtSuitability, TtActivityTemplate, TtWeekPattern, \
    TtWeek, TtPosModuleGroupModule, TtActivity, TtLocationResourceMap
from api.views.admin.base import AdminApiBase
from api.validator import BaseValidator
from api.utils import decrypt_aes_128_cbc, encrypt_aes_128_cbc, log_critical_error, get_exception_detail, \
    calculate_preset_pattern_time, time_to_slot, calculate_slot_by_start_time_slot, calculate_slot_by_day
from rest_framework.exceptions import ValidationError
from api.translation import __


class BookingGetResources(AdminApiBase):
    def validate_request(self,request):
        rules = {
            "activity_id": "nullable|exists:api.TtActivity,id",
            "type": "required", # staff or location
            "constraint_breaking_mode": "nullable",
        }
        # if need rename field, can put here
        attribute = {
            # "email": __("attr.email"),
        }
        # validate id first, cause need get activity_template by id, if wrong id direct return error
        validator = BaseValidator(request.data,rules,attribute)
        error = validator.validate()
        if error:
            raise ValidationError(error)

    def post(self, request):
        # Validate input
        try:
            """
            this api can ignore constraint_breaking_mode, cause constraint_braking_mode wont affect get resources
            """
            self.api_log_skip_outgoing_data=True
            self.validate_request(request)
            setting_params = {
                "minute_per_slot",
                "slot_per_day"
            }
            settings = TtSetting.get_multiple_setting(setting_params)
            minute_per_slot = int(settings["minute_per_slot"])
            slot_per_day = int(settings["slot_per_day"])

            constraint_breaking_mode = request.data.get("constraint_breaking_mode")
            activity_id = request.data.get("activity_id")
            activity = TtActivity.objects.filter(id=activity_id).prefetch_related("location_suitability__location").first()
            if not activity.is_booking:
                raise ValidationError({
                    "error": __("validation.invalid", field=__("attr.activity")),
                    "errors": {"id": __("validation.invalid", field=__("attr.activity"))}
                })

            type = request.data.get("type")
            planned_size = activity.planned_size
            resources = None
            if type == "location":
                location_ids = list(
                    activity.location_suitability.all()
                    .values_list('location__id', flat=True)
                    .distinct()
                )
                # 0 means all
                resources = (
                        TtLocation.objects.filter(Q(capacity__gte=planned_size) | Q(capacity=0))
                        .select_related("department", "zone", "availability", "start_preference", "usage_preference")
                        .prefetch_related(
                            "shared_with_department",
                            "suitability",
                            "tag__tag"
                            # tags means the relation add in models, __tag means the relation table tag_id get the tag object
                        )
                        .order_by(self.list_default_order_column)
                    )
                if location_ids:
                    resources = resources.filter(id__in=location_ids)
            elif type == "staff":
                resources = TtStaff.objects.all()

            suggested_day = activity.suggested_day
            suggested_time_slot = activity.suggested_time_slot
            # filter out available resources to user only when have suggested day and suggested time
            if suggested_day is not None and suggested_time_slot is not None and type == "location" and not constraint_breaking_mode:
                location_ids = [r.id for r in resources.all()]
                activity_week = [w.id for w in activity.week.all()]
                # if using sql to search, sql is start from 1, not start from 0, so must + 1
                sql_available_slot = int(suggested_day) * int(slot_per_day) + int(suggested_time_slot) + 1
                slot_required = activity.slot_required
                available_slot_required = "0" * slot_required

                available_location_ids = (
                    TtLocationResourceMap.objects
                    .filter(week_id__in=activity_week,location_id__in=location_ids)# filter week and location first
                    .annotate(existing_slot_pattern=Substr("pattern", sql_available_slot, slot_required)) # take out the pattern in sql_available slot until slot_required.eg. "000100" for do compare
                    .filter(existing_slot_pattern=available_slot_required)# compare the existing slot pattern with available slot(always all 0 for available slot)
                    .values("location_id") # group by location_id
                    .annotate(match_week_count=Count("week_id", distinct=True))# count got how many match result
                    .filter(match_week_count=len(activity_week))# count result must same as activity_week length
                    .values_list("location_id", flat=True) # only get location_id
                )
                resources = resources.filter(id__in=available_location_ids)
            return_data = []
            for resource in resources:
                # if suggested_day is not None and suggested_time_slot is not None and type == "location" and not constraint_breaking_mode:
                resource_data = {}
                resource_data["id"] = resource.id
                resource_data["code"] = resource.code
                resource_data["name"] = resource.name
                resource_data['desc'] = resource.desc
                resource_data['department'] = resource.department.name if resource.department else None
                resource_data['department_id'] = resource.department_id
                resource_data['zone'] = resource.zone.name if resource.zone else None
                resource_data['zone_id'] = resource.zone_id
                resource_data['maximum_period'] = resource.maximum_period
                resource_data['contract_period'] = resource.contract_period

                resource_data["availability_id"] = resource.availability.id if resource.availability else None
                resource_data["availability_name"] = resource.availability.name if resource.availability else "-"
                resource_data["availability_pattern"] = resource.availability.pattern if resource.availability else resource.availability_pattern
                resource_data["start_preference_id"] = resource.start_preference.id if resource.start_preference else None
                resource_data["start_preference_name"] = resource.start_preference.name if resource.start_preference else "-"
                resource_data["start_preference_pattern"] = resource.start_preference.pattern if resource.start_preference else resource.start_preference_pattern
                resource_data["usage_preference_id"] = resource.usage_preference.id if resource.usage_preference else None
                resource_data["usage_preference_name"] = resource.usage_preference.name if resource.usage_preference else "-"
                resource_data["usage_preference_pattern"] = resource.usage_preference.pattern if resource.usage_preference else resource.usage_preference_pattern

                resource_data['suitability'] = [
                    {"id": suit.id, "name": suit.name}
                    for suit in resource.suitability.all()
                ]
                resource_data['shared_with_department'] = [
                    {"id": dept.id, "name": dept.name}
                    for dept in resource.shared_with_department.all()
                ]
                resource_data['avoid_concurrency'] = [
                    {"id": avoid.id, "name": avoid.name}
                    for avoid in resource.avoid_concurrency.all()
                ]
                resource_data['shared_with_all_department'] = resource.shared_with_all_department
                resource_data["tag"] = [
                    {"id": tag.tag.id, "name": tag.tag.name}
                    for tag in resource.tag.all()
                ]
                for i in range(1, 11):
                    extra_data_field = f"extra_data_{i}"
                    resource_data[extra_data_field] = getattr(resource, extra_data_field, None)
                if type == "location":
                    resource_data['capacity'] = resource.capacity
                    resource_data['area'] = resource.area
                return_data.append(resource_data)
            return_data.sort(key=lambda x: x["name"].lower())

            response = {
                "resources": return_data,
            }

            return self.api_response(data=response)
        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)