from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import UserPassesTestMixin
from django.core.exceptions import PermissionDenied
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404
from django.template.loader import get_template
from django.urls import reverse_lazy
from django.utils.decorators import method_decorator
from django.views.generic import CreateView, ListView, DetailView, DeleteView

from wagtail.core.blocks import RichTextBlock

from opentech.apply.activity.messaging import messenger, MESSAGES
from opentech.apply.funds.models import ApplicationSubmission, AssignedReviewers
from opentech.apply.review.blocks import RecommendationBlock, RecommendationCommentsBlock
from opentech.apply.review.forms import ReviewModelForm, ReviewOpinionForm
from opentech.apply.stream_forms.models import BaseStreamForm
from opentech.apply.users.decorators import staff_required
from opentech.apply.users.groups import REVIEWER_GROUP_NAME
from opentech.apply.utils.views import CreateOrUpdateView
from opentech.apply.utils.image import generate_image_tag

from .models import Review
from .options import DISAGREE


class ReviewContextMixin:
    def get_context_data(self, **kwargs):
        assigned_reviewers = self.object.assigned.review_order()

        if not self.object.stage.has_external_review:
            assigned_reviewers = assigned_reviewers.staff()

        # Calculate the recommendation based on role and staff reviews
        recommendation = self.object.reviews.by_staff().recommendation()

        return super().get_context_data(
            hidden_types=[REVIEWER_GROUP_NAME],
            staff_reviewers_exist=assigned_reviewers.staff().exists(),
            assigned_reviewers=assigned_reviewers,
            recommendation=recommendation,
            **kwargs,
        )


def get_fields_for_stage(submission):
    forms = submission.get_from_parent('review_forms').all()
    index = submission.workflow.stages.index(submission.stage)
    try:
        return forms[index].form.form_fields
    except IndexError:
        return forms[0].form.form_fields


@method_decorator(login_required, name='dispatch')
class ReviewCreateOrUpdateView(BaseStreamForm, CreateOrUpdateView):
    submission_form_class = ReviewModelForm
    model = Review
    template_name = 'review/review_form.html'

    def get_object(self, queryset=None):
        return self.model.objects.get(submission=self.submission, author__reviewer=self.request.user)

    def dispatch(self, request, *args, **kwargs):
        self.submission = get_object_or_404(ApplicationSubmission, id=self.kwargs['submission_pk'])

        if not self.submission.phase.permissions.can_review(request.user) or not self.submission.has_permission_to_review(request.user):
            raise PermissionDenied()

        if self.request.POST and self.submission.reviewed_by(request.user):
            return self.get(request, *args, **kwargs)

        return super().dispatch(request, *args, **kwargs)

    def get_context_data(self, **kwargs):
        has_submitted_review = self.submission.reviewed_by(self.request.user)
        return super().get_context_data(
            submission=self.submission,
            has_submitted_review=has_submitted_review,
            title="Update Review draft" if self.object else 'Create Review',
            **kwargs
        )

    def get_defined_fields(self):
        return get_fields_for_stage(self.submission)

    def get_form_kwargs(self):
        kwargs = super().get_form_kwargs()
        kwargs['user'] = self.request.user
        kwargs['submission'] = self.submission

        if self.object:
            kwargs['initial'] = self.object.form_data

        return kwargs

    def form_valid(self, form):
        form.instance.form_fields = self.get_defined_fields()
        form.instance.author, _ = AssignedReviewers.objects.get_or_create_for_user(
            submission=self.submission,
            reviewer=self.request.user,
        )

        response = super().form_valid(form)

        if not self.object.is_draft:
            messenger(
                MESSAGES.NEW_REVIEW,
                request=self.request,
                user=self.request.user,
                submission=self.submission,
                related=self.object,
            )
        return response

    def get_success_url(self):
        return self.submission.get_absolute_url()


class ReviewDisplay(UserPassesTestMixin, DetailView):
    model = Review
    raise_exception = True

    def get_context_data(self, **kwargs):
        review = self.get_object()
        if review.author.reviewer != self.request.user:
            consensus_form = ReviewOpinionForm(
                instance=review.opinions.filter(author__reviewer=self.request.user).first(),
            )
        else:
            consensus_form = None
        return super().get_context_data(
            form=consensus_form,
            **kwargs,
        )

    def test_func(self):
        review = self.get_object()
        user = self.request.user
        author = review.author
        submission = review.submission
        partner_has_access = submission.partners.filter(pk=user.pk).exists()

        if user.is_apply_staff:
            return True

        if user == author:
            return True

        if user.is_reviewer and review.reviewer_visibility:
            return True

        if user.is_partner and partner_has_access and review.reviewer_visibility and submission.user != user:
            return True

        if user.is_community_reviewer and submission.community_review and review.reviewer_visibility and submission.user != user:
            return True

        return False

    def dispatch(self, request, *args, **kwargs):
        review = self.get_object()

        if review.is_draft:
            return HttpResponseRedirect(reverse_lazy('apply:submissions:reviews:form', args=(review.submission.id,)))

        return super().dispatch(request, *args, **kwargs)


class ReviewOpinionFormView(UserPassesTestMixin, CreateView):
    template_name = 'review/review_detail.html'
    form_class = ReviewOpinionForm
    model = Review
    raise_exception = True

    def get_form_kwargs(self):
        self.object = self.get_object()
        kwargs = super().get_form_kwargs()
        instance = kwargs['instance']
        kwargs['instance'] = instance.opinions.filter(author__reviewer=self.request.user).first()
        return kwargs

    def test_func(self):
        review = self.get_object()
        user = self.request.user
        author = review.author
        submission = review.submission
        partner_has_access = submission.partners.filter(pk=user.pk).exists()

        if user.is_apply_staff:
            return True

        if user == author:
            return False

        if user.is_reviewer and review.reviewer_visibility:
            return True

        if user.is_partner and partner_has_access and review.reviewer_visibility and submission.user != user:
            return True

        if user.is_community_reviewer and submission.community_review and review.reviewer_visibility and submission.user != user:
            return True

        return False

    def form_valid(self, form):
        self.review = self.get_object()
        author, _ = AssignedReviewers.objects.get_or_create_for_user(
            submission=self.review.submission,
            reviewer=self.request.user,
        )
        form.instance.author = author
        form.instance.review = self.review
        response = super().form_valid(form)
        opinion = form.instance

        messenger(
            MESSAGES.REVIEW_OPINION,
            request=self.request,
            user=self.request.user,
            submission=self.review.submission,
            related=opinion,
        )

        if opinion.opinion == DISAGREE:
            return HttpResponseRedirect(reverse_lazy('apply:submissions:reviews:form', args=(self.review.submission.pk,)))
        else:
            return response

    def get_success_url(self):
        return self.review.get_absolute_url()


@method_decorator(login_required, name='dispatch')
class ReviewDetailView(DetailView):
    def get(self, request, *args, **kwargs):
        view = ReviewDisplay.as_view()
        return view(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        view = ReviewOpinionFormView.as_view()
        return view(request, *args, **kwargs)


@method_decorator(staff_required, name='dispatch')
class ReviewListView(ListView):
    model = Review

    def get_queryset(self):
        self.submission = get_object_or_404(ApplicationSubmission, id=self.kwargs['submission_pk'])
        self.queryset = self.model.objects.filter(submission=self.submission, is_draft=False)
        return super().get_queryset()

    def should_display(self, field):
        return not isinstance(field.block, (RecommendationBlock, RecommendationCommentsBlock, RichTextBlock))

    def get_context_data(self, **kwargs):
        review_data = {}

        # Add the header rows
        review_data['title'] = {'question': '', 'answers': list()}
        review_data['opinions'] = {'question': 'Opinions', 'answers': list()}
        review_data['score'] = {'question': 'Overall Score', 'answers': list()}
        review_data['recommendation'] = {'question': 'Recommendation', 'answers': list()}
        review_data['revision'] = {'question': 'Revision', 'answers': list()}
        review_data['comments'] = {'question': 'Comments', 'answers': list()}

        responses = self.object_list.count()
        ordered_reviewers = AssignedReviewers.objects.filter(submission=self.submission).reviewed().review_order()

        reviews = {review.author: review for review in self.object_list}
        for i, reviewer in enumerate(ordered_reviewers):
            review = reviews[reviewer]
            author = '<a href="{}"><span>{}</span></a>'.format(review.get_absolute_url(), review.author)
            if review.author.role:
                author += generate_image_tag(review.author.role.icon, '12x12')
            author = f'<div>{author}</div>'

            review_data['title']['answers'].append(author)
            opinions_template = get_template('review/includes/review_opinions_list.html')
            opinions_html = opinions_template.render({'opinions': review.opinions.select_related('author').all()})
            review_data['opinions']['answers'].append(opinions_html)
            review_data['score']['answers'].append(review.get_score_display)
            review_data['recommendation']['answers'].append(review.get_recommendation_display())
            review_data['comments']['answers'].append(review.get_comments_display(include_question=False))
            if review.for_latest:
                revision = 'Current'
            else:
                revision = '<a href="{}">Compare</a>'.format(review.get_compare_url())
            review_data['revision']['answers'].append(revision)

            for field_id in review.fields:
                field = review.field(field_id)
                data = review.data(field_id)
                if self.should_display(field):
                    question = field.value['field_label']
                    review_data.setdefault(field.id, {'question': question, 'answers': [''] * responses})
                    review_data[field.id]['answers'][i] = field.block.render(None, {'data': data})

        return super().get_context_data(
            submission=self.submission,
            review_data=review_data,
            **kwargs
        )


class ReviewDeleteView(UserPassesTestMixin, DeleteView):
    model = Review
    raise_exception = True

    def test_func(self):
        review = self.get_object()
        return self.request.user.has_perm('review.delete_review') or self.request.user == review.author

    def delete(self, request, *args, **kwargs):
        review = self.get_object()
        messenger(
            MESSAGES.DELETE_REVIEW,
            user=request.user,
            request=request,
            submission=review.submission,
            related=review,
        )
        response = super().delete(request, *args, **kwargs)
        return response

    def get_success_url(self):
        review = self.get_object()
        return reverse_lazy('funds:submissions:detail', args=(review.submission.id,))