import hashlib
import json
import random
import time
from collections import defaultdict

from custommail.models import CustomMail
from custommail.shortcuts import send_custom_mail, send_mass_custom_mail
from django.conf import settings
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.core import serializers
from django.core.exceptions import NON_FIELD_ERRORS
from django.core.urlresolvers import reverse
from django.db import transaction
from django.db.models import Count, Prefetch
from django.forms.models import inlineformset_factory
from django.http import HttpResponseBadRequest, HttpResponseRedirect, JsonResponse
from django.shortcuts import get_object_or_404, render
from django.template.defaultfilters import pluralize
from django.utils import formats, timezone
from django.views.generic.list import ListView

from bda.algorithm import Algorithm
from bda.forms import (
    AnnulForm,
    InscriptionInlineFormSet,
    InscriptionReventeForm,
    ResellForm,
    ReventeTirageAnnulForm,
    ReventeTirageForm,
    SoldForm,
    TokenForm,
)
from bda.models import (
    Attribution,
    CategorieSpectacle,
    ChoixSpectacle,
    Participant,
    Salle,
    Spectacle,
    SpectacleRevente,
    Tirage,
)
from gestioncof.decorators import buro_required, cof_required
from utils.views.autocomplete import Select2QuerySetView


@cof_required
def etat_places(request, tirage_id):
    """
    Résumé des spectacles d'un tirage avec pour chaque spectacle :
    - Le nombre de places en jeu
    - Le nombre de demandes
    - Le ratio demandes/places
    Et le total de toutes les demandes
    """
    tirage = get_object_or_404(Tirage, id=tirage_id)

    spectacles = tirage.spectacle_set.select_related("location")
    spectacles_dict = {}  # index of spectacle by id

    for spectacle in spectacles:
        spectacle.total = 0  # init total requests
        spectacles_dict[spectacle.id] = spectacle

    choices = (
        ChoixSpectacle.objects.filter(spectacle__in=spectacles)
        .values("spectacle")
        .annotate(total=Count("spectacle"))
    )

    # choices *by spectacles* whose only 1 place is requested
    choices1 = choices.filter(double_choice="1")
    # choices *by spectacles* whose 2 places is requested
    choices2 = choices.exclude(double_choice="1")

    for spectacle in choices1:
        pk = spectacle["spectacle"]
        spectacles_dict[pk].total += spectacle["total"]
    for spectacle in choices2:
        pk = spectacle["spectacle"]
        spectacles_dict[pk].total += 2 * spectacle["total"]

    # here, each spectacle.total contains the number of requests

    slots = 0  # proposed slots
    total = 0  # requests
    for spectacle in spectacles:
        slots += spectacle.slots
        total += spectacle.total
        spectacle.ratio = spectacle.total / spectacle.slots

    context = {
        "proposed": slots,
        "spectacles": spectacles,
        "total": total,
        "tirage": tirage,
    }
    return render(request, "bda/etat-places.html", context)


def _hash_queryset(queryset):
    data = serializers.serialize("json", queryset).encode("utf-8")
    hasher = hashlib.sha256()
    hasher.update(data)
    return hasher.hexdigest()


@cof_required
def places(request, tirage_id):
    tirage = get_object_or_404(Tirage, id=tirage_id)
    participant, _ = Participant.objects.get_or_create(user=request.user, tirage=tirage)
    places = participant.attribution_set.order_by(
        "spectacle__date", "spectacle"
    ).select_related("spectacle", "spectacle__location")
    total = sum(place.spectacle.price for place in places)
    filtered_places = []
    places_dict = {}
    spectacles = []
    dates = []
    warning = False
    for place in places:
        if place.spectacle in spectacles:
            places_dict[place.spectacle].double = True
        else:
            place.double = False
            places_dict[place.spectacle] = place
            spectacles.append(place.spectacle)
            filtered_places.append(place)
            date = place.spectacle.date.date()
            if date in dates:
                warning = True
            else:
                dates.append(date)
    # On prévient l'utilisateur s'il a deux places à la même date
    if warning:
        messages.warning(
            request,
            "Attention, vous avez reçu des places pour "
            "des spectacles différents à la même date.",
        )
    return render(
        request,
        "bda/resume_places.html",
        {
            "participant": participant,
            "places": filtered_places,
            "tirage": tirage,
            "total": total,
        },
    )


@cof_required
def inscription(request, tirage_id):
    """
    Vue d'inscription à un tirage BdA.
    - On vérifie qu'on se situe bien entre la date d'ouverture et la date de
      fermeture des inscriptions.
    - On vérifie que l'inscription n'a pas été modifiée entre le moment où le
      client demande le formulaire et le moment où il soumet son inscription
      (autre session par exemple).
    """
    tirage = get_object_or_404(Tirage, id=tirage_id)
    if timezone.now() < tirage.ouverture:
        # Le tirage n'est pas encore ouvert.
        opening = formats.localize(timezone.template_localtime(tirage.ouverture))
        messages.error(
            request,
            "Le tirage n'est pas encore ouvert : " "ouverture le {:s}".format(opening),
        )
        return render(request, "bda/resume-inscription-tirage.html", {})

    participant, _ = Participant.objects.select_related("tirage").get_or_create(
        user=request.user, tirage=tirage
    )

    if timezone.now() > tirage.fermeture:
        # Le tirage est fermé.
        choices = participant.choixspectacle_set.order_by("priority")
        messages.error(request, " C'est fini : tirage au sort dans la journée !")
        return render(
            request, "bda/resume-inscription-tirage.html", {"choices": choices}
        )

    BdaFormSet = inlineformset_factory(
        Participant,
        ChoixSpectacle,
        fields=("spectacle", "double_choice", "priority"),
        formset=InscriptionInlineFormSet,
        error_messages={
            NON_FIELD_ERRORS: {
                "unique_together": "Vous avez déjà demandé ce voeu plus haut !"
            }
        },
    )

    if request.method == "POST":
        # use *this* queryset
        dbstate = _hash_queryset(participant.choixspectacle_set.all())
        if "dbstate" in request.POST and dbstate != request.POST["dbstate"]:
            formset = BdaFormSet(instance=participant)
            messages.error(
                request,
                "Impossible d'enregistrer vos modifications "
                ": vous avez apporté d'autres modifications "
                "entre temps.",
            )
        else:
            formset = BdaFormSet(request.POST, instance=participant)
            if formset.is_valid():
                formset.save()
                formset = BdaFormSet(instance=participant)
                messages.success(
                    request, "Votre inscription a été mise à jour avec succès !"
                )
            else:
                messages.error(
                    request,
                    "Une erreur s'est produite lors de l'enregistrement de vos vœux.  "
                    "Avez-vous demandé plusieurs fois le même spectacle ?",
                )
    else:
        formset = BdaFormSet(instance=participant)
    # use *this* queryset
    dbstate = _hash_queryset(participant.choixspectacle_set.all())
    total_price = 0
    choices = participant.choixspectacle_set.select_related("spectacle")
    for choice in choices:
        total_price += choice.spectacle.price
        if choice.double:
            total_price += choice.spectacle.price
    return render(
        request,
        "bda/inscription-tirage.html",
        {
            "formset": formset,
            "total_price": total_price,
            "dbstate": dbstate,
            "tirage": tirage,
        },
    )


def do_tirage(tirage_elt, token):
    """
    Fonction auxiliaire à la vue ``tirage`` qui lance effectivement le tirage
    après qu'on a vérifié que c'est légitime et que le token donné en argument
    est correct.
    Rend les résultats
    """
    # Initialisation du dictionnaire data qui va contenir les résultats
    start = time.time()
    data = {
        "shows": tirage_elt.spectacle_set.select_related("location"),
        "token": token,
        "members": tirage_elt.participant_set.select_related("user"),
        "total_slots": 0,
        "total_losers": 0,
        "total_sold": 0,
        "total_deficit": 0,
        "opera_deficit": 0,
    }

    # On lance le tirage
    choices = (
        ChoixSpectacle.objects.filter(spectacle__tirage=tirage_elt)
        .order_by("participant", "priority")
        .select_related("participant", "participant__user", "spectacle")
    )
    results = Algorithm(data["shows"], data["members"], choices)(token)

    # On compte les places attribuées et les déçus
    for (_, members, losers) in results:
        data["total_slots"] += len(members)
        data["total_losers"] += len(losers)

    # On calcule le déficit et les bénéfices pour le BdA
    # FIXME: le traitement de l'opéra est sale
    for (show, members, _) in results:
        deficit = (show.slots - len(members)) * show.price
        data["total_sold"] += show.slots * show.price
        if deficit >= 0:
            if "Opéra" in show.location.name:
                data["opera_deficit"] += deficit
            data["total_deficit"] += deficit
    data["total_sold"] -= data["total_deficit"]

    # Participant objects are not shared accross spectacle results,
    # so assign a single object for each Participant id
    members_uniq = {}
    members2 = {}
    for (show, members, _) in results:
        for (member, _, _, _) in members:
            if member.id not in members_uniq:
                members_uniq[member.id] = member
                members2[member] = []
                member.total = 0
            member = members_uniq[member.id]
            members2[member].append(show)
            member.total += show.price
    members2 = members2.items()
    data["members2"] = sorted(members2, key=lambda m: m[0].user.last_name)

    # ---
    # À partir d'ici, le tirage devient effectif
    # ---

    # On suppression les vieilles attributions, on sauvegarde le token et on
    # désactive le tirage
    Attribution.objects.filter(spectacle__tirage=tirage_elt).delete()
    tirage_elt.tokens += '{:s}\n"""{:s}"""\n'.format(
        timezone.now().strftime("%y-%m-%d %H:%M:%S"), token
    )
    tirage_elt.enable_do_tirage = False
    tirage_elt.save()

    # On enregistre les nouvelles attributions
    Attribution.objects.bulk_create(
        [
            Attribution(spectacle=show, participant=member)
            for show, members, _ in results
            for member, _, _, _ in members
        ]
    )

    # On inscrit à BdA-Revente ceux qui n'ont pas eu les places voulues
    ChoixRevente = Participant.choicesrevente.through

    # Suppression des reventes demandées/enregistrées
    # (si le tirage est relancé)
    (ChoixRevente.objects.filter(spectacle__tirage=tirage_elt).delete())
    (
        SpectacleRevente.objects.filter(
            attribution__spectacle__tirage=tirage_elt
        ).delete()
    )

    lost_by = defaultdict(set)
    for show, _, losers in results:
        for loser, _, _, _ in losers:
            lost_by[loser].add(show)

    ChoixRevente.objects.bulk_create(
        ChoixRevente(participant=member, spectacle=show)
        for member, shows in lost_by.items()
        for show in shows
    )

    data["duration"] = time.time() - start
    data["results"] = results
    return data


@buro_required
def tirage(request, tirage_id):
    tirage_elt = get_object_or_404(Tirage, id=tirage_id)
    if not (tirage_elt.enable_do_tirage and tirage_elt.fermeture < timezone.now()):
        return render(request, "tirage-failed.html", {"tirage": tirage_elt})
    if request.POST:
        form = TokenForm(request.POST)
        if form.is_valid():
            results = do_tirage(tirage_elt, form.cleaned_data["token"])
            return render(request, "bda-attrib-extra.html", results)
    else:
        form = TokenForm()
    return render(request, "bda-token.html", {"form": form})


@login_required
def revente_manage(request, tirage_id):
    """
    Gestion de ses propres reventes :
    - Création d'une revente
    - Annulation d'une revente
    - Confirmation d'une revente = transfert de la place à la personne qui
      rachète
    - Annulation d'une revente après que le tirage a eu lieu
    """
    tirage = get_object_or_404(Tirage, id=tirage_id)
    participant, created = Participant.objects.get_or_create(
        user=request.user, tirage=tirage
    )

    if not participant.paid:
        return render(request, "bda/revente/notpaid.html", {})

    resellform = ResellForm(participant, prefix="resell")
    annulform = AnnulForm(participant, prefix="annul")
    soldform = SoldForm(participant, prefix="sold")

    if request.method == "POST":
        # On met en vente une place
        if "resell" in request.POST:
            resellform = ResellForm(participant, request.POST, prefix="resell")
            if resellform.is_valid():
                datatuple = []
                attributions = resellform.cleaned_data["attributions"]
                with transaction.atomic():
                    for attribution in attributions:
                        revente, created = SpectacleRevente.objects.get_or_create(
                            attribution=attribution, defaults={"seller": participant}
                        )
                        if not created:
                            revente.reset()

                        context = {
                            "vendeur": participant.user,
                            "show": attribution.spectacle,
                            "revente": revente,
                        }
                        datatuple.append(
                            (
                                "bda-revente-new",
                                context,
                                settings.MAIL_DATA["revente"]["FROM"],
                                [participant.user.email],
                            )
                        )
                        revente.save()
                    send_mass_custom_mail(datatuple)
        # On annule une revente
        elif "annul" in request.POST:
            annulform = AnnulForm(participant, request.POST, prefix="annul")
            if annulform.is_valid():
                reventes = annulform.cleaned_data["reventes"]
                for revente in reventes:
                    revente.delete()
        # On confirme une vente en transférant la place à la personne qui a
        # gagné le tirage
        elif "transfer" in request.POST:
            soldform = SoldForm(participant, request.POST, prefix="sold")
            if soldform.is_valid():
                reventes = soldform.cleaned_data["reventes"]
                for revente in reventes:
                    revente.attribution.participant = revente.soldTo
                    revente.attribution.save()

        # On annule la revente après le tirage au sort (par exemple si
        # la personne qui a gagné le tirage ne se manifeste pas). La place est
        # alors remise en vente
        elif "reinit" in request.POST:
            soldform = SoldForm(participant, request.POST, prefix="sold")
            if soldform.is_valid():
                reventes = soldform.cleaned_data["reventes"]
                for revente in reventes:
                    if revente.attribution.spectacle.date > timezone.now():
                        # On antidate pour envoyer le mail plus vite
                        new_date = timezone.now() - SpectacleRevente.remorse_time
                        revente.reset(new_date=new_date)

    sold_exists = soldform.fields["reventes"].queryset.exists()
    annul_exists = annulform.fields["reventes"].queryset.exists()
    resell_exists = resellform.fields["attributions"].queryset.exists()

    return render(
        request,
        "bda/revente/manage.html",
        {
            "tirage": tirage,
            "soldform": soldform,
            "annulform": annulform,
            "resellform": resellform,
            "sold_exists": sold_exists,
            "annul_exists": annul_exists,
            "resell_exists": resell_exists,
        },
    )


@login_required
def revente_tirages(request, tirage_id):
    """
    Affiche à un participant la liste de toutes les reventes en cours (pour un
    tirage donné) et lui permet de s'inscrire et se désinscrire à ces reventes.
    """
    tirage = get_object_or_404(Tirage, id=tirage_id)
    participant, _ = Participant.objects.get_or_create(user=request.user, tirage=tirage)
    subform = ReventeTirageForm(participant, prefix="subscribe")
    annulform = ReventeTirageAnnulForm(participant, prefix="annul")

    if request.method == "POST":
        if "subscribe" in request.POST:
            subform = ReventeTirageForm(participant, request.POST, prefix="subscribe")
            if subform.is_valid():
                reventes = subform.cleaned_data["reventes"]
                count = reventes.count()
                for revente in reventes:
                    revente.confirmed_entry.add(participant)
                if count > 0:
                    messages.success(
                        request,
                        "Tu as bien été inscrit à {} revente{}".format(
                            count, pluralize(count)
                        ),
                    )
        elif "annul" in request.POST:
            annulform = ReventeTirageAnnulForm(
                participant, request.POST, prefix="annul"
            )
            if annulform.is_valid():
                reventes = annulform.cleaned_data["reventes"]
                count = reventes.count()
                for revente in reventes:
                    revente.confirmed_entry.remove(participant)
                if count > 0:
                    messages.success(
                        request,
                        "Tu as bien été désinscrit de {} revente{}".format(
                            count, pluralize(count)
                        ),
                    )

    annul_exists = annulform.fields["reventes"].queryset.exists()
    sub_exists = subform.fields["reventes"].queryset.exists()

    return render(
        request,
        "bda/revente/tirages.html",
        {
            "annulform": annulform,
            "subform": subform,
            "annul_exists": annul_exists,
            "sub_exists": sub_exists,
        },
    )


@login_required
def revente_confirm(request, revente_id):
    revente = get_object_or_404(SpectacleRevente, id=revente_id)
    participant, _ = Participant.objects.get_or_create(
        user=request.user, tirage=revente.attribution.spectacle.tirage
    )
    if not revente.notif_sent or revente.shotgun:
        return render(request, "bda/revente/wrongtime.html", {"revente": revente})

    revente.confirmed_entry.add(participant)
    return render(
        request,
        "bda/revente/confirmed.html",
        {"spectacle": revente.attribution.spectacle, "date": revente.date_tirage},
    )


@login_required
def revente_subscribe(request, tirage_id):
    """
    Permet à un participant de sélectionner ses préférences pour les reventes.
    Il recevra des notifications pour les spectacles qui l'intéressent et il
    est automatiquement inscrit aux reventes en cours au moment où il ajoute un
    spectacle à la liste des spectacles qui l'intéressent.
    """
    tirage = get_object_or_404(Tirage, id=tirage_id)
    participant, _ = Participant.objects.get_or_create(user=request.user, tirage=tirage)
    deja_revente = False
    success = False
    inscrit_revente = []
    if request.method == "POST":
        form = InscriptionReventeForm(tirage, request.POST)
        if form.is_valid():
            choices = form.cleaned_data["spectacles"]
            participant.choicesrevente = choices
            participant.save()
            for spectacle in choices:
                qset = SpectacleRevente.objects.filter(attribution__spectacle=spectacle)
                if qset.filter(shotgun=True, soldTo__isnull=True).exists():
                    # Une place est disponible au shotgun, on suggère à
                    # l'utilisateur d'aller la récupérer
                    deja_revente = True
                else:
                    # La place n'est pas disponible au shotgun, si des reventes
                    # pour ce spectacle existent déjà, on inscrit la personne à
                    # la revente ayant le moins d'inscrits
                    min_resell = (
                        qset.filter(shotgun=False)
                        .annotate(nb_subscribers=Count("confirmed_entry"))
                        .order_by("nb_subscribers")
                        .first()
                    )
                    if min_resell is not None:
                        min_resell.confirmed_entry.add(participant)
                        inscrit_revente.append(spectacle)
            success = True
    else:
        form = InscriptionReventeForm(
            tirage, initial={"spectacles": participant.choicesrevente.all()}
        )
    # Messages
    if success:
        messages.success(request, "Votre inscription a bien été prise en compte")
    if deja_revente:
        messages.info(
            request,
            "Des reventes existent déjà pour certains de "
            "ces spectacles, vérifiez les places "
            "disponibles sans tirage !",
        )
    if inscrit_revente:
        shows = map("<li>{!s}</li>".format, inscrit_revente)
        msg = (
            "Vous avez été inscrit·e à des reventes en cours pour les spectacles "
            "<ul>{:s}</ul>".format("\n".join(shows))
        )
        messages.info(request, msg, extra_tags="safe")

    return render(request, "bda/revente/subscribe.html", {"form": form})


@login_required
def revente_buy(request, spectacle_id):
    spectacle = get_object_or_404(Spectacle, id=spectacle_id)
    tirage = spectacle.tirage
    participant, _ = Participant.objects.get_or_create(user=request.user, tirage=tirage)
    reventes = SpectacleRevente.objects.filter(
        attribution__spectacle=spectacle, soldTo__isnull=True
    )

    # Si l'utilisateur veut racheter une place qu'il est en train de revendre,
    # on supprime la revente en question.
    own_reventes = reventes.filter(seller=participant)
    if len(own_reventes) > 0:
        own_reventes[0].delete()
        return HttpResponseRedirect(reverse("bda-revente-shotgun", args=[tirage.id]))

    reventes_shotgun = reventes.filter(shotgun=True)

    if not reventes_shotgun:
        return render(request, "bda/revente/none.html", {})

    if request.POST:
        revente = random.choice(reventes_shotgun)
        revente.soldTo = participant
        revente.save()
        context = {
            "show": spectacle,
            "acheteur": request.user,
            "vendeur": revente.seller.user,
        }
        send_custom_mail(
            "bda-buy-shotgun",
            "bda@ens.fr",
            [revente.seller.user.email],
            context=context,
        )
        return render(
            request,
            "bda/revente/mail-success.html",
            {"seller": revente.attribution.participant.user, "spectacle": spectacle},
        )

    return render(
        request,
        "bda/revente/confirm-shotgun.html",
        {"spectacle": spectacle, "user": request.user},
    )


@login_required
def revente_shotgun(request, tirage_id):
    tirage = get_object_or_404(Tirage, id=tirage_id)
    spectacles = (
        tirage.spectacle_set.filter(date__gte=timezone.now())
        .select_related("location")
        .prefetch_related(
            Prefetch(
                "attribues",
                queryset=(
                    Attribution.objects.filter(
                        revente__shotgun=True, revente__soldTo__isnull=True
                    )
                ),
                to_attr="shotguns",
            )
        )
    )
    shotgun = [sp for sp in spectacles if len(sp.shotguns) > 0]

    return render(request, "bda/revente/shotgun.html", {"spectacles": shotgun})


@buro_required
def spectacle(request, tirage_id, spectacle_id):
    tirage = get_object_or_404(Tirage, id=tirage_id)
    spectacle = get_object_or_404(Spectacle, id=spectacle_id, tirage=tirage)
    attributions = spectacle.attribues.select_related(
        "participant", "participant__user"
    )
    participants = {}
    for attrib in attributions:
        participant = attrib.participant
        participant_info = {
            "lastname": participant.user.last_name,
            "name": participant.user.get_full_name,
            "username": participant.user.username,
            "email": participant.user.email,
            "given": int(attrib.given),
            "paid": participant.paid,
            "nb_places": 1,
        }
        if participant.id in participants:
            participants[participant.id]["nb_places"] += 1
            participants[participant.id]["given"] += attrib.given
        else:
            participants[participant.id] = participant_info

    participants_info = sorted(participants.values(), key=lambda part: part["lastname"])
    return render(
        request,
        "bda/participants.html",
        {"spectacle": spectacle, "participants": participants_info},
    )


class SpectacleListView(ListView):
    model = Spectacle
    template_name = "spectacle_list.html"

    def get_queryset(self):
        self.tirage = get_object_or_404(Tirage, id=self.kwargs["tirage_id"])
        categories = self.tirage.spectacle_set.select_related("location")
        return categories

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context["tirage_id"] = self.tirage.id
        context["tirage_name"] = self.tirage.title
        return context


@buro_required
def unpaid(request, tirage_id):
    tirage = get_object_or_404(Tirage, id=tirage_id)
    unpaid = (
        tirage.participant_set.annotate(nb_attributions=Count("attribution"))
        .filter(paid=False, nb_attributions__gt=0)
        .select_related("user")
    )
    return render(request, "bda-unpaid.html", {"unpaid": unpaid})


@buro_required
def send_rappel(request, spectacle_id):
    show = get_object_or_404(Spectacle, id=spectacle_id)
    # Mails d'exemples
    custommail = CustomMail.objects.get(shortname="bda-rappel")
    exemple_mail_1place = custommail.render(
        {"member": request.user, "show": show, "nb_attr": 1}
    )
    exemple_mail_2places = custommail.render(
        {"member": request.user, "show": show, "nb_attr": 2}
    )
    # Contexte
    ctxt = {
        "show": show,
        "exemple_mail_1place": exemple_mail_1place,
        "exemple_mail_2places": exemple_mail_2places,
        "custommail": custommail,
    }
    # Envoi confirmé
    if request.method == "POST":
        members = show.send_rappel()
        ctxt["sent"] = True
        ctxt["members"] = members
    # Demande de confirmation
    else:
        ctxt["sent"] = False
        if show.rappel_sent:
            messages.warning(
                request,
                "Attention, un mail de rappel pour ce spectale a déjà été "
                "envoyé le {}".format(
                    formats.localize(timezone.template_localtime(show.rappel_sent))
                ),
            )
    return render(request, "bda/mails-rappel.html", ctxt)


def descriptions_spectacles(request, tirage_id):
    tirage = get_object_or_404(Tirage, id=tirage_id)
    shows_qs = tirage.spectacle_set.select_related("location").prefetch_related(
        "quote_set"
    )
    category_name = request.GET.get("category", "")
    location_id = request.GET.get("location", "")
    if category_name:
        shows_qs = shows_qs.filter(category__name=category_name)
    if location_id:
        try:
            shows_qs = shows_qs.filter(location__id=int(location_id))
        except ValueError:
            return HttpResponseBadRequest(
                "La variable GET 'location' doit contenir un entier"
            )
    return render(request, "descriptions.html", {"shows": shows_qs})


def catalogue(request, request_type):
    """
    Vue destinée à communiquer avec un client AJAX, fournissant soit :
    - la liste des tirages
    - les catégories et salles d'un tirage
    - les descriptions d'un tirage (filtrées selon la catégorie et la salle)
    """
    if request_type == "list":
        # Dans ce cas on retourne la liste des tirages et de leur id en JSON
        data_return = list(
            Tirage.objects.filter(appear_catalogue=True).values("id", "title")
        )
        return JsonResponse(data_return, safe=False)
    if request_type == "details":
        # Dans ce cas on retourne une liste des catégories et des salles
        tirage_id = request.GET.get("id", None)
        if tirage_id is None:
            return HttpResponseBadRequest("Missing GET parameter: id <int>")
        try:
            tirage = get_object_or_404(Tirage, id=int(tirage_id))
        except ValueError:
            return HttpResponseBadRequest("Bad format: int expected for `id`")
        shows = tirage.spectacle_set.values_list("id", flat=True)
        categories = list(
            CategorieSpectacle.objects.filter(spectacle__in=shows)
            .distinct()
            .values("id", "name")
        )
        locations = list(
            Salle.objects.filter(spectacle__in=shows).distinct().values("id", "name")
        )
        data_return = {"categories": categories, "locations": locations}
        return JsonResponse(data_return, safe=False)
    if request_type == "descriptions":
        # Ici on retourne les descriptions correspondant à la catégorie et
        # à la salle spécifiées

        tirage_id = request.GET.get("id", "")
        categories = request.GET.get("category", "[]")
        locations = request.GET.get("location", "[]")
        try:
            tirage_id = int(tirage_id)
            categories_id = json.loads(categories)
            locations_id = json.loads(locations)
            # Integers expected
            if not all(isinstance(id, int) for id in categories_id):
                raise ValueError
            if not all(isinstance(id, int) for id in locations_id):
                raise ValueError
        except ValueError:  # Contient JSONDecodeError
            return HttpResponseBadRequest(
                "Parse error, please ensure the GET parameters have the "
                "following types:\n"
                "id: int, category: [int], location: [int]\n"
                "Data received:\n"
                "id = {}, category = {}, locations = {}".format(
                    request.GET.get("id", ""),
                    request.GET.get("category", "[]"),
                    request.GET.get("location", "[]"),
                )
            )
        tirage = get_object_or_404(Tirage, id=tirage_id)

        shows_qs = tirage.spectacle_set.select_related("location").prefetch_related(
            "quote_set"
        )
        if categories_id and 0 not in categories_id:
            shows_qs = shows_qs.filter(category__id__in=categories_id)
        if locations_id and 0 not in locations_id:
            shows_qs = shows_qs.filter(location__id__in=locations_id)

        # On convertit les descriptions à envoyer en une liste facilement
        # JSONifiable (il devrait y avoir un moyen plus efficace en
        # redéfinissant le serializer de JSON)
        data_return = [
            {
                "title": spectacle.title,
                "category": str(spectacle.category),
                "date": str(
                    formats.date_format(
                        timezone.localtime(spectacle.date), "SHORT_DATETIME_FORMAT"
                    )
                ),
                "location": str(spectacle.location),
                "vips": spectacle.vips,
                "description": spectacle.description,
                "slots_description": spectacle.slots_description,
                "quotes": [
                    dict(author=quote.author, text=quote.text)
                    for quote in spectacle.quote_set.all()
                ],
                "image": spectacle.getImgUrl(),
                "ext_link": spectacle.ext_link,
                "price": spectacle.price,
                "slots": spectacle.slots,
            }
            for spectacle in shows_qs
        ]
        return JsonResponse(data_return, safe=False)
    # Si la requête n'est pas de la forme attendue, on quitte avec une erreur
    return HttpResponseBadRequest()


##
# Autocomplete views
#
# https://django-autocomplete-light.readthedocs.io/en/master/tutorial.html#create-an-autocomplete-view
##


class ParticipantAutocomplete(Select2QuerySetView):
    model = Participant
    search_fields = ("user__username", "user__first_name", "user__last_name")


participant_autocomplete = buro_required(ParticipantAutocomplete.as_view())


class SpectacleAutocomplete(Select2QuerySetView):
    model = Spectacle
    search_fields = ("title",)


spectacle_autocomplete = buro_required(SpectacleAutocomplete.as_view())