from django.db import transaction
from rest_framework import status
from rest_framework.exceptions import ValidationError

from api.models import AuditTrail, AuditTrailDetails, EsExamActivity, EsExamRequirement, EsExamRequirementPrecedingExamination, EsExamRequirementPrecedingExaminationGroup
from api.translation import __
from api.utils import get_exception_detail, get_ip, log_critical_error
from api.validator import BaseValidator
from api.views.admin.exam_requirement_relation_grouping import (
    expanded_exam_activity_ids_for_requirement_selection,
    validate_preceding_examination_relation_update,
    validate_relation_exam_activity_ids,
)
from api.views.admin.base import AdminApiBase


class ExamRequirementUpdatePrecedingExamination(AdminApiBase):
    def validate_request(self, request):
        rules = {
            "id": "required|array|exists:api.EsExamRequirement,id",
            "exam_activity_id": "nullable|array|exists:api.EsExamActivity,id",
        }

        attribute = {
            "id": "ID",
            "exam_activity_id": __("attr.activity_id"),
        }

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

        validate_relation_exam_activity_ids(request.data)

        exam_requirement_ids = request.data.get("id") or []
        return EsExamRequirement.objects.filter(id__in=exam_requirement_ids)

    @transaction.atomic
    def post(self, request):
        try:
            edit_obj = self.validate_request(request)
            edit_requirement_ids = list(edit_obj.values_list("id", flat=True))

            action = "exam_requirement_update_preceding_examination"
            old_new_data = {
                "new_data": {},
                "old_data": {},
            }

            payload_exam_activity_ids = list(dict.fromkeys(request.data.get("exam_activity_id") or []))

            validate_preceding_examination_relation_update(
                EsExamRequirementPrecedingExamination,
                edit_requirement_ids,
                payload_exam_activity_ids,
            )

            for obj in edit_obj:
                existing_relations = EsExamRequirementPrecedingExamination.objects.filter(
                    exam_requirement_id=obj.id,
                )
                existing_exam_activity_ids = set(
                    existing_relations.values_list("exam_activity_id", flat=True)
                )
                target_exam_activity_ids = expanded_exam_activity_ids_for_requirement_selection(
                    obj.id,
                    payload_exam_activity_ids,
                )

                if existing_exam_activity_ids != target_exam_activity_ids:
                    old_new_data.setdefault("old_data", {}).setdefault(obj.id, {})["exam_activity_id"] = sorted(existing_exam_activity_ids)
                    old_new_data.setdefault("new_data", {}).setdefault(obj.id, {})["exam_activity_id"] = sorted(target_exam_activity_ids)

                    old_group_ids = list(
                        existing_relations.values_list("group_id", flat=True).distinct()
                    )
                    existing_relations.delete()

                    if target_exam_activity_ids:
                        group = EsExamRequirementPrecedingExaminationGroup.objects.create()
                        EsExamRequirementPrecedingExamination.objects.bulk_create(
                            [
                                EsExamRequirementPrecedingExamination(
                                    group_id=group.id,
                                    exam_requirement_id=obj.id,
                                    exam_activity_id=exam_activity_id,
                                )
                                for exam_activity_id in sorted(target_exam_activity_ids)
                            ]
                        )

                    for group_id in old_group_ids:
                        if not EsExamRequirementPrecedingExamination.objects.filter(group_id=group_id).exists():
                            EsExamRequirementPrecedingExaminationGroup.objects.filter(id=group_id).delete()

            if old_new_data["old_data"] or old_new_data["new_data"]:
                audit_trail = AuditTrail.objects.create(
                    user_id=request.user.id,
                    type=self.audit_type,
                    ip_address=get_ip(request),
                )

                remark_param = {
                    "code": ",".join(edit_obj.values_list("code", flat=True)),
                }

                AuditTrailDetails.custom_insert(
                    audit_trail=audit_trail,
                    action=action,
                    remark_param=remark_param,
                    new_data=old_new_data["new_data"],
                    old_data=old_new_data["old_data"],
                )

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