from django.db.models import Count, Q, Case, When, BooleanField, F, Value
from django.db.models.expressions import Subquery, OuterRef
from django.db.models.functions import Coalesce
from django.utils.functional import cached_property
from django.contrib.postgres.search import SearchRank

from rest_framework import status
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response

from intranet.femida.src.actionlog.decorators import action_logged
from intranet.femida.src.api.core.views import (
    BaseView,
    BaseFormViewMixin,
    InstanceFormViewMixin,
    QueryParamsFormView,
    unfold_query_params,
)
from intranet.femida.src.api.complaints.serializers import ComplaintLiteSerializer
from intranet.femida.src.core.db import SearchQuery
from intranet.femida.src.interviews.models import Assignment
from intranet.femida.src.problems.choices import COMPLAINT_RESOLUTIONS
from intranet.femida.src.problems.models import Problem, Complaint, PresetProblem
from intranet.femida.src.problems.complaints.controllers import close_complaint
from intranet.femida.src.problems.helpers import add_active_complaints_count, get_problems_stats
from intranet.femida.src.problems.controllers import update_or_create_problem
from intranet.femida.src.problems.utils import merge, get_problems_usage_in_context

from . import serializers, forms, permissions


class ProblemsSearchView(BaseView):

    model_class = Problem
    list_item_serializer_class = serializers.ProblemSerializer
    permission_classes = [
        IsAuthenticated,
        permissions.ProblemPermission,
    ]

    @cached_property
    def params(self):
        return unfold_query_params(
            query_params=self.request.query_params,
            list_fields=('categories',),
        )

    def get_list_item_serializer_context(self):
        problem_ids = [p.id for p in self.page]
        return {
            'problems_usage_in_context': self.problems_usage,
            'problems_stats': get_problems_stats(problem_ids),
        }

    def _get_filter_data(self, filter_form_class):
        filter_form = filter_form_class(data=self.params)
        self.validate(filter_form)
        return filter_form.cleaned_data

    def _is_problem_in_list(self, problem_ids):
        return Case(
            When(id__in=problem_ids, then=True),
            default=False,
            output_field=BooleanField(),
        )

    def _annotate_extra_data(self, queryset, extra_data):
        sort = extra_data.get('sort')

        annotations = {}

        # Проставляем флаг is_favorite
        favorite_problems = self.request.user.favorite_problems.values_list('id', flat=True)
        annotations['is_favorite'] = self._is_problem_in_list(favorite_problems)

        if 'popularity' in sort or '-popularity' in sort:
            problems_popularity = (
                Assignment.objects
                .values('problem_id')
                .annotate(popularity=Count('grade'))
                .filter(problem_id=OuterRef('pk'))
                .order_by()
                .values('popularity')
            )
            annotations['popularity'] = Coalesce(Subquery(problems_popularity), Value(0))

        if ('rank' in sort or '-rank' in sort) and not extra_data.get('text'):
            annotations['rank'] = F('id')

        queryset = add_active_complaints_count(queryset)

        return queryset.annotate(**annotations)

    def get_queryset(self):
        return self.model_class.alive_objects.all()

    def filter_queryset_by_usage(self, queryset, filter_params):
        for_interview = filter_params.get('for_interview')
        for_preset = filter_params.get('for_preset')
        self.problems_usage = get_problems_usage_in_context(
            interview=for_interview,
            preset=for_preset,
        )

        used_flag_problem_ids_map = {
            'used_for_interview': [],
            'used_for_another_interview': [],
            'used_for_candidate': [],
            'used_for_preset': [],
        }
        _items = self.problems_usage.get('interview', {}).get('problems', {}).items()
        for problem_id, info in _items:
            if info['used_for_interview']:
                used_flag_problem_ids_map['used_for_interview'].append(problem_id)
            if info['used_for_another_interview']:
                used_flag_problem_ids_map['used_for_another_interview'].append(problem_id)
            if info['used_for_interview'] or info['used_for_another_interview']:
                used_flag_problem_ids_map['used_for_candidate'].append(problem_id)

        for problem_id, info in self.problems_usage.get('preset', {}).get('problems', {}).items():
            if info['used_for_preset']:
                used_flag_problem_ids_map['used_for_preset'].append(problem_id)

        for flag, problem_ids in used_flag_problem_ids_map.items():
            if filter_params[flag] is True:
                queryset = queryset.filter(id__in=problem_ids)
            elif filter_params[flag] is False:
                queryset = queryset.exclude(id__in=problem_ids)
        return queryset

    def filter_queryset(self, queryset):
        filter_params = self._get_filter_data(forms.ProblemSearchForm)
        filter_params.update(self._get_filter_data(forms.ProblemListFilterForm))

        # Для быстрого переключения на multiplesort,
        # изначально делаем массив
        sort = filter_params.get('sort')
        filter_params['sort'] = [sort] if sort else []

        queryset = self._annotate_extra_data(queryset, filter_params)

        if filter_params['text']:
            search_query = SearchQuery(filter_params['text'])
            queryset = (
                queryset
                .filter(search_vector=search_query)
                .annotate(rank=SearchRank(F('search_vector'), search_query))
            )
            filter_params['sort'].append('-rank')

        category_ids = [c.id for c in filter_params['categories']]
        if category_ids:
            category_problems = (
                Problem.categories.through.objects.values('problem_id')
                .annotate(categories_count=Count('category_id'))
                .filter(category__in=category_ids, categories_count=len(category_ids))
                .values('problem_id')
            )
            queryset = queryset.filter(id__in=category_problems)

        if filter_params['only_favorite']:
            queryset = queryset.filter(fans=self.request.user)

        if filter_params['exclude_deprecated']:
            queryset = queryset.filter(is_deprecated=False)

        if filter_params['preset']:
            queryset = queryset.filter(id__in=(
                PresetProblem.objects
                .filter(preset=filter_params['preset'])
                .values('problem')
            ))

        if filter_params['created_by']:
            queryset = queryset.filter(created_by=filter_params['created_by'])

        queryset = self.filter_queryset_by_usage(queryset, filter_params)

        if filter_params['sort']:
            queryset = queryset.order_by(*filter_params['sort'])

        return queryset

    def get(self, request, *args, **kwargs):
        """
        Поиск по задачам 2.0
        """
        return self.list(request, *args, **kwargs)


class ProblemsSearchFormView(QueryParamsFormView):

    model_class = Problem
    validator_class = forms.ProblemSearchForm
    form_serializer_class = serializers.ProblemSearchFormSerializer
    permission_classes = [
        IsAuthenticated,
        permissions.ProblemPermission,
    ]

    def get_query_params(self):
        return unfold_query_params(
            query_params=self.request.query_params,
            list_fields=('categories',),
        )


class ProblemCreateView(BaseFormViewMixin, BaseView):

    model_class = Problem
    validator_class = forms.ProblemCreateForm
    detail_serializer_class = serializers.ProblemDetailSerializer
    permission_classes = [
        IsAuthenticated,
        permissions.ProblemPermission,
    ]

    def get_detail_serializer_context(self):
        return {
            # При создании задачи никакой статистики решения
            # по ней ещё нет, поэтому получаем "пустую" статистику.
            'problems_stats': get_problems_stats([]),
        }

    @action_logged('problem_create')
    def post(self, request, *args, **kwargs):
        """
        Создание задачи
        """
        return self.create(request, *args, **kwargs)

    def perform_create(self, data):
        return update_or_create_problem(data, self.request.user)


class ProblemDetailView(InstanceFormViewMixin, BaseView):
    """
    Редактирование задачи
    """
    model_class = Problem
    detail_serializer_class = serializers.ProblemDetailSerializer
    form_serializer_class = serializers.ProblemUpdateFormSerializer
    validator_class = forms.ProblemUpdateForm
    permission_classes = [
        IsAuthenticated,
        permissions.ProblemPermission,
    ]

    def get_detail_serializer_context(self):
        instance = self.get_object()
        return {
            'problems_stats': get_problems_stats([instance.id]),
        }

    def get_queryset(self):
        if self.request.method == 'GET':
            queryset = self.model_class.objects.all()
        else:
            queryset = self.model_class.alive_objects.all()
        queryset = add_active_complaints_count(queryset)
        return queryset

    def get(self, request, *args, **kwargs):
        """
        Одна задача
        """
        return self.retrieve(request, *args, **kwargs)

    @action_logged('problem_update')
    def put(self, request, *args, **kwargs):
        """
        Редактирование задачи
        """
        return self.update(request, *args, **kwargs)

    @action_logged('problem_update')
    def patch(self, request, *args, **kwargs):
        """
        Частичное редактирование задачи
        """
        return self.partial_update(request, *args, **kwargs)

    @action_logged('problem_delete')
    def delete(self, request, *args, **kwargs):
        """
        Удаление задачи
        """
        return self.destroy(request, *args, **kwargs)

    def perform_update(self, data, instance):
        return update_or_create_problem(data, self.request.user, instance)

    def perform_destroy(self, instance):
        instance.deleted = True
        instance.save()


class ProblemMergeView(BaseView):

    validator_class = forms.ProblemMergeForm
    permission_classes = [
        IsAuthenticated,
        permissions.ProblemPermission,
    ]

    @action_logged('problem_merge')
    def post(self, request, *args, **kwargs):
        """
        Слияние задач
        """
        data = self.request.data
        data['problem_id'] = kwargs.get('pk')

        validator = self.get_validator_object(data)
        self.validate(validator)

        duplicate = validator.cleaned_data['problem_id']
        original = validator.cleaned_data['original_problem_id']

        merge(duplicate, original)
        related_complaints = self.process_related_complaints(duplicate, original)

        response_data = {
            'related_complaints': ComplaintLiteSerializer(related_complaints, many=True).data,
            'original_problem': serializers.ProblemSerializer(
                instance=original,
                context={
                    'problems_stats': get_problems_stats([original.id]),
                },
            ).data,
        }
        return Response(data=response_data, status=status.HTTP_200_OK)

    def process_related_complaints(self, duplicate, original):
        """
        TODO: Вообще говоря, эта логика весьма сомнительна
        """
        related_complaints = []
        # Закрываем с резолюцией resolved данную жалобу и эквивалентные ей
        relevant_complaints = (
            Complaint.objects
            .filter(
                is_active=True,
                problem=duplicate,
                original_problem=original,
            )
        )
        for complaint in relevant_complaints:
            instance = close_complaint(complaint, self.request.user, COMPLAINT_RESOLUTIONS.resolved)
            related_complaints.append(instance)

        # Закрываем без резолюции жалобы к дублирующей задаче,
        # а также пометки ее как дубликата на другие задачи
        irrelevant_complaints = (
            Complaint.objects
            .filter(
                Q(is_active=True),
                Q(problem=duplicate) | Q(original_problem=duplicate),
            )
        )
        for complaint in irrelevant_complaints:
            instance = close_complaint(complaint, self.request.user, COMPLAINT_RESOLUTIONS.rejected)
            related_complaints.append(instance)
        return related_complaints


class ProblemAssignments(BaseView):

    model_class = Assignment
    list_item_serializer_class = serializers.ProblemAssignmentsSerializer
    permission_classes = [
        IsAuthenticated,
        permissions.ProblemPermission,
    ]

    def get(self, *args, **kwargs):
        return self.list(*args, **kwargs)

    def get_queryset(self):
        problem_id = self.kwargs['pk']
        return (
            Assignment.objects
            .filter(
                problem_id=problem_id,
                grade__isnull=False,
            )
            .order_by('-id')
        )
