from django.contrib import messages
from django.contrib.messages.views import SuccessMessageMixin
from django.core.mail import EmailMessage
from django.db import transaction
from django.http import Http404, HttpResponseRedirect
from django.urls import reverse
from django.utils import timezone
from django.utils.decorators import method_decorator
from django.utils.text import slugify
from django.utils.translation import gettext_lazy as _
from django.views.decorators.http import require_POST
from django.views.generic import (
    CreateView,
    DetailView,
    FormView,
    ListView,
    RedirectView,
    UpdateView,
)

from .forms import (
    DeleteVoteForm,
    ElectionForm,
    OptionForm,
    QuestionForm,
    UploadVotersForm,
    VoterMailForm,
)
from .mixins import (
    AdminOnlyMixin,
    ClosedElectionMixin,
    CreatorOnlyEditMixin,
    CreatorOnlyMixin,
    NotArchivedMixin,
    OpenElectionOnlyMixin,
)
from .models import Election, Option, Question, User, Vote
from .staticdefs import MAIL_VOTE_DELETED, MAIL_VOTERS, QUESTION_TYPES, VOTE_RULES
from .utils import create_users, send_mail

# 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(request, *args, **kwargs)


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


class ElectionCreateView(AdminOnlyMixin, 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(), "question_types": QUESTION_TYPES}
        )
        return super().get_context_data(**kwargs)

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


class ElectionUploadVotersView(CreatorOnlyEditMixin, SuccessMessageMixin, FormView):
    model = Election
    form_class = UploadVotersForm
    success_message = _("Liste de votant·e·s importée avec succès !")
    template_name = "elections/upload_voters.html"

    def get_queryset(self):
        # On ne peut ajouter une liste d'électeurs que sur une élection restreinte
        return super().get_queryset().filter(restricted=True)

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

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context["voters"] = self.object.registered_voters.all()
        return context

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

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

    def form_valid(self, form):
        # On crée les comptes nécessaires à l'élection, en supprimant ceux
        # existant déjà pour ne pas avoir de doublons
        self.object.registered_voters.all().delete()
        create_users(self.object, form.cleaned_data["csv_file"])
        return super().form_valid(form)


class ElectionMailVotersView(CreatorOnlyEditMixin, SuccessMessageMixin, FormView):
    model = Election
    form_class = VoterMailForm
    success_message = _("Mail d'annonce envoyé avec succès !")
    template_name = "elections/mail_voters.html"

    def get_queryset(self):
        # On ne peut envoyer un mail que sur une élection restreinte qui n'a pas
        # déjà vu son mail envoyé
        return super().get_queryset().filter(restricted=True, sent_mail=False)

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

    def get_initial(self):
        return {"objet": f"Vote : {self.object.name}", "message": MAIL_VOTERS}

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

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

    def form_valid(self, form):
        self.object.sent_mail = True
        send_mail(self.object, form)
        self.object.save()
        return super().form_valid(form)


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 DeleteVoteView(ClosedElectionMixin, FormView):
    model = Election
    template_name = "elections/delete_vote.html"
    form_class = DeleteVoteForm

    def get_success_url(self):
        return reverse("election.voters", args=[self.object.pk]) + "#v_{anchor}".format(
            **self.kwargs
        )

    def get_form_kwargs(self):
        kwargs = super().get_form_kwargs()
        kwargs["voter"] = self.voter
        return kwargs

    def get_queryset(self):
        # On n'affiche la page que pour les élections ouvertes à toustes
        return super().get_queryset().filter(restricted=False)

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context["anchor"] = self.kwargs["anchor"]
        return context

    def get(self, request, *args, **kwargs):
        self.object = super().get_object()
        self.voter = User.objects.get(pk=self.kwargs["user_pk"])
        return super().get(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        self.object = super().get_object()
        self.voter = User.objects.get(pk=self.kwargs["user_pk"])
        return super().post(request, *args, **kwargs)

    @transaction.atomic
    def form_valid(self, form):
        if form.cleaned_data["delete"] == "oui":
            # On envoie un mail à la personne lui indiquant que le vote est supprimé
            EmailMessage(
                subject="Vote removed",
                body=MAIL_VOTE_DELETED.format(
                    full_name=self.voter.full_name,
                    election_name=self.object.name,
                ),
                to=[self.voter.email],
            ).send()

            # On supprime les votes
            Vote.objects.filter(
                user=self.voter,
                option__question__election=self.object,
            ).delete()

            # On marque les questions comme non votées
            self.voter.cast_elections.remove(self.object)
            self.voter.cast_questions.remove(*list(self.object.questions.all()))

        return super().form_valid(form)


class ElectionTallyView(ClosedElectionMixin, 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(tallied=False)
            .prefetch_related("questions__options")
        )

    def get(self, request, *args, **kwargs):
        election = self.get_object()
        for q in election.questions.all():
            q.tally()

        election.tallied = True
        election.save()
        return super().get(request, *args, **kwargs)


class ElectionChangePublicationView(ClosedElectionMixin, 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(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(ClosedElectionMixin, BackgroundUpdateView):
    model = Election
    pattern_name = "election.admin"
    success_message = _("Élection archivée avec succès !")

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


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


@method_decorator(require_POST, name="dispatch")
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
# #############################################################################


@method_decorator(require_POST, name="dispatch")
class AddOptionView(CreatorOnlyEditMixin, CreateView):
    model = Question
    form_class = OptionForm

    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)


# #############################################################################
#  Public Views
# #############################################################################


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


class ElectionView(NotArchivedMixin, 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()

        if user.is_authenticated:
            context["can_vote"] = user.can_vote(self.request, context["election"])
            context["cast_questions"] = user.cast_questions.all()
            context["has_voted"] = user.cast_elections.filter(
                pk=context["election"].pk
            ).exists()

        return context

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


class ElectionVotersView(NotArchivedMixin, DetailView):
    model = Election
    template_name = "elections/election_voters.html"

    def get_context_data(self, **kwargs):
        user = self.request.user
        context = super().get_context_data(**kwargs)
        election = context["election"]

        if user.is_authenticated:
            context["can_delete"] = (
                not election.restricted
                and election.created_by == user
                and election.end_date < timezone.now()
                and not election.tallied
            )
        return context


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
            # On enregistre le vote pour l'élection
            self.object.election.voters.add(self.request.user)
            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_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        questions = list(self.object.election.questions.all())
        context["q_index"] = questions.index(self.object) + 1
        context["q_total"] = len(questions)
        context["vote_rule"] = VOTE_RULES[self.object.type]
        return context

    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(self.request, question.election):
            raise Http404
        return question

    def get(self, request, *args, **kwargs):
        self.object = self.get_object()
        vote_form = self.object.get_formset()(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 = self.object.get_formset()(self.request.POST, instance=self.object)

        if self.object.is_form_valid(vote_form):
            # On enregistre le vote
            self.object.cast_ballot(self.request.user, vote_form)
            self.object.voters.add(self.request.user)
            messages.success(self.request, _("Votre choix a bien été enregistré !"))

            return HttpResponseRedirect(self.get_success_url())
        else:
            return self.render_to_response(self.get_context_data(formset=vote_form))
