from itertools import product, groupby, chain
from operator import itemgetter

from django.core.paginator import Paginator, EmptyPage
from django.core.exceptions import ObjectDoesNotExist
from django.db.models import Q, Count, Value

from rest_framework.response import Response

from staff.person.models import Staff
from staff.groups.models import Group, GroupMembership
from staff.lib.decorators import available_for_external

from staff.achievery.domain import GivenAchievement
from staff.achievery.models import Achievement, Icon, Event
from staff.achievery.serializers import (
    PaginatedGivenAchievementSerializer,
    GivenAchievementSerializer,
)
from staff.achievery.utils import expand, icon_compat_wrapper
from staff.achievery.views.base import (
    AchieveryAPIView,
    ListMixin,
    SchemaView,
    DetailsMixin,
    EditMixin,
    CreateMixin,
)
from staff.achievery.views.utils import (
    serialize_given,
    GIVEN_FIELDS,
    has_access_to_achievements_info,
)


__all__ = (
    'GivenListView', 'GivenDetailsView', 'GivenSchemaView',
)


@available_for_external('achievery.external_with_achievery_access')
class GivenView(AchieveryAPIView):
    domain_object_class = GivenAchievement
    serializer_class = GivenAchievementSerializer
    paginated_serializer_class = PaginatedGivenAchievementSerializer


@available_for_external('achievery.external_with_achievery_access')
class GivenListView(GivenView, ListMixin, CreateMixin):
    default_fields = GIVEN_FIELDS

    def get(self, request):
        lookup = self.validate_fields(request.PARAMS.filter)
        user = self.get_user()

        try:
            target_login = request.GET.get('person.login')
            if (
                (target_login is None and not user.is_internal())
                or (target_login is not None and not has_access_to_achievements_info(user, target_login))
            ):
                return Response('Access denied', status=403)
        except ObjectDoesNotExist:
            return Response('User with this login does not exist', status=400)

        roles = self.role_registry
        model_cls = GivenAchievement.get_model_class()
        filter_lookup = list(GivenAchievement.translate_lookup(lookup))
        sort = list(GivenAchievement.translate_fields(request.PARAMS.sort))

        givens = (
            roles
            .get_base_query(user, model_cls)
            .get_queryset(model_cls)
            .filter(*filter_lookup)
            .order_by(*sort)
        )

        if request.PARAMS.search:
            search_lookup = Q()
            for f, suffix in product(GivenAchievement.__search_fields__, GivenAchievement.__search_suffixes__):
                search_lookup |= Q(**{'{}__{}'.format(f, suffix): request.PARAMS.search})
            givens = givens.filter(search_lookup)

        paginator = Paginator(givens.values().distinct(), request.PARAMS.limit)
        try:
            page = paginator.page(request.PARAMS.page)
        except EmptyPage:
            page = paginator.page(paginator.num_pages)

        givens = page.object_list
        givens_ids = {ga['id'] for ga in givens}

        achievements = (
            Achievement.objects
            .filter(pk__in={ga['achievement_id'] for ga in givens})
            .values()
        )
        achievements = {a['id']: a for a in achievements}
        achievements_ids = achievements.keys()

        groups = (
            Group.objects
            .filter(achievement__in=achievements_ids)
            .values()
        )
        groups = {g['id']: g for g in groups}
        groups_ids = groups.keys()

        group_memberships = (
            GroupMembership.objects
            .filter(group__in=groups_ids)
            .order_by('group')
            .values()
        )

        events = (
            Event.objects
            .filter(given_achievement__in=givens_ids)
            .order_by('given_achievement', '-created_at')
            .values()
        )

        staff_ids = set(chain(
            (gm['staff_id'] for gm in group_memberships),
            (ga['person_id'] for ga in givens),
            (e['initiator_id'] for e in events),
        ))

        persons = {
            p['id']: p
            for p in Staff.objects.filter(pk__in=staff_ids).values(
                'id',
                'login',
                'uid',
                'is_dismissed',
                'first_name',
                'first_name_en',
                'last_name',
                'last_name_en',
            )
        }

        icons = icon_compat_wrapper(
            Icon.objects
            .filter(achievement__in=achievements_ids)
            .values(
                'id',
                'mime_type',
                'level',
                'achievement_id',
                'modified_at'
            )
        )

        ctx = {
            'achievements': achievements,
            'groups': groups,
            'persons': persons,
            'icons': {
                (i['achievement_id'], i['level'], i['is_big']): i
                for i in icons
            },
            'persons_by_groups': {
                gid: [persons[m['staff_id']] for m in memberships]
                for gid, memberships
                in groupby(group_memberships, itemgetter('group_id'))
            },
            'events_by_givens': {
                gaid: [e for e in events]
                for gaid, events
                in groupby(events, itemgetter('given_achievement_id'))
            },
            'localize': self.get_lang(),
            'role_registry': self.role_registry,
            'given_counts': {
                ga['achievement']: ga['count'] for ga in
                GivenAchievement.get_model_class().active.values('achievement').annotate(count=Count(Value(1)))
            },
        }
        fields_ = list(request.PARAMS.fields)
        requested_fields = (
            expand(dict(zip(fields_, [None]*len(fields_)))) or self.default_fields
        )
        data = {
            'total': paginator.count,
            'limit': paginator.per_page,
            'pages': paginator.num_pages,
            'page': page.number,
            'objects': [
                serialize_given(obj, ctx, requested_fields, is_internal_observer=user.is_internal())
                for obj in givens
            ],
        }

        return Response(data)


@available_for_external('achievery.external_with_achievery_access')
class GivenDetailsView(GivenView, DetailsMixin, EditMixin):
    pass


@available_for_external('achievery.external_with_achievery_access')
class GivenSchemaView(SchemaView):
    domain_object_class = GivenAchievement
