import re

from django.db.models import Count
from rest_framework import status

from api.helper.activity_sequencing_helper import helper_build_update_redis_sequencing_data
from api.helper.resource_map_helper import helper_recalculate_and_get_redis_resource_map
from api.models import TtSetting, AuditTrail, AuditTrailDetails, TtActivity, TtActivityWeek, TtAcademicTermWeek, \
    TtActivitySequencing
from api.views.admin.base import AdminApiBase
from api.validator import BaseValidator
from api.utils import log_critical_error, get_exception_detail, \
    bulk_sync_to_redis, get_ip, \
    remove_variant_activity_name_range, format_variant_activity_name_week_pattern_to_ranges
from rest_framework.exceptions import ValidationError
from api.translation import __
from api.services.integration.tracking import track_activity_ids
from backend.kafka import send_request
from backend.redis_client import redis_client


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

        # custom error after basic validation
        variant_parent_name = []
        activities = TtActivity.objects.filter(id__in=request.data.get("id"))
        for activity in activities:
            variant_parent_name = [
                activity.name for activity in activities
                if activity.is_variant == 1 and not activity.variant_parent_id
            ]

        if variant_parent_name:
            formatted_name = ", ".join(variant_parent_name)
            raise ValidationError({
                "error": __('validation.not_allow_delete_parent_variant_activity', activities_name=formatted_name),
                "errors": {"id": __("validation.not_allow_delete_parent_variant_activity", activities_name=formatted_name)}
            })

    def post(self, request):
        # Validate input
        try:
            setting_params = {
                "slot_per_week",
                "minute_per_slot",
            }
            settings = TtSetting.get_multiple_setting(setting_params)
            self.validate_request(request)

            action = "activity_delete"
            ids = request.data.get("id")

            slot_per_week = int(settings["slot_per_week"])
            activities = (
                TtActivity.objects.filter(id__in=ids)
                .select_related("week_pattern", "activity_template")
                .prefetch_related(
                    "week",
                    "week_pattern__week",
                    "staff",
                    "location",
                    "student_set",
                    "sequencing"
                )
            )
            activity_name = ",".join([a.name for a in activities.all()])

            staff_with_affected_week = {}
            location_with_affected_week = {}
            student_set_with_affected_week = {}

            staff_ids = set()
            location_ids = set()
            student_set_ids = set()
            parent_ids = set()
            reschedule_resource_map = False
            activity_week_objs = []
            parent_week_ids = {}
            sequencing_activity_ids = set()
            for activity in activities:
                sequencing_activity_ids.update({
                    activity_sequencing.id for activity_sequencing in activity.sequencing.all()
                })
                if activity.week_pattern:
                    current_week_ids = {aw.id for aw in activity.week_pattern.week.all()}
                else:
                    current_week_ids = {aw.id for aw in activity.week.all()}
                if activity.is_variant == 1 and activity.variant_parent_id is not None:
                    # this parent_week_ids is used for update parent resource_map week pattern
                    if activity.variant_parent_id not in parent_week_ids:
                        parent_week_ids[activity.variant_parent_id] = set()
                    parent_week_ids[activity.variant_parent_id].update(current_week_ids)

                    parent_ids.add(activity.variant_parent_id)
                    # if is variant, need merge week_pattern back to parent
                    # query for merge week pattern back to parent
                    activity_week_objs.extend([
                        TtActivityWeek(week_id=week_id, activity_id=activity.variant_parent_id)
                        for week_id in current_week_ids
                    ])

                # if the activity is scheduled, only need remove related resource_map
                if activity.scheduled:
                    reschedule_resource_map = True
                    # this part only get for deleted activity staff/location/student_set to decide which want to recalculate, will have another to check in parent site
                    for staff in activity.staff.all():
                        if staff.id not in staff_with_affected_week:
                            staff_with_affected_week[staff.id] = set()
                        staff_with_affected_week[staff.id].update(current_week_ids)
                        staff_ids.add(staff.id)

                    for location in activity.location.all():
                        if location.id not in location_with_affected_week:
                            location_with_affected_week[location.id] = set()
                        location_with_affected_week[location.id].update(current_week_ids)
                        location_ids.add(location.id)

                    for student_set in activity.student_set.all():
                        if student_set.id not in student_set_with_affected_week:
                            student_set_with_affected_week[student_set.id] = set()
                        student_set_with_affected_week[student_set.id].update(current_week_ids)
                        student_set_ids.add(student_set.id)

            redis_data = {
                "delete": {},
                "update": {}
            }
            redis_staff_table = "staff"
            redis_location_table = "location"
            redis_student_set_table = "student_set"
            redis_activity_table = "activity"

            # need delete activity first only can recalculate the resource_map
            TtActivity.objects.filter(id__in=ids).delete()

            if activity_week_objs:
                TtActivityWeek.objects.bulk_create(activity_week_objs,ignore_conflicts=True)

            # get latest parent activity for update redis data
            if parent_ids:
                parent_activities = (
                    TtActivity.objects.filter(id__in=parent_ids)
                    .select_related("week_pattern", "activity_template")
                    .prefetch_related(
                        "week",
                        "week_pattern__week",
                        "staff",
                        "location",
                        "student_set"
                    )
                    .annotate(variant_child_count=Count('variant_children'))
                )
                parent_activities_to_update = []
                for parent_activity in parent_activities:
                    reschedule_resource_map = True
                    # this part will handle parent side staff/location/student recalculate resource map
                    # remain using week_ids, cause is only affected to same week
                    for staff in parent_activity.staff.all():
                        if staff.id not in staff_with_affected_week:
                            staff_with_affected_week[staff.id] = set()
                        staff_with_affected_week[staff.id].update(parent_week_ids[parent_activity.id])
                        staff_ids.add(staff.id)

                    for location in parent_activity.location.all():
                        if location.id not in location_with_affected_week:
                            location_with_affected_week[location.id] = set()
                        location_with_affected_week[location.id].update(parent_week_ids[parent_activity.id])
                        location_ids.add(location.id)

                    for student_set in parent_activity.student_set.all():
                        if student_set.id not in student_set_with_affected_week:
                            student_set_with_affected_week[student_set.id] = set()
                        student_set_with_affected_week[student_set.id].update(parent_week_ids[parent_activity.id])
                        student_set_ids.add(student_set.id)

                    if parent_activity.week_pattern:
                        parent_week_pattern = list(parent_activity.week_pattern.week.all().values("id", "week"))
                    else:
                        parent_week_pattern = list(parent_activity.week.all().values("id", "week"))

                    academic_term_week_pattern = list(
                        TtAcademicTermWeek.objects
                        .filter(academic_term_id=parent_activity.academic_term_id)
                        .select_related("week")
                        .order_by("week__week")
                        .values("week_id", "week__week")
                    )
        
                    academic_term_week_pattern = [
                        {
                            "id": w["week_id"],
                            "week": w["week__week"],
                        }
                        for w in academic_term_week_pattern
                    ]
                    
                    parent_activity.name = remove_variant_activity_name_range(parent_activity.name)
                    # if no more child, update redis and table is_variant = 0
                    if parent_activity.variant_child_count == 0:
                        parent_activity.is_variant = 0
                        parent_activity.code = re.sub(r"\s\d{3}$", "", parent_activity.code)
                    else:
                        # just update for name if still got variant child
                        parent_activity.name = parent_activity.name + " " + format_variant_activity_name_week_pattern_to_ranges(parent_week_pattern, academic_term_week_pattern)

                    parent_activities_to_update.append(parent_activity)
                    redis_data["update"].setdefault(redis_activity_table, []).append({
                        "id": parent_activity.id,
                        "is_variant": parent_activity.is_variant,
                        "code": parent_activity.code,
                        "name": parent_activity.name,
                    })
                if parent_activities_to_update:
                    track_activity_ids(parent.id for parent in parent_activities_to_update)
                    TtActivity.objects.bulk_update(parent_activities_to_update, ['is_variant','code','name'])

            # have this value means need update for resource map
            if reschedule_resource_map:
                staff_ids = list(staff_ids)
                location_ids = list(location_ids)
                student_set_ids = list(student_set_ids)

                # Filter out entries with empty sets to prevent passing {id: []} to the function
                staff_with_affected_week = {k: v for k, v in staff_with_affected_week.items() if v}
                location_with_affected_week = {k: v for k, v in location_with_affected_week.items() if v}
                student_set_with_affected_week = {k: v for k, v in student_set_with_affected_week.items() if v}
                # since got update student set, this helper already cater if got update student set, no need pass in student details also will update student resource map
                redis_data = helper_recalculate_and_get_redis_resource_map(
                    redis_data=redis_data,
                    staff_with_affected_week=staff_with_affected_week,
                    location_with_affected_week=location_with_affected_week,
                    student_set_with_affected_week=student_set_with_affected_week,
                    slot_per_week=slot_per_week,
                )
            # update related activity sequencing in redis
            if sequencing_activity_ids:
                sequencing_activity_ids = list(sequencing_activity_ids)
                sequencing_related_activities = TtActivity.objects.filter(id__in=sequencing_activity_ids).prefetch_related("sequencing_from")
                for related_sequencing_activity in sequencing_related_activities:
                    redis_data["update"].setdefault(redis_activity_table, []).append(
                        helper_build_update_redis_sequencing_data(related_sequencing_activity)
                    )

            # delete activity from redis
            for id in ids:
                redis_data["delete"].setdefault(redis_activity_table, []).append(id)

            bulk_sync_to_redis(redis_client,redis_data)

            #kafka
            method = "activity_delete"
            kafka_topic = self.kafka_config['MICROSERVICES_TT_TOPIC']
            if kafka_topic:
                kafka_request_data = {
                    "session_id": request.user.name,
                    "activity_ids": ids
                }
                send_request(kafka_topic, kafka_request_data, None, method)

            audit_trail = AuditTrail.objects.create(
                user_id=request.user.id,
                type=self.audit_type,
                ip_address=get_ip(request)
            )
            remark_param = {
                "name": activity_name
            }

            # call to a function for insert details, so in future if want change to used worker, can modify in function only
            AuditTrailDetails.custom_insert(
                audit_trail=audit_trail,
                action=action,
                remark_param=remark_param,
            )
            response = {

            }

            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)
