from typing import Iterable, Tuple

from django.apps import apps
from django.contrib.contenttypes.models import ContentType
from django.db import models

from ..core.config import config
from ..utils.autoupdate import inform_changed_data
from ..utils.rest_api import ValidationError
from .models import Item, ListOfSpeakers


def listen_to_related_object_post_save(sender, instance, created, **kwargs):
    """
    Receiver function to create agenda items. It is connected to the signal
    django.db.models.signals.post_save during app loading.

    The agenda_item_update_information container may have fields like type,
    parent_id, comment, duration, weight or skip_autoupdate.

    Do not run caching and autoupdate if the instance has a key
    skip_autoupdate in the agenda_item_update_information container.
    """
    instance_inform_changed_data = (
        False  # evaluates, if the related_object has to be send again.
    )
    # This is the case, if it was newly created and the autoupdate is not skipped with
    # agenda_item_skip_autoupdate or List_of_speakers_skip_autoupdate. If the related_object is
    # related to agenda items and list of speakers, the autoupdate is skipped, if one of the given
    # values is True.
    is_agenda_item_content_object = hasattr(instance, "get_agenda_title_information")
    is_list_of_speakers_content_object = hasattr(
        instance, "get_list_of_speakers_title_information"
    )

    if is_agenda_item_content_object:
        if created:

            if instance.get_collection_string() == "topics/topic":
                should_create_item = True
            elif config["agenda_item_creation"] == "always":
                should_create_item = True
            elif config["agenda_item_creation"] == "never":
                should_create_item = False
            else:
                should_create_item = instance.agenda_item_update_information.get(
                    "create"
                )
                if should_create_item is None:
                    should_create_item = config["agenda_item_creation"] == "default_yes"

            if should_create_item:
                attrs = {}
                for attr in ("type", "parent_id", "comment", "duration", "weight"):
                    if instance.agenda_item_update_information.get(attr):
                        attrs[attr] = instance.agenda_item_update_information.get(attr)
                # Validation: The type is validated in the serializers (to be between 1 and 3).
                # If the parent id is given, set the weight to the parent's weight +1 to
                # ensure the right placement in the tree. Also validate the parent_id!
                parent_id = attrs.get("parent_id")
                if parent_id is not None:
                    try:
                        parent = Item.objects.get(pk=parent_id)
                    except Item.DoesNotExist:
                        raise ValidationError(
                            {
                                "detail": "The parent item with id {0} does not exist",
                                "args": [parent_id],
                            }
                        )
                    attrs["weight"] = parent.weight + 1
                Item.objects.create(content_object=instance, **attrs)

                if not instance.agenda_item_skip_autoupdate:
                    instance_inform_changed_data = True
            else:
                is_agenda_item_content_object = False
                # important for the check for item and list of speakers together.

        elif (
            not instance.agenda_item_skip_autoupdate
            and instance.agenda_item is not None
        ):
            # If the object has changed, then also the agenda item has to be sent.
            inform_changed_data(instance.agenda_item)

    if is_list_of_speakers_content_object:
        if created:
            ListOfSpeakers.objects.create(content_object=instance)
            if not instance.list_of_speakers_skip_autoupdate:
                instance_inform_changed_data = True

        elif not instance.list_of_speakers_skip_autoupdate:
            # If the object has changed, then also the list of speakers has to be sent.
            inform_changed_data(instance.list_of_speakers)

    # If the related_object is related to the angenda and list of speakers, check, if skip_autoupdate
    # is False for both,
    if created and is_agenda_item_content_object and is_list_of_speakers_content_object:
        instance_inform_changed_data = not (
            instance.agenda_item_skip_autoupdate
            or instance.list_of_speakers_skip_autoupdate
        )

    if instance_inform_changed_data:
        inform_changed_data(instance)


def listen_to_related_object_post_delete(sender, instance, **kwargs):
    """
    Receiver function to delete agenda items. It is connected to the signal
    django.db.models.signals.post_delete during app loading.
    """

    has_content_object_mapping: Iterable[Tuple[str, models.Model]] = (
        ("get_agenda_title_information", Item),
        ("get_list_of_speakers_title_information", ListOfSpeakers),
    )

    for (attr, Model) in has_content_object_mapping:
        if hasattr(instance, attr):
            content_type = ContentType.objects.get_for_model(instance)
            try:
                Model.objects.get(
                    object_id=instance.pk, content_type=content_type
                ).delete()
            except Model.DoesNotExist:
                # Model does not exist so we do not have to delete it.
                pass


def get_permission_change_data(sender, permissions, **kwargs):
    """
    Yields all necessary collections if 'agenda.can_see',
    'agenda.can_see_list_of_speakers', 'agenda.can_see_internal_items'
    or 'agenda.can_manage' permissions changes.
    """
    agenda_app = apps.get_app_config(app_label="agenda")
    for permission in permissions:
        # There could be only one 'agenda.can_see' and then we want to return data.
        if (
            permission.content_type.app_label == agenda_app.label
            and permission.codename
            in (
                "can_see",
                "can_see_list_of_speakers",
                "can_see_internal_items",
                "can_manage",
            )
        ):
            yield from agenda_app.get_startup_elements()
            break
