from django.contrib import admin
from django.contrib.auth import get_user_model
from django.db.models import Q
from django.utils.translation import ugettext_lazy as _
from swapper import load_model

User = get_user_model()
OrganizationUser = load_model('openwisp_users', 'OrganizationUser')


class MultitenantAdminMixin(object):
    """
    Mixin that makes a ModelAdmin class multitenant:
    users will see only the objects related to the organizations
    they are associated with.
    """

    multitenant_shared_relations = []
    multitenant_parent = None

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        parent = self.multitenant_parent
        shared_relations = self.multitenant_shared_relations
        if parent and parent not in shared_relations:
            self.multitenant_shared_relations.append(parent)

    def get_repr(self, obj):
        return str(obj)

    get_repr.short_description = _('name')

    def get_queryset(self, request):
        """
        If current user is not superuser, show only the
        objects associated to organizations he/she is associated with
        """
        qs = super().get_queryset(request)
        user = request.user
        if self.model == User:
            return self.multitenant_behaviour_for_user_admin(request)
        if user.is_superuser:
            return qs
        if hasattr(self.model, 'organization'):
            return qs.filter(organization__in=user.organizations_dict.keys())
        elif not self.multitenant_parent:
            return qs
        else:
            qsarg = '{0}__organization__in'.format(self.multitenant_parent)
            return qs.filter(**{qsarg: user.organizations_dict.keys()})

    def _edit_form(self, request, form):
        """
        Modifies the form querysets as follows;
        if current user is not superuser:
            * show only relevant organizations
            * show only relations associated to relevant organizations
              or shared relations
        else show everything
        """
        fields = form.base_fields
        if not request.user.is_superuser:
            orgs_pk = request.user.organizations_dict.keys()
            # organizations relation;
            # may be readonly and not present in field list
            if 'organization' in fields:
                org_field = fields['organization']
                org_field.queryset = org_field.queryset.filter(pk__in=orgs_pk)
            # other relations
            q = Q(organization__in=orgs_pk) | Q(organization=None)
            for field_name in self.multitenant_shared_relations:
                # each relation may be readonly
                # and not present in field list
                if field_name not in fields:
                    continue
                field = fields[field_name]
                field.queryset = field.queryset.filter(q)

    def get_form(self, request, obj=None, **kwargs):
        form = super().get_form(request, obj, **kwargs)
        self._edit_form(request, form)
        return form

    def get_formset(self, request, obj=None, **kwargs):
        formset = super().get_formset(request, obj=None, **kwargs)
        self._edit_form(request, formset.form)
        return formset

    def multitenant_behaviour_for_user_admin(self, request):
        """
        if operator is logged in - show only users
        from same organization and hide superusers
        if superuser is logged in - show all users
        """
        if not request.user.is_superuser:
            user = request.user
            org_users = OrganizationUser.objects.filter(user=user).select_related(
                'organization'
            )
            qs = User.objects.none()
            for org_user in org_users:
                if org_user.is_admin:
                    qs = qs | org_user.organization.users.all().distinct()
            # hide superusers from organization operators
            # so they can't edit nor delete them
            qs = qs.filter(is_superuser=False)
        else:
            qs = super().get_queryset(request)
        return qs


class MultitenantOrgFilter(admin.RelatedFieldListFilter):
    """
    Admin filter that shows only organizations the current
    user is associated with in its available choices
    """

    multitenant_lookup = 'pk__in'

    def field_choices(self, field, request, model_admin):
        if request.user.is_superuser:
            return super().field_choices(field, request, model_admin)
        organizations = request.user.organizations_dict.keys()
        return field.get_choices(
            include_blank=False,
            limit_choices_to={self.multitenant_lookup: organizations},
        )


class MultitenantRelatedOrgFilter(MultitenantOrgFilter):
    """
    Admin filter that shows only objects which have a relation with
    one of the organizations the current user is associated with
    """

    multitenant_lookup = 'organization__in'