# -*- coding: utf-8 -*-

from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals

from django.shortcuts import render, get_object_or_404
from django.contrib.auth.decorators import login_required
from django.db import models
from django.db.models import Count
from django.core import serializers
from django.forms.models import inlineformset_factory
import hashlib

from django.core.mail import send_mail
from django.utils import timezone
from django.views.generic.list import ListView

from datetime import timedelta
import time

from gestioncof.decorators import cof_required, buro_required
from bda.models import Spectacle, Participant, ChoixSpectacle, Attribution,\
    Tirage, render_template
from bda.algorithm import Algorithm

from bda.forms import BaseBdaFormSet, TokenForm, ResellForm


@cof_required
def etat_places(request, tirage_id):
    tirage = get_object_or_404(Tirage, id=tirage_id)
    spectacles1 = ChoixSpectacle.objects \
        .filter(spectacle__tirage=tirage) \
        .filter(double_choice="1") \
        .all() \
        .values('spectacle', 'spectacle__title') \
        .annotate(total=models.Count('spectacle'))
    spectacles2 = ChoixSpectacle.objects \
        .filter(spectacle__tirage=tirage) \
        .exclude(double_choice="1") \
        .all() \
        .values('spectacle', 'spectacle__title') \
        .annotate(total=models.Count('spectacle'))
    spectacles = tirage.spectacle_set.all()
    spectacles_dict = {}
    total = 0
    for spectacle in spectacles:
        spectacle.total = 0
        spectacle.ratio = 0.0
        spectacles_dict[spectacle.id] = spectacle
    for spectacle in spectacles1:
        spectacles_dict[spectacle["spectacle"]].total += spectacle["total"]
        spectacles_dict[spectacle["spectacle"]].ratio = \
            spectacles_dict[spectacle["spectacle"]].total / \
            spectacles_dict[spectacle["spectacle"]].slots
        total += spectacle["total"]
    for spectacle in spectacles2:
        spectacles_dict[spectacle["spectacle"]].total += 2*spectacle["total"]
        spectacles_dict[spectacle["spectacle"]].ratio = \
            spectacles_dict[spectacle["spectacle"]].total / \
            spectacles_dict[spectacle["spectacle"]].slots
        total += spectacle["total"]
    return render(request, "etat-places.html",
                  {"spectacles": spectacles, "total": total, 'tirage': tirage})


def _hash_queryset(queryset):
    data = serializers.serialize("json", queryset).encode()
    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, created = Participant.objects.get_or_create(
        user=request.user, tirage=tirage)
    places = participant.attribution_set.order_by(
        "spectacle__date", "spectacle").all()
    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)
    return render(request, "resume_places.html",
                  {"participant": participant,
                   "places": filtered_places,
                   "tirage": tirage,
                   "total": total,
                   "warning": warning})


@cof_required
def places_ics(request, tirage_id):
    tirage = get_object_or_404(Tirage, id=tirage_id)
    participant, created = Participant.objects.get_or_create(
        user=request.user, tirage=tirage)
    places = participant.attribution_set.order_by(
        "spectacle__date", "spectacle").all()
    filtered_places = []
    places_dict = {}
    spectacles = []
    for place in places:
        if place.spectacle in spectacles:
            places_dict[place.spectacle].double = True
        else:
            place.double = False
            place.spectacle.dtend = place.spectacle.date \
                + timedelta(seconds=7200)
            places_dict[place.spectacle] = place
            spectacles.append(place.spectacle)
            filtered_places.append(place)
    return render(request, "resume_places.ics",
                  {"participant": participant,
                   "places": filtered_places}, content_type="text/calendar")


@cof_required
def inscription(request, tirage_id):
    tirage = get_object_or_404(Tirage, id=tirage_id)
    if timezone.now() < tirage.ouverture:
        error_desc = "Ouverture le %s" % (
            tirage.ouverture.strftime('%d %b %Y à %H:%M'))
        return render(request, 'resume_inscription.html',
                      {"error_title": "Le tirage n'est pas encore ouvert !",
                       "error_description": error_desc})
    if timezone.now() > tirage.fermeture:
        participant, created = Participant.objects.get_or_create(
            user=request.user, tirage=tirage)
        choices = participant.choixspectacle_set.order_by("priority").all()
        return render(request, "resume_inscription.html",
                      {"error_title": "C'est fini !",
                       "error_description":
                           "Tirage au sort dans la journée !",
                       "choices": choices})

    def formfield_callback(f, **kwargs):
        if f.name == "spectacle":
            kwargs['queryset'] = tirage.spectacle_set
        return f.formfield(**kwargs)
    BdaFormSet = inlineformset_factory(
        Participant,
        ChoixSpectacle,
        fields=("spectacle", "double_choice", "priority"),
        formset=BaseBdaFormSet,
        formfield_callback=formfield_callback)
    participant, created = Participant.objects.get_or_create(
        user=request.user, tirage=tirage)
    success = False
    stateerror = False
    if request.method == "POST":
        dbstate = _hash_queryset(participant.choixspectacle_set.all())
        if "dbstate" in request.POST and dbstate != request.POST["dbstate"]:
            stateerror = True
            formset = BdaFormSet(instance=participant)
        else:
            formset = BdaFormSet(request.POST, instance=participant)
            if formset.is_valid():
                formset.save()
                success = True
                formset = BdaFormSet(instance=participant)
    else:
        formset = BdaFormSet(instance=participant)
    dbstate = _hash_queryset(participant.choixspectacle_set.all())
    total_price = 0
    for choice in participant.choixspectacle_set.all():
        total_price += choice.spectacle.price
        if choice.double:
            total_price += choice.spectacle.price
    return render(request, "inscription-bda.html",
                  {"formset": formset,
                   "success": success,
                   "total_price": total_price,
                   "dbstate": dbstate,
                   'tirage': tirage,
                   "stateerror": stateerror})


def do_tirage(request, tirage_id):
    tirage_elt = get_object_or_404(Tirage, id=tirage_id)
    form = TokenForm(request.POST)
    if not form.is_valid():
        return tirage(request, tirage_id)
    tirage_elt.token = form.cleaned_data['token']
    tirage_elt.save()
    start = time.time()
    data = {}
    shows = tirage_elt.spectacle_set.select_related().all()
    members = tirage_elt.participant_set.all()
    choices = ChoixSpectacle.objects.filter(spectacle__tirage=tirage_elt) \
        .order_by('participant', 'priority').select_related().all()
    algo = Algorithm(shows, members, choices)
    results = algo(form.cleaned_data["token"])
    total_slots = 0
    total_losers = 0
    for (_, members, losers) in results:
        total_slots += len(members)
        total_losers += len(losers)
    data["total_slots"] = total_slots
    data["total_losers"] = total_losers
    data["shows"] = shows
    data["token"] = form.cleaned_data["token"]
    data["members"] = members
    data["results"] = results
    total_sold = 0
    total_deficit = 0
    opera_deficit = 0
    for (show, members, _) in results:
        deficit = (show.slots - len(members)) * show.price
        total_sold += show.slots * show.price
        if deficit >= 0:
            if "Opéra" in show.location.name:
                opera_deficit += deficit
            total_deficit += deficit
    data["total_sold"] = total_sold - total_deficit
    data["total_deficit"] = total_deficit
    data["opera_deficit"] = opera_deficit
    data["duration"] = time.time() - start
    if request.user.is_authenticated():
        members2 = {}
        # Participant objects are not shared accross spectacle results,
        # so assign a single object for each Participant id
        members_uniq = {}
        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
        # FIXME: Établir les conditions de validations (formulaire ?)
        # cf. issue #32
        if True:
            Attribution.objects.filter(
                spectacle__tirage=tirage_elt
            ).delete()
            for (show, members, _) in results:
                for (member, _, _, _) in members:
                    attrib = Attribution(spectacle=show, participant=member)
                    attrib.save()
        return render(request, "bda-attrib-extra.html", data)
    else:
        return render(request, "bda-attrib.html", data)


@buro_required
def tirage(request, tirage_id):
    if request.POST:
        form = TokenForm(request.POST)
        if form.is_valid():
            return do_tirage(request, tirage_id)
    else:
        form = TokenForm()
    return render(request, "bda-token.html", {"form": form})


def do_resell(request, form):
    spectacle = form.cleaned_data["spectacle"]
    count = form.cleaned_data["count"]
    places = "2 places" if count == "2" else "une place"
    mail = """Bonjour,

Je souhaite revendre %s pour %s le %s (%s) à %.02f€.
Contactez moi par email si vous êtes intéressé·e·s !

%s (%s)""" % (places, spectacle.title, spectacle.date_no_seconds(),
              spectacle.location, spectacle.price,
              request.user.get_full_name(), request.user.email)
    send_mail("%s" % spectacle, mail,
              request.user.email, ["bda-revente@lists.ens.fr"],
              fail_silently=False)
    return render(request, "bda-success.html",
                  {"show": spectacle, "places": places})


@login_required
def revente(request, tirage_id):
    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-notpaid.html", {})
    if request.POST:
        form = ResellForm(participant, request.POST)
        if form.is_valid():
            return do_resell(request, form)
    else:
        form = ResellForm(participant)
    return render(request, "bda-revente.html",
                  {"form": form, 'tirage': tirage})


@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.all()
    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.all()
        return categories

    def get_context_data(self, **kwargs):
        context = super(SpectacleListView, self).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).all()
    return render(request, "bda-unpaid.html", {"unpaid": unpaid})


@buro_required
def liste_spectacles_ics(request, tirage_id):
    tirage = get_object_or_404(Tirage, id=tirage_id)
    spectacles = tirage.spectacle_set.order_by("date").all()
    for spectacle in spectacles:
        spectacle.dtend = spectacle.date + timedelta(seconds=7200)
    return render(request, "liste_spectacles.ics",
                  {"spectacles": spectacles, "tirage": tirage},
                  content_type="text/calendar")


@buro_required
def send_rappel(request, spectacle_id):
    show = get_object_or_404(Spectacle, id=spectacle_id)
    # Mails d'exemples
    fake_member = request.user
    fake_member.nb_attr = 1
    exemple_mail_1place = render_template('mail-rappel.txt', {
            'member': fake_member,
            'show': show})
    fake_member.nb_attr = 2
    exemple_mail_2places = render_template('mail-rappel.txt', {
            'member': fake_member,
            'show': show})
    # Contexte
    ctxt = {'show': show,
            'exemple_mail_1place': exemple_mail_1place,
            'exemple_mail_2places': exemple_mail_2places}
    # Envoi confirmé
    if request.method == 'POST':
        members = show.send_rappel()
        ctxt['sent'] = True
        ctxt['members'] = members
    # Demande de confirmation
    else:
        ctxt['sent'] = False
    return render(request, "mails-rappel.html", ctxt)
