
# coding: utf-8

from django.contrib.auth.models import User
from django.shortcuts import render, get_object_or_404
from django.contrib.auth.decorators import login_required
from django.db import models
from django.http import Http404
from django import forms
from django.forms.models import inlineformset_factory, BaseInlineFormSet
from django.core import serializers
import hashlib

from django.core.mail import send_mail                                        

from datetime import datetime
import time

from gestioncof.decorators import cof_required, buro_required
from bda3.models import Spectacle, Participant, ChoixSpectacle, Attribution
from bda3.algorithm import Algorithm

class BaseBdaFormSet(BaseInlineFormSet):
    def clean(self):
        """Checks that no two articles have the same title."""
        super(BaseBdaFormSet, self).clean()
        if any(self.errors):
            # Don't bother validating the formset unless each form is valid on its own
            return
        spectacles = []
        for i in range(0, self.total_form_count()):
            form = self.forms[i]
            if not form.cleaned_data:
                continue
            spectacle = form.cleaned_data['spectacle']
            delete = form.cleaned_data['DELETE']
            if not delete and spectacle in spectacles:
                raise forms.ValidationError("Vous ne pouvez pas vous inscrire deux fois pour le même spectacle.")
            spectacles.append(spectacle)

@cof_required
def etat_places(request):
    spectacles1 = ChoixSpectacle.objects.all().values('spectacle','spectacle__title').annotate(total = models.Count('spectacle'))
    spectacles2 = ChoixSpectacle.objects.filter(double = True).all().values('spectacle','spectacle__title').annotate(total = models.Count('spectacle'))
    spectacles = Spectacle.objects.all()
    spectacles_dict = {}
    total = 0
    for spectacle in spectacles:
        spectacle.total = 0
        spectacle.ratio = -1.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/float(spectacles_dict[spectacle["spectacle"]].slots)
        total += spectacle["total"]  
    for spectacle in spectacles2:
        spectacles_dict[spectacle["spectacle"]].total += spectacle["total"]
        spectacles_dict[spectacle["spectacle"]].ratio = spectacles_dict[spectacle["spectacle"]].total/float(spectacles_dict[spectacle["spectacle"]].slots)
        total += spectacle["total"]  
    return render(request, "etat-places.html", {"spectacles": spectacles, "total": total})

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

@cof_required
def places(request):
    participant, created = Participant.objects.get_or_create(user = request.user)
    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,
                   "total": total,
                   "warning": warning})

@cof_required
def inscription(request):
    if datetime.now() > datetime(2016, 4, 10, 11, 59):
        participant, created = Participant.objects.get_or_create(user = request.user)
        choices = participant.choixspectacle_set.order_by("priority").all()
        return render(request, "resume_inscription.html", {"error_title": "C'est fini !", "error_description": u"Tirage au sort très bientôt !", "choices": choices})
    BdaFormSet = inlineformset_factory(Participant, ChoixSpectacle, fields = ("spectacle","double","autoquit","priority",), formset = BaseBdaFormSet)
    participant, created = Participant.objects.get_or_create(user = request.user)
    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():
                #ChoixSpectacle.objects.filter(participant = participant).delete()
                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-bda3.html", {"formset": formset, "success": success, "total_price": total_price, "dbstate": dbstate, "stateerror": stateerror})

def do_tirage(request):
    form = TokenForm(request.POST)
    if not form.is_valid():
        return tirage(request)
    start = time.time()
    data = {}
    shows = Spectacle.objects.select_related().all()
    members = Participant.objects.all()
    choices = ChoixSpectacle.objects.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 u"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 = {}
        members_uniq = {} # Participant objects are not shared accross spectacle results,
                          # So assign a single object for each Participant id
        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)
        if False and request.user.username in ["seguin", "harazi", "fromherz", "mpepin"]:
            Attribution.objects.all().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)

class TokenForm(forms.Form):
    token = forms.CharField(widget = forms.widgets.Textarea())

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

class SpectacleModelChoiceField(forms.ModelChoiceField):
    def label_from_instance(self, obj):
        return u"%s le %s (%s) à %.02f€" % (obj.title, obj.date_no_seconds(), obj.location, obj.price)

class ResellForm(forms.Form):
    count = forms.ChoiceField(choices = (("1","1"),("2","2"),))
    spectacle = SpectacleModelChoiceField(queryset = Spectacle.objects.none())

    def __init__(self, participant, *args, **kwargs):
        super(ResellForm, self).__init__(*args, **kwargs)
        self.fields['spectacle'].queryset = participant.attributions.all().distinct()

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 = u"""Bonjour,

Je souhaite revendre %s pour %s le %s (%s) à %.02f€.
Contactez moi par email si vous êtes intéressé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 = True)
    return render(request, "bda-success.html", {"show": spectacle, "places": places})

@login_required
def revente(request):
    participant, created = Participant.objects.get_or_create(user = request.user)
    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})

@buro_required
def spectacle(request, spectacle_id):
    spectacle = get_object_or_404(Spectacle, id = spectacle_id)
    return render(request, "bda-emails.html", {"spectacle": spectacle})

@buro_required
def unpaid(request):
    return render(request, "bda-unpaid.html", {"unpaid": Participant.objects.filter(paid = False).all()})
