from itertools import product, combinations

from rest_framework import serializers, status

from api.helper.resource_map_helper import helper_recalculate_and_get_redis_resource_map
from api.models import TtSetting, TtActivity, TtActivityStaff, TtActivityLocation
from api.models.audit_trail import AuditTrail
from api.models.audit_trail_details import AuditTrailDetails
from api.utils import get_ip, bulk_sync_to_redis

from backend.kafka import send_request
from backend.redis_client import redis_client
from api.services.integration.tracking import track_activity_ids

def unschedule(request,kafka_topic,audit_type,slot_per_week=None):
    if not slot_per_week:
        setting_params = {
            "slot_per_week",
        }
        settings = TtSetting.get_multiple_setting(setting_params)
        slot_per_week = int(settings["slot_per_week"])
    activity_ids = request.data["activity_ids"]
    track_activity_ids(activity_ids)
    action = "unschedule"
    old_new_data = {
        "old_data": {},
        "new_data": {},
    }

    activities = (
        TtActivity.objects.filter(id__in=activity_ids, scheduled=1)
        .select_related("week_pattern")
        .prefetch_related(
            "week",
            "week_pattern__week",
            "staff",
            "location",
            "student_set",
        )
    )
    staff_ids = set()
    location_ids = set()
    student_set_ids = set()
    name = ",".join(activities.values_list("name", flat=True))

    # record down the affected map for the staff, sample result is staff_with_affected_week[1] = [1,2,3,4]
    staff_with_affected_week = {}
    location_with_affected_week = {}
    student_set_with_affected_week = {}
    kafka_unschedule = []

    for activity in activities:
        old_new_data["old_data"].setdefault(activity.id, {})
        old_new_data["new_data"].setdefault(activity.id, {})
        if activity.week_pattern_id:
            week_pattern = list(activity.week_pattern.week.values_list("id", flat=True))
        else:
            week_pattern = list(activity.week.values_list("id", flat=True))

        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(week_pattern)
            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(week_pattern)
            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(week_pattern)
            student_set_ids.add(student_set.id)

        old_new_data["old_data"][activity.id]["scheduled"] = activity.scheduled
        old_new_data["old_data"][activity.id]["scheduled_start_slot"] = activity.scheduled_start_slot
        old_new_data["old_data"][activity.id]["scheduled_day"] = activity.scheduled_day
        old_new_data["old_data"][activity.id]["scheduled_start_time"] = (
            activity.scheduled_start_time.isoformat() if activity.scheduled_start_time else None
        )
        old_new_data["old_data"][activity.id]["staff_ids"] = sorted([s.id for s in activity.staff.all()])
        old_new_data["old_data"][activity.id]["location_ids"] = sorted([l.id for l in activity.location.all()])
        old_new_data["old_data"][activity.id]["student_set_ids"] = sorted([ss.id for ss in activity.student_set.all()])

        # update activity scheduled/scheduled_start_time/scheduled_day, but not save here, will used bulk update to perform all in 1 query
        activity.scheduled_start_time = None
        activity.scheduled_day = None
        activity.scheduled_start_slot = None
        activity.scheduled = 0

        old_new_data["new_data"][activity.id]["scheduled"] = 0
        old_new_data["new_data"][activity.id]["scheduled_start_slot"] = None
        old_new_data["new_data"][activity.id]["scheduled_day"] = None
        old_new_data["new_data"][activity.id]["scheduled_start_time"] = None
        old_new_data["new_data"][activity.id]["staff_ids"] = []
        old_new_data["new_data"][activity.id]["location_ids"] = []
        old_new_data["new_data"][activity.id]["student_set_ids"] = sorted([ss.id for ss in activity.student_set.all()])

        # kafka
        kafka_unschedule.append(
            {
                "activity_id": activity.id,
                "scheduled": 0,
                "staff": [],
                "location": [],
                "scheduled_start_time": None,
                "scheduled_day": None,
                "scheduled_start_slot": None,
            }
        )

    # use bulk update to update all record in 1 query
    TtActivity.objects.bulk_update(activities,["scheduled", "scheduled_start_time", "scheduled_day", "scheduled_start_slot"])

    # remove staff from activity_staff relation
    TtActivityStaff.objects.filter(activity_id__in=activity_ids).delete()
    # remove location from activity_location relation
    TtActivityLocation.objects.filter(activity_id__in=activity_ids).delete()

    redis_data = {
        "update": {}
    }
    redis_activity_table = "activity"
    for activity_id in activity_ids:
        redis_data["update"].setdefault(redis_activity_table, []).append({
            "id": activity_id,
            "scheduled": 0,
            "scheduled_start_slot": None,
        })

    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 redis activity
    bulk_sync_to_redis(redis_client, redis_data)

    # kafka push
    method = "unschedule"
    if kafka_topic and kafka_unschedule:
        kafka_request_data = {
            "session_id": request.user.name,
            "activity": kafka_unschedule
        }
        send_request(kafka_topic, kafka_request_data, None, method)

    # insert audit trail and details
    # can insert the parent first, then only insert details
    audit_trail = AuditTrail.objects.create(
        user_id=request.user.id,
        type=audit_type,
        ip_address=get_ip(request)
    )
    remark_param = {
        "name": 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,
        old_data=old_new_data['old_data'],
        new_data=old_new_data['new_data'],
    )