from django.contrib import messages
from django.contrib.messages.views import SuccessMessageMixin

# from django.db.models import Count, Prefetch
from django.http import Http404, HttpResponseRedirect
from django.urls import reverse
from django.utils import timezone
from django.utils.text import slugify
from django.utils.translation import gettext_lazy as _
from django.views.generic import (
    CreateView,
    DetailView,
    FormView,
    ListView,
    RedirectView,
    UpdateView,
)

from .forms import (
    ElectionForm,
    OptionForm,
    OptionFormSet,
    QuestionForm,
    UploadVotersForm,
)
from .mixins import CreatorOnlyEditMixin, CreatorOnlyMixin, OpenElectionOnlyMixin
from .models import Election, Option, Question

# TODO: access control *everywhere*

# #############################################################################
#  Utils Views
# #############################################################################


class BackgroundUpdateView(RedirectView):
    success_message = ""

    def get_success_message(self):
        return self.success_message

    def get(self, request, *args, **kwargs):
        success_message = self.get_success_message()
        if success_message:
            messages.success(self.request, success_message)
        return super().get(self, request, *args, **kwargs)


# #############################################################################
#  Administration Views
# #############################################################################


class ElectionCreateView(SuccessMessageMixin, CreateView):
    model = Election
    form_class = ElectionForm
    success_message = _("Élection créée avec succès !")
    template_name = "elections/election_create.html"

    def get_success_url(self):
        return reverse("election.admin", args=[self.object.pk])

    def form_valid(self, form):
        # We need to add the short name and the creator od the election
        form.instance.short_name = slugify(
            form.instance.start_date.strftime("%Y-%m-%d") + "_" + form.instance.name
        )[:50]
        # TODO: Change this if we modify the user model
        form.instance.created_by = self.request.user
        return super().form_valid(form)


class ElectionAdminView(CreatorOnlyMixin, DetailView):
    model = Election
    template_name = "elections/election_admin.html"

    def get_next_url(self):
        return reverse("election.view", args=[self.object.pk])

    def get_context_data(self, **kwargs):
        kwargs.update({"current_time": timezone.now()})
        return super().get_context_data(**kwargs)

    def get_queryset(self):
        return super().get_queryset().prefetch_related("questions__options")


class ElectionUploadVotersView(CreatorOnlyEditMixin, FormView):
    model = Election
    form_class = UploadVotersForm
    template_name = "elections/upload_voters.html"

    def get(self, request, *args, **kwargs):
        self.object = self.get_object()
        return super().get(request, *args, **kwargs)

    def form_valid(self, form):
        pass


class ElectionListView(CreatorOnlyMixin, ListView):
    model = Election
    template_name = "elections/election_list.html"


class ElectionUpdateView(CreatorOnlyEditMixin, SuccessMessageMixin, UpdateView):
    model = Election
    form_class = ElectionForm
    success_message = _("Élection modifiée avec succès !")
    template_name = "elections/election_update.html"

    def get_success_url(self):
        return reverse("election.admin", args=[self.object.pk])


class ElectionTallyView(CreatorOnlyEditMixin, BackgroundUpdateView):
    model = Election
    pattern_name = "election.admin"
    success_message = _("Élection dépouillée avec succès !")

    def get_queryset(self):
        return (
            super()
            .get_queryset()
            .filter(end_date__lt=timezone.now())
            .prefetch_related("questions__options")
        )

    def get(self, request, *args, **kwargs):
        election = self.get_object()
        options, questions = [], []
        for q in election.questions.all():
            max_votes = 0
            for o in q.options.all():
                o.nb_votes = o.voters.count()
                max_votes = max(max_votes, o.nb_votes)
                options.append(o)

            q.max_votes = max_votes
            questions.append(q)

        Option.objects.bulk_update(options, ["nb_votes"])
        Question.objects.bulk_update(questions, ["max_votes"])
        election.tallied = True
        election.save()
        return super().get(request, *args, **kwargs)


class ElectionChangePublicationView(CreatorOnlyEditMixin, BackgroundUpdateView):
    model = Election
    pattern_name = "election.admin"

    def get_success_message(self):
        if self.election.results_public:
            return _("Élection publiée avec succès !")
        return _("Élection dépubliée avec succès !")

    def get_queryset(self):
        return super().get_queryset().filter(end_date__lt=timezone.now())

    def get(self, request, *args, **kwargs):
        self.election = self.get_object()
        self.election.results_public = not self.election.results_public
        self.election.save()
        return super().get(request, *args, **kwargs)


class ElectionArchiveView(CreatorOnlyEditMixin, BackgroundUpdateView):
    model = Election
    pattern_name = "election.admin"
    success_message = _("Élection archivée avec succès !")

    def get_queryset(self):
        return super().get_queryset().filter(end_date__lt=timezone.now())

    def get(self, request, *args, **kwargs):
        election = self.get_object()
        election.archived = True
        election.save()
        return super().get(request, *args, **kwargs)


# #############################################################################
#  Question Views
# #############################################################################


class AddQuestionView(CreatorOnlyEditMixin, CreateView):
    model = Election
    form_class = QuestionForm

    def get_success_url(self):
        return reverse("election.admin", args=[self.election.pk]) + "#q_add"

    def form_valid(self, form):
        self.election = self.get_object()
        # On ajoute l'élection voulue à la question créée
        form.instance.election = self.election
        return super().form_valid(form)


class ModQuestionView(CreatorOnlyEditMixin, SuccessMessageMixin, UpdateView):
    model = Question
    form_class = QuestionForm
    success_message = _("Question modifiée avec succès !")
    template_name = "elections/question_update.html"

    def get_success_url(self):
        return (
            reverse("election.admin", args=[self.object.election.pk])
            + f"#q_{self.object.pk}"
        )


class DelQuestionView(CreatorOnlyEditMixin, BackgroundUpdateView):
    model = Question
    success_message = _("Question supprimée !")

    def get_redirect_url(self, *args, **kwargs):
        return reverse("election.admin", args=[self.election.pk]) + "#q_add"

    def get(self, request, *args, **kwargs):
        question = self.get_object()
        self.election = question.election
        question.delete()
        return super().get(request, *args, **kwargs)


# #############################################################################
#  Option Views
# #############################################################################


class AddOptionView(CreatorOnlyEditMixin, CreateView):
    model = Question
    form_class = OptionForm

    def get_queryset(self):
        return super().get_queryset()

    def get_success_url(self):
        return (
            reverse("election.admin", args=[self.question.election.pk])
            + f"#q_{self.question.pk}"
        )

    def form_valid(self, form):
        self.question = self.get_object()
        # On ajoute l'élection voulue à la question créée
        form.instance.question = self.question
        return super().form_valid(form)


class ModOptionView(CreatorOnlyEditMixin, SuccessMessageMixin, UpdateView):
    model = Option
    form_class = OptionForm
    success_message = _("Option modifiée avec succès !")
    template_name = "elections/option_update.html"

    def get_success_url(self):
        return (
            reverse("election.admin", args=[self.object.question.election.pk])
            + f"#o_{self.object.pk}"
        )


class DelOptionView(CreatorOnlyEditMixin, BackgroundUpdateView):
    model = Option
    success_message = _("Option supprimée !")

    def get_redirect_url(self, *args, **kwargs):
        return reverse("election.admin", args=[self.election.pk]) + "#q_add"

    def get(self, request, *args, **kwargs):
        option = self.get_object()
        self.election = option.question.election
        option.delete()
        return super().get(request, *args, **kwargs)


# #############################################################################
#  Common Views
# #############################################################################


class ElectionView(DetailView):
    model = Election
    template_name = "elections/election.html"

    def get_next_url(self):
        return self.request.path

    def get_context_data(self, **kwargs):
        user = self.request.user
        context = super().get_context_data(**kwargs)
        context["current_time"] = timezone.now()
        context["can_vote"] = user.is_authenticated and user.can_vote(
            context["election"]
        )
        return context

    def get_queryset(self):
        return (
            super()
            .get_queryset()
            .filter(archived=False)
            .prefetch_related("questions__options")
        )


class VoteView(OpenElectionOnlyMixin, DetailView):
    model = Question
    template_name = "elections/vote.html"

    def get_next_url(self):
        return reverse("election.view", args=[self.object.election.pk])

    def get_success_url(self):
        questions = list(self.object.election.questions.all())
        q_index = questions.index(self.object)
        if q_index + 1 == len(questions):
            # On était à la dernière question
            return reverse("election.view", args=[self.object.election.pk])

        # On récupère l'id de la prochaine question
        q_next = questions[q_index + 1].pk

        return reverse("election.vote", args=[q_next])

    def get_object(self):
        question = super().get_object()
        # Seulement les utilisateur·ice·s ayant le droit de voter dans l'élection
        # peuvent voir la page
        if not self.request.user.can_vote(question.election):
            raise Http404
        return question

    def get(self, request, *args, **kwargs):
        self.object = self.get_object()
        vote_form = OptionFormSet(instance=self.object)

        return self.render_to_response(self.get_context_data(formset=vote_form))

    def post(self, request, *args, **kwargs):
        self.object = self.get_object()
        vote_form = OptionFormSet(self.request.POST, instance=self.object)

        # We record the cast votes
        for v in vote_form:
            v.record_vote(self.request.user)

        messages.success(self.request, _("Votre choix a bien été enregistré !"))

        return HttpResponseRedirect(self.get_success_url())
