from datetime import datetime, time
from typing import Iterable, Union

from graphene_django.types import DjangoObjectType
from guardian.shortcuts import get_objects_for_user

from aleksis.core.schema.base import (
    BaseBatchCreateMutation,
    BaseBatchDeleteMutation,
    BaseBatchPatchMutation,
    DjangoFilterMixin,
    PermissionsTypeMixin,
)

from ..models import Absence, AbsenceReason, AbsenceReasonTag


class AbsenceReasonTagType(PermissionsTypeMixin, DjangoFilterMixin, DjangoObjectType):
    class Meta:
        model = AbsenceReasonTag
        fields = ("id", "short_name", "name")
        filter_fields = {
            "short_name": ["icontains", "exact"],
            "name": ["icontains", "exact"],
        }

    @classmethod
    def get_queryset(cls, queryset, info):
        return get_objects_for_user(info.context.user, "kolego.view_absencereasontag", queryset)


class AbsenceReasonType(PermissionsTypeMixin, DjangoFilterMixin, DjangoObjectType):
    class Meta:
        model = AbsenceReason
        fields = ("id", "short_name", "name", "colour", "count_as_absent", "default", "tags")
        filter_fields = {
            "short_name": ["icontains", "exact"],
            "name": ["icontains", "exact"],
        }

    def resolve_tags(root, info, **kwargs):
        return root.tags.managed_and_unmanaged().filter(absence_reasons=root)

    @classmethod
    def get_queryset(cls, queryset, info):
        if not info.context.user.has_perm("kolego.fetch_absencereasons_rule"):
            return []
        return queryset


class AbsenceType(PermissionsTypeMixin, DjangoFilterMixin, DjangoObjectType):
    class Meta:
        model = Absence
        fields = (
            "id",
            "person",
            "reason",
            "comment",
            "datetime_start",
            "datetime_end",
            "date_start",
            "date_end",
        )
        filter_fields = {
            "person__full_name": ["icontains", "exact"],
            "comment": ["icontains", "exact"],
        }

    @classmethod
    def get_queryset(cls, queryset, info):
        query = get_objects_for_user(info.context.user, "kolego.view_absence", queryset)
        query = query.select_related("person", "reason")
        return query


class AbsenceBatchCreateMutation(BaseBatchCreateMutation):
    class Meta:
        model = Absence
        fields = (
            "person",
            "reason",
            "comment",
            "datetime_start",
            "datetime_end",
            "date_start",
            "date_end",
        )
        optional_fields = ("comment", "reason")
        permissions = ("kolego.create_absence_rule",)

    @classmethod
    def before_save(cls, root, info, input, created_objects):  # noqa
        modified_created_objects = []

        for obj in created_objects:
            events_within = (
                Absence.get_objects(
                    None,
                    {"person": obj.person.pk},
                    start=obj.datetime_start or obj.date_start,
                    end=obj.datetime_end or obj.date_end,
                )
                .order_by("pk")
                .filter(reason=obj.reason)
            )

            if events_within.exists():
                # Convert dates of new event to datetimes in case dates are used
                new_datetime_start = (
                    obj.datetime_start
                    if obj.datetime_start
                    else datetime.combine(obj.date_start, time())
                ).astimezone(obj.timezone)
                new_datetime_end = (
                    obj.datetime_end
                    if obj.datetime_end
                    else datetime.combine(obj.date_end, datetime.max.time())
                ).astimezone(obj.timezone)

                events_within_datetime_start_list = [
                    (
                        Absence.value_start_datetime(event_within)
                        if event_within.datetime_start
                        else datetime.combine(Absence.value_start_datetime(event_within), time())
                    )
                    for event_within in events_within
                ]

                events_within_datetime_end_list = [
                    (
                        Absence.value_end_datetime(event_within)
                        if event_within.datetime_end
                        else datetime.combine(
                            Absence.value_end_datetime(event_within), datetime.max.time()
                        )
                    ).astimezone(event_within.timezone)
                    for event_within in events_within
                ]

                # Extend overlapping lesson with same reason
                last_event_within = events_within.last()
                last_event_within.datetime_start = min(
                    new_datetime_start, *events_within_datetime_start_list
                )
                last_event_within.datetime_end = max(
                    new_datetime_end, *events_within_datetime_end_list
                )

                events_within.exclude(pk=last_event_within.pk).delete()

                modified_created_objects.append(last_event_within)
            else:
                modified_created_objects.append(obj)

        return modified_created_objects


class AbsenceBatchDeleteMutation(BaseBatchDeleteMutation):
    class Meta:
        model = Absence
        permissions = ("kolego.delete_absence_rule",)


class AbsenceBatchPatchMutation(BaseBatchPatchMutation):
    class Meta:
        model = Absence
        fields = (
            "id",
            "person",
            "reason",
            "comment",
            "datetime_start",
            "datetime_end",
            "date_start",
            "date_end",
        )
        permissions = ("kolego.edit_absence_rule",)


class AbsenceReasonBatchCreateMutation(BaseBatchCreateMutation):
    class Meta:
        model = AbsenceReason
        fields = ("short_name", "name", "colour", "count_as_absent", "default", "tags")
        optional_fields = ("name",)
        permissions = ("kolego.create_absencereason_rule",)

    @classmethod
    def get_all_objs(cls, Model, ids: Iterable[Union[str, int]]):
        return list(
            Model.objects.managed_and_unmanaged().filter(
                pk__in=[cls.resolve_id(id_) for id_ in ids]
            )
        )


class AbsenceReasonBatchDeleteMutation(BaseBatchDeleteMutation):
    class Meta:
        model = AbsenceReason
        permissions = ("kolego.delete_absencereason_rule",)


class AbsenceReasonBatchPatchMutation(BaseBatchPatchMutation):
    class Meta:
        model = AbsenceReason
        fields = ("id", "short_name", "name", "colour", "count_as_absent", "default", "tags")
        permissions = ("kolego.edit_absencereason_rule",)

    @classmethod
    def get_all_objs(cls, Model, ids: Iterable[Union[str, int]]):
        return list(
            Model.objects.managed_and_unmanaged().filter(
                pk__in=[cls.resolve_id(id_) for id_ in ids]
            )
        )


class AbsenceReasonTagBatchCreateMutation(BaseBatchCreateMutation):
    class Meta:
        model = AbsenceReasonTag
        fields = ("short_name", "name")
        optional_fields = ("name",)
        permissions = ("kolego.create_absencereasontag_rule",)


class AbsenceReasonTagBatchDeleteMutation(BaseBatchDeleteMutation):
    class Meta:
        model = AbsenceReasonTag
        permissions = ("kolego.delete_absencereasontag_rule",)


class AbsenceReasonTagBatchPatchMutation(BaseBatchPatchMutation):
    class Meta:
        model = AbsenceReasonTag
        fields = ("id", "short_name", "name")
        permissions = ("kolego.edit_absencereasontag_rule",)