from rest_framework import status
from rest_framework.exceptions import ValidationError

from django.db.models import Q, Sum
from django.db.models.functions import Coalesce

from datetime import datetime, timedelta

from api.models import UserPermission, RolePermission, TtStudent
from api.models.tt_activity import TtActivity
from api.models.tt_week import TtWeek
from api.models.tt_staff import TtStaff
from api.views.admin.base import AdminApiBase
from api.validator import BaseValidator
from api.utils import log_critical_error, get_exception_detail, get_setting_academic_term_ids
from api.translation import __


class ActivityByDate(AdminApiBase):
    def validate_request(self, request):
        rules = {
            "date": "required|date",
            "activity_name": "nullable",
            "department": "nullable|array",
            "pos": "nullable|array",
            "module": "nullable|array",
            "staff": "nullable|array",
            "location": "nullable|array",
            "student_set": "nullable|array",
            "view_self": "nullable"
        }

        attribute = {}

        validator = BaseValidator(request.data, rules, attribute)
        error = validator.validate()
        if error:
            raise ValidationError(error)

    def get_permission_summary(self, user):
        summary = {
            "department": {
                "show": False,
                "grant_to_all_department": False,
                "department_ids": set(),
            },
            "pos": {
                "show": False,
                "grant_to_all_department": False,
                "department_ids": set(),
            },
            "module": {
                "show": False,
                "grant_to_all_department": False,
                "department_ids": set(),
            },
            "staff": {
                "show": False,
                "grant_to_all_department": False,
                "department_ids": set(),
            },
            "location": {
                "show": False,
                "grant_to_all_department": False,
                "department_ids": set(),
            },
            "student_set": {
                "show": False,
                "grant_to_all_department": False,
                "department_ids": set(),
            },
        }

        if user.is_superuser:
            for key in summary:
                summary[key]["show"] = True
                summary[key]["grant_to_all_department"] = True
            return summary

        user_permissions = (
            UserPermission.objects
            .filter(user=user)
            .prefetch_related("userpermissiondepartment_set")
        )

        role_ids = list(user.role.values_list("id", flat=True))
        role_permissions = (
            RolePermission.objects
            .filter(role_id__in=role_ids)
            .prefetch_related("rolepermissiondepartment_set")
        )

        for row in user_permissions:
            if row.permission not in summary:
                continue

            item = summary[row.permission]
            item["show"] = True

            if row.grant_to_all_department:
                item["grant_to_all_department"] = True
            else:
                item["department_ids"].update(
                    row.userpermissiondepartment_set.values_list("department_id", flat=True)
                )

        for row in role_permissions:
            if row.permission not in summary:
                continue

            item = summary[row.permission]
            item["show"] = True

            if row.grant_to_all_department:
                item["grant_to_all_department"] = True
            else:
                item["department_ids"].update(
                    row.rolepermissiondepartment_set.values_list("department_id", flat=True)
                )

        return summary
    
    def get_shared_activity_q(self, relation_name, permission_item):
        if not permission_item["show"]:
            return Q(pk__in=[])

        if permission_item["grant_to_all_department"]:
            return Q(**{f"{relation_name}__isnull": False})

        department_ids = permission_item["department_ids"]

        return (
            Q(**{f"{relation_name}__department_id__in": department_ids}) |
            Q(**{f"{relation_name}__shared_with_department__id__in": department_ids}) |
            Q(**{f"{relation_name}__shared_with_all_department": True})
        )

    def get_matching_jta_parent_ids(self, filter_q):
        return TtActivity.objects.filter(
            is_jta=1,
            jta_parent_id__isnull=False,
        ).filter(filter_q).values_list("jta_parent_id", flat=True)

    def get_jta_variant_ids(self, variant_parent_ids):
        return TtActivity.objects.filter(
            is_variant=1,
            variant_parent_id__in=variant_parent_ids,
        ).values_list("id", flat=True)

    def post(self, request):
        try:
            self.validate_request(request)

            response = {
                "data": self.get_activity_by_date(request.user, request.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
            )

    def is_only_self_user(self, user):
        return getattr(user, "is_only_self", False)

    def get_self_staff(self, user):
        email = (user.email or "").strip()

        if not email:
            return None

        return (
            TtStaff.objects
            .filter(
                email__iexact=email,
                status=TtStaff.STATUS_TO_CODE["active"],
            )
            .first()
        )

    def get_self_student(self, user):
        email = (user.email or "").strip()

        if not email:
            return None

        return (
            TtStudent.objects
            .filter(
                email__iexact=email,
                status=TtStudent.STATUS_TO_CODE["active"],
            )
            .first()
        )

    def get_activity_by_date(self, user, request_data):
        target_date = request_data.get("date")

        department_ids = request_data.get("department", [])
        pos_ids = request_data.get("pos", [])
        module_ids = request_data.get("module", [])
        staff_ids = request_data.get("staff", [])
        location_ids = request_data.get("location", [])
        student_set_ids = request_data.get("student_set", [])
        activity_name = request_data.get("activity_name")

        permission_summary = self.get_permission_summary(user)
        is_only_self = self.is_only_self_user(user)

        # if FE got pass in this flag, show self only
        if request_data.get("view_self"):
            is_only_self = True

        target_date_obj = datetime.strptime(target_date, "%Y-%m-%d").date()

        week = (
            TtWeek.objects
            .filter(
                start_date__lte=target_date_obj,
                start_date__gte=target_date_obj - timedelta(days=6),
            )
            .order_by("-start_date")
            .first()
        )

        if not week:
            return []

        self_staff = None
        self_student = None
        if is_only_self:
            self_staff = self.get_self_staff(user)

            if not self_staff:
                self_student = self.get_self_student(user)

                if not self_student:
                    return []
        else:
            if not any(item["show"] for item in permission_summary.values()):
                return []

        activities = (
            TtActivity.objects
            .filter(
                scheduled=1,
                status=TtActivity.STATUS_TO_CODE["active"],
            )
            .filter(
                Q(week_pattern_id__isnull=True, week__id=week.id) |
                Q(week_pattern__week__id=week.id)
            )
        )

        # all user can filter by this activity name, just a extra filter
        if activity_name:
            activities = activities.filter(name__icontains=activity_name)

        setting_academic_term_ids= get_setting_academic_term_ids()
        if setting_academic_term_ids:
            activities = activities.filter(
                Q(academic_term_id__in=setting_academic_term_ids) | 
                Q(academic_term_id__isnull=True)
            )

        if is_only_self:
            if self_staff:
                activities = activities.filter(staff__id=self_staff.id).distinct()
            elif self_student:
                activities = activities.filter(student_set__student__id=self_student.id).distinct()
            else:
                return []
        else:
            has_any_all_department = any(
                item["show"] and item["grant_to_all_department"]
                for item in permission_summary.values()
            )

            if not has_any_all_department:
                allowed_department_ids = set()
                for item in permission_summary.values():
                    if item["show"]:
                        allowed_department_ids.update(item["department_ids"])

                activity_permission_q = (
                    Q(department_id__in=allowed_department_ids) |
                    Q(department_id__isnull=True)
                )

                if permission_summary["staff"]["show"]:
                    activity_permission_q |= self.get_shared_activity_q(
                        "staff",
                        permission_summary["staff"],
                    )

                if permission_summary["location"]["show"]:
                    activity_permission_q |= self.get_shared_activity_q(
                        "location",
                        permission_summary["location"],
                    )

                activities = activities.filter(activity_permission_q).distinct()

            if department_ids:
                if not permission_summary["department"]["show"]:
                    return []

                activities = activities.filter(
                    Q(department_id__in=department_ids) |
                    Q(department_id__isnull=True)
                )

            if pos_ids:
                if not permission_summary["pos"]["show"]:
                    return []
                pos_filter_pattern = Q(module__ttposmodulegroupmodule__pos_module_group__pos_id__in=pos_ids)
                pos_jta_parent_ids = self.get_matching_jta_parent_ids(pos_filter_pattern)
                pos_jta_variant_ids = self.get_jta_variant_ids(pos_jta_parent_ids) if pos_jta_parent_ids else []

                activities = activities.filter(
                    pos_filter_pattern |
                    Q(id__in=pos_jta_parent_ids) |
                    Q(id__in=pos_jta_variant_ids)
                ).distinct()

            if module_ids:
                if not permission_summary["module"]["show"]:
                    return []
                module_filter_pattern = Q(module_id__in=module_ids)
                module_jta_parent_ids = self.get_matching_jta_parent_ids(module_filter_pattern)
                module_jta_variant_ids = self.get_jta_variant_ids(module_jta_parent_ids) if module_jta_parent_ids else []

                activities = activities.filter(
                    module_filter_pattern |
                    Q(id__in=module_jta_parent_ids) |
                    Q(id__in=module_jta_variant_ids)
                ).distinct()

            if staff_ids:
                if not permission_summary["staff"]["show"]:
                    return []

                activities = activities.filter(
                    Q(staff__id__in=staff_ids) &
                    self.get_shared_activity_q("staff", permission_summary["staff"])
                )

            if location_ids:
                if not permission_summary["location"]["show"]:
                    return []

                activities = activities.filter(
                    Q(location__id__in=location_ids) &
                    self.get_shared_activity_q("location", permission_summary["location"])
                )

            if student_set_ids:
                if not permission_summary["student_set"]["show"]:
                    return []
                activities = activities.filter(student_set__id__in=student_set_ids)

        activities = (
            activities
            .select_related(
                "activity_type",
                "activity_template",
                "department",
                "academic_term",
                "module",
                "week_pattern",
            )
            .prefetch_related(
                "week",
                "week_pattern__week",
                "staff",
                "location",
                "student_set",
            )
            .annotate(
                real_size=Coalesce(Sum("student_set__planned_size"), 0)
            )
            .distinct()
        )

        result = []
        for val in activities:
            # if val.is_booking == 1:
            #     name_clean = val.name or ""
            #     desc_clean = val.desc or ""
            #     display_name = f"{name_clean} {desc_clean}".strip()
            # else:
            #     display_name = val.name
            display_name = val.name

            data = {
                "id": val.id,
                "code": val.code,
                "name": display_name,
                "desc": val.desc,
                "module_desc": val.module.desc if val.module else None,
                "activity_template": val.activity_template.name if val.activity_template else None,
                "activity_template_id": val.activity_template_id,
                "department": val.department.name if val.department else None,
                "department_id": val.department_id,
                "academic_term": val.academic_term.name if val.academic_term else None,
                "academic_term_id": val.academic_term_id,
                "activity_type": val.activity_type.name if val.activity_type else None,
                "activity_type_color": val.activity_type.color if val.activity_type else None,
                "activity_type_id": val.activity_type_id,
                "module": val.module.name if val.module else None,
                "module_id": val.module_id,
                "duration": val.duration,
                "slot_required": val.slot_required,
                "planned_size": val.planned_size,
                "real_size": val.real_size,
                "scheduled_start_time": val.scheduled_start_time,
                "scheduled_day": val.scheduled_day,
                "scheduled_start_slot": val.scheduled_start_slot,
                "scheduled": val.scheduled,
                "is_jta": val.is_jta,
                "jta_parent_id": val.jta_parent_id,
                "is_variant": val.is_variant,
                "variant_parent_id": val.variant_parent_id,
                "is_booking": val.is_booking,
                "week_pattern_id": val.week_pattern_id,
                "week_pattern_name": val.week_pattern.name if val.week_pattern else None,
            }

            if val.week_pattern:
                data["week"] = [
                    {
                        "id": w.id,
                        "week": w.week,
                        "start_date": w.start_date,
                    }
                    for w in val.week_pattern.week.all()
                ] if val.week_pattern else []
            else:
                data["week"] = [
                    {
                        "id": w.id,
                        "week": w.week,
                        "start_date": w.start_date,
                    }
                    for w in val.week.all()
                ]


            data["staff"] = [
                {
                    "id": o.id,
                    "code": o.code,
                    "name": o.name,
                }
                for o in val.staff.all()
            ]

            data["location"] = [
                {
                    "id": o.id,
                    "code": o.code,
                    "name": o.name,
                }
                for o in val.location.all()
            ]

            data["student_set"] = [
                {
                    "id": o.id,
                    "code": o.code,
                    "name": o.name,
                    "planned_size": o.planned_size,
                }
                for o in val.student_set.all()
            ]

            result.append(data)

        return result