"""
Author: Michel Peltriaux
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
Contact: michel.peltriaux@sgdnord.rlp.de
Created on: 07.12.20

"""
import collections

from dal_select2.views import Select2QuerySetView, Select2GroupQuerySetView
from django.core.exceptions import ImproperlyConfigured

from konova.utils.message_templates import UNGROUPED
from user.models import User, Team
from django.db.models import Q

from codelist.models import KonovaCode
from codelist.settings import CODELIST_COMPENSATION_ACTION_ID, CODELIST_BIOTOPES_ID, CODELIST_LAW_ID, \
    CODELIST_REGISTRATION_OFFICE_ID, CODELIST_CONSERVATION_OFFICE_ID, CODELIST_PROCESS_TYPE_ID, \
    CODELIST_BIOTOPES_EXTRA_CODES_ID, CODELIST_COMPENSATION_ACTION_DETAIL_ID, CODELIST_HANDLER_ID
from compensation.models import EcoAccount
from intervention.models import Intervention


class EcoAccountAutocomplete(Select2QuerySetView):
    """ Autocomplete for ecoAccount entries

    Only returns entries that are already recorded and not deleted

    """
    def get_queryset(self):
        if self.request.user.is_anonymous:
            return EcoAccount.objects.none()
        qs = EcoAccount.objects.filter(
            deleted=None,
            recorded__isnull=False,
        ).order_by(
            "identifier"
        )
        if self.q:
            qs = qs.filter(
                Q(identifier__icontains=self.q) |
                Q(title__icontains=self.q)
            ).distinct()
        return qs


class InterventionAutocomplete(Select2QuerySetView):
    """ Autocomplete for intervention entries

    Only returns entries that are accessible for the requesting user

    """
    def get_queryset(self):
        user = self.request.user
        if user.is_anonymous:
            return Intervention.objects.none()
        qs = Intervention.objects.filter(
            Q(deleted=None) &
            Q(users__in=[user]) |
            Q(teams__in=user.teams.all())
        ).order_by(
            "identifier"
        ).distinct()
        if self.q:
            qs = qs.filter(
                Q(identifier__icontains=self.q) |
                Q(title__icontains=self.q)
            ).distinct()
        return qs


class ShareUserAutocomplete(Select2QuerySetView):
    """ Autocomplete for share with single users


    """
    def get_queryset(self):
        if self.request.user.is_anonymous:
            return User.objects.none()
        qs = User.objects.all()
        if self.q:
            # Due to privacy concerns only a full username match will return the proper user entry
            qs = qs.filter(
                Q(username=self.q) |
                Q(email=self.q)
            ).distinct()
        qs = qs.order_by("username")
        return qs


class ShareTeamAutocomplete(Select2QuerySetView):
    """ Autocomplete for share with teams

    """
    def get_queryset(self):
        if self.request.user.is_anonymous:
            return Team.objects.none()
        qs = Team.objects.filter(
            deleted__isnull=True
        )
        if self.q:
            # Due to privacy concerns only a full username match will return the proper user entry
            qs = qs.filter(
                name__icontains=self.q
            )
        qs = qs.order_by(
            "name"
        )
        return qs


class TeamAdminAutocomplete(Select2QuerySetView):
    """ Autocomplete for share with teams

    """
    def get_queryset(self):
        if self.request.user.is_anonymous:
            return User.objects.none()
        qs = User.objects.filter(
            id__in=self.forwarded.get("members", [])
        ).exclude(
            id__in=self.forwarded.get("admins", [])
        )
        if self.q:
            # Due to privacy concerns only a full username match will return the proper user entry
            qs = qs.filter(
                name__icontains=self.q
            )
        qs = qs.order_by(
            "username"
        )
        return qs


class KonovaCodeAutocomplete(Select2GroupQuerySetView):
    """
    Provides simple autocomplete functionality for codes

    Parameter support:
    * q: Search for a word inside long_name of a code
    * c: Search inside a special codelist

    """
    paginate_by = 50

    def order_by(self, qs):
        """ Orders by a predefined value

        Wrapped in a function to provide inheritance-based different orders

        Args:
            qs (QuerySet): The queryset to be ordered

        Returns:
            qs (QuerySet): The ordered queryset
        """
        return qs.order_by(
            "long_name"
        )

    def get_queryset(self):
        if self.request.user.is_anonymous:
            return KonovaCode.objects.none()
        qs = KonovaCode.objects.filter(
            is_archived=False,
            is_selectable=True,
            is_leaf=True,
        )
        qs = self.order_by(qs)
        if self.c:
            qs = qs.filter(
                code_lists__in=[self.c]
            )
        if self.q:
            # Remove whitespaces from self.q and split input in all keywords (if multiple given)
            q = dict.fromkeys(self.q.strip().split(" "))
            # Create one filter looking up for all keys where all keywords can be found in the same result
            _filter = Q()
            for keyword in q:
                q_or = Q()
                q_or |= Q(long_name__icontains=keyword)
                q_or |= Q(short_name__icontains=keyword)
                q_or |= Q(parent__long_name__icontains=keyword)
                q_or |= Q(parent__short_name__icontains=keyword)
                q_or |= Q(parent__parent__long_name__icontains=keyword)
                q_or |= Q(parent__parent__short_name__icontains=keyword)
                _filter.add(q_or, Q.AND)
            qs = qs.filter(_filter).distinct()
        return qs

    def get_result_label(self, result):
        return f"{result.long_name}"

    def get_selected_result_label(self, result):
        return f"{result.__str__()}"


class CompensationActionCodeAutocomplete(KonovaCodeAutocomplete):
    """
    Due to limitations of the django dal package, we need to subclass for each code list
    """
    group_by_related = "parent"
    related_field_name = "long_name"

    def __init__(self, *args, **kwargs):
        self.c = CODELIST_COMPENSATION_ACTION_ID
        super().__init__(*args, **kwargs)

    def order_by(self, qs):
        return qs.order_by(
            "parent__long_name"
        )


class CompensationActionDetailCodeAutocomplete(KonovaCodeAutocomplete):
    """
    Due to limitations of the django dal package, we need to subclass for each code list
    """
    group_by_related = "parent"
    related_field_name = "long_name"
    paginate_by = 200

    def __init__(self, *args, **kwargs):
        self.c = CODELIST_COMPENSATION_ACTION_DETAIL_ID
        super().__init__(*args, **kwargs)

    def order_by(self, qs):
        return qs.order_by(
            "long_name"
        )


class BiotopeCodeAutocomplete(KonovaCodeAutocomplete):
    """
    Due to limitations of the django dal package, we need to subclass for each code list
    """
    group_by_related = "parent"
    related_field_name = "long_name"

    def __init__(self, *args, **kwargs):
        self.c = CODELIST_BIOTOPES_ID
        super().__init__(*args, **kwargs)

    def order_by(self, qs):
        """ Orders by a predefined value

        Wrapped in a function to provide inheritance-based different orders

        Args:
            qs (QuerySet): The queryset to be ordered

        Returns:
            qs (QuerySet): The ordered queryset
        """
        return qs.order_by(
            "short_name",
        )

    def get_result_label(self, result):
        return f"{result.long_name} ({result.short_name})"

    def get_results(self, context):
        """Return the options grouped by a common related model.

        Raises ImproperlyConfigured if self.group_by_name is not configured
        """
        if not self.group_by_related:
            raise ImproperlyConfigured("Missing group_by_related.")

        super_groups = collections.OrderedDict()

        object_list = context['object_list']

        for result in object_list:
            group = result.parent if result.parent else None
            group_name = f"{group.long_name} ({group.short_name})" if group else UNGROUPED
            super_group = result.parent.parent if result.parent else None
            super_group_name = f"{super_group.long_name} ({super_group.short_name})" if super_group else UNGROUPED
            super_groups.setdefault(super_group_name, {})
            super_groups[super_group_name].setdefault(group_name, [])
            super_groups[super_group_name][group_name].append(result)

        return [{
            'id': None,
            'text': super_group,
            'children': [{
                "id": None,
                "text": group,
                "children": [{
                    'id': self.get_result_value(result),
                    'text': self.get_result_label(result),
                    'selected_text': self.get_selected_result_label(result),
                } for result in results]
            } for group, results in groups.items()]
        } for super_group, groups in super_groups.items()]


class BiotopeExtraCodeAutocomplete(KonovaCodeAutocomplete):
    """
    Due to limitations of the django dal package, we need to subclass for each code list
    """
    group_by_related = "parent"
    related_field_name = "long_name"
    paginate_by = 200

    def __init__(self, *args, **kwargs):
        self.c = CODELIST_BIOTOPES_EXTRA_CODES_ID
        super().__init__(*args, **kwargs)

    def order_by(self, qs):
        """ Orders by a predefined value

        Wrapped in a function to provide inheritance-based different orders

        Args:
            qs (QuerySet): The queryset to be ordered

        Returns:
            qs (QuerySet): The ordered queryset
        """
        return qs.order_by(
            "long_name",
        )

    def get_result_label(self, result):
        return f"{result.long_name} ({result.short_name})"


class LawCodeAutocomplete(KonovaCodeAutocomplete):
    """
    Due to limitations of the django dal package, we need to subclass for each code list
    """
    group_by_related = "parent"
    related_field_name = "long_name"

    def __init__(self, *args, **kwargs):
        self.c = CODELIST_LAW_ID
        super().__init__(*args, **kwargs)

    def get_result_label(self, result):
        return f"{result.long_name} ({result.short_name})"


class ProcessTypeCodeAutocomplete(KonovaCodeAutocomplete):
    """
    Due to limitations of the django dal package, we need to subclass for each code list
    """
    group_by_related = "parent"
    related_field_name = "long_name"

    def __init__(self, *args, **kwargs):
        self.c = CODELIST_PROCESS_TYPE_ID
        super().__init__(*args, **kwargs)


class RegistrationOfficeCodeAutocomplete(KonovaCodeAutocomplete):
    """
    Due to limitations of the django dal package, we need to subclass for each code list
    """
    group_by_related = "parent"
    related_field_name = "long_name"

    def __init__(self, *args, **kwargs):
        self.c = CODELIST_REGISTRATION_OFFICE_ID
        super().__init__(*args, **kwargs)

    def order_by(self, qs):
        return qs.order_by(
            "parent__long_name"
        )


class ConservationOfficeCodeAutocomplete(KonovaCodeAutocomplete):
    """
    Due to limitations of the django dal package, we need to subclass for each code list
    """
    group_by_related = "parent"
    related_field_name = "long_name"

    def __init__(self, *args, **kwargs):
        self.c = CODELIST_CONSERVATION_OFFICE_ID
        super().__init__(*args, **kwargs)

    def get_result_label(self, result):
        return f"{result.long_name} ({result.short_name})"


class HandlerCodeAutocomplete(KonovaCodeAutocomplete):
    """
    Due to limitations of the django dal package, we need to subclass for each code list
    """
    group_by_related = "parent"
    related_field_name = "long_name"

    def __init__(self, *args, **kwargs):
        self.c = CODELIST_HANDLER_ID
        super().__init__(*args, **kwargs)

    def get_result_label(self, result):
        return result.long_name
