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

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

import random

from django.shortcuts import render, get_object_or_404
from django.contrib.auth.decorators import login_required
from django.db import models, transaction
from django.db.models import Count, Q
from django.core import serializers, mail
from django.forms.models import inlineformset_factory
from django.http import HttpResponseBadRequest, HttpResponseRedirect
from django.core.urlresolvers import reverse
from django.conf import settings
import hashlib

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

import time
from datetime import timedelta

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

from bda.forms import BaseBdaFormSet, TokenForm, ResellForm, AnnulForm,\
        InscriptionReventeForm


@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('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, 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 inscription(request, tirage_id):
    tirage = get_object_or_404(Tirage, id=tirage_id)
    if timezone.now() < tirage.ouverture:
        error_desc = tirage.ouverture.strftime('Ouverture le %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)
    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
        Attribution.objects.filter(spectacle__tirage=tirage_elt).delete()
        tirage_elt.tokens += "%s\n\"\"\"%s\"\"\"\n" % (
                timezone.now().strftime("%y-%m-%d %H:%M:%S"),
                form.cleaned_data['token'])
        tirage_elt.enable_do_tirage = False
        tirage_elt.save()
        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
        for (show, _, losers) in results:
            for (loser, _, _, _) in losers:
                loser.choicesrevente.add(show)
                loser.save()
        return render(request, "bda-attrib-extra.html", data)
    else:
        return render(request, "bda-attrib.html", 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():
            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.method == 'POST':
        if 'resell' in request.POST:
            resellform = ResellForm(participant, request.POST, prefix='resell')
            annulform = AnnulForm(participant, prefix='annul')
            if resellform.is_valid():
                mails = []
                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.seller = participant
                            revente.date = timezone.now()
                        mail_subject = "BdA-Revente : {:s}".format(attribution.spectacle.title)
                        mail_body = loader.render_to_string('mail-revente-new.txt', {
                            'vendeur': participant.user,
                            'spectacle': attribution.spectacle,
                            'revente': revente,
                        })
                        mails.append(mail.EmailMessage(
                            mail_subject, mail_body,
                            from_email=settings.MAIL_DATA['revente']['FROM'],
                            to=[participant.user.email],
                            reply_to=[settings.MAIL_DATA['revente']['REPLYTO']],
                        ))
                        revente.save()
                    mail.get_connection().send_messages(mails)

        elif 'annul' in request.POST:
            annulform = AnnulForm(participant, request.POST, prefix='annul')
            resellform = ResellForm(participant, prefix='resell')
            if annulform.is_valid():
                attributions = annulform.cleaned_data["attributions"]
                for attribution in attributions:
                    attribution.revente.delete()

        elif 'transfer' in request.POST:
            resellform = ResellForm(participant, prefix='resell')
            annulform = AnnulForm(participant, prefix='annul')

            revente_id = request.POST['transfer'][0]
            rev = SpectacleRevente.objects.filter(soldTo__isnull=False,
                                                  id=revente_id)
            if rev.exists():
                revente = rev.get()
                attrib = revente.attribution
                attrib.participant = revente.soldTo
                attrib.save()

        elif 'reinit' in request.POST:
            resellform = ResellForm(participant, prefix='resell')
            annulform = AnnulForm(participant, prefix='annul')
            revente_id = request.POST['reinit'][0]
            rev = SpectacleRevente.objects.filter(soldTo__isnull=False,
                                                  id=revente_id)
            if rev.exists():
                revente = rev.get()
                if revente.attribution.spectacle.date > timezone.now():
                    revente.date = timezone.now() - timedelta(hours=1)
                    revente.soldTo = None
                    revente.notif_sent = False
                    revente.tirage_done = False
                    if revente.answered_mail:
                        revente.answered_mail.clear()
                    revente.save()

        else:
            resellform = ResellForm(participant, prefix='resell')
            annulform = AnnulForm(participant, prefix='annul')
    else:
        resellform = ResellForm(participant, prefix='resell')
        annulform = AnnulForm(participant, prefix='annul')

    overdue = participant.attribution_set.filter(
            spectacle__date__gte=timezone.now(),
            revente__isnull=False,
            revente__seller=participant,
            revente__date__lte=timezone.now()-timedelta(hours=1)).filter(
            Q(revente__soldTo__isnull=True) | Q(revente__soldTo=participant))
    sold = participant.attribution_set.filter(
            spectacle__date__gte=timezone.now(),
            revente__isnull=False,
            revente__soldTo__isnull=False).exclude(
            revente__soldTo=participant)

    return render(request, "bda-revente.html",
                  {'tirage': tirage, 'overdue': overdue, "sold": sold,
                   "annulform": annulform, "resellform": resellform})


@login_required
def revente_interested(request, revente_id):
    revente = get_object_or_404(SpectacleRevente, id=revente_id)
    participant, created = Participant.objects.get_or_create(
        user=request.user, tirage=revente.attribution.spectacle.tirage)
    if timezone.now() < revente.date + timedelta(hours=1) or revente.shotgun:
        return render(request, "bda-wrongtime.html", {})

    revente.answered_mail.add(participant)
    return render(request, "bda-interested.html",
                  {"spectacle": revente.attribution.spectacle,
                   "date": revente.expiration_time})


@login_required
def list_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)
    spectacles = tirage.spectacle_set.filter(
            date__gte=timezone.now())
    shotgun = []
    deja_revente = False
    success = False
    for spectacle in spectacles:
        revente_objects = SpectacleRevente.objects.filter(
                attribution__spectacle=spectacle,
                soldTo__isnull=True)
        revente_count = 0
        for revente in revente_objects:
            if revente.shotgun:
                revente_count += 1
        if revente_count:
            spectacle.revente_count = revente_count
            shotgun.append(spectacle)

    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.exists():
                    # On l'inscrit à l'un des tirages au sort
                    for revente in qset.all():
                        if revente.shotgun and not revente.soldTo:
                            deja_revente = True
                        else:
                            revente.answered_mail.add(participant)
                            revente.save()
                            break
            success = True
    else:
        form = InscriptionReventeForm(
                tirage,
                initial={'spectacles': participant.choicesrevente.all()})

    return render(request, "liste-reventes.html",
                  {"form": form, 'shotgun': shotgun,
                      "deja_revente": deja_revente, "success": success})


@login_required
def buy_revente(request, spectacle_id):
    spectacle = get_object_or_404(Spectacle, id=spectacle_id)
    tirage = spectacle.tirage
    participant, created = Participant.objects.get_or_create(
            user=request.user, tirage=tirage)
    reventes = SpectacleRevente.objects.filter(
                         attribution__spectacle=spectacle,
                         soldTo__isnull=True)
    if reventes.filter(seller=participant).exists():
        revente = reventes.filter(seller=participant)[0]
        revente.delete()
        return HttpResponseRedirect(reverse("bda-shotgun",
                                            args=[tirage.id]))
    reventes_shotgun = []
    for revente in reventes.all():
        if revente.shotgun:
            reventes_shotgun.append(revente)

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

    if request.POST:
        revente = random.choice(reventes_shotgun)
        revente.soldTo = participant
        revente.save()
        mail = """Bonjour !

Je souhaiterais racheter ta place pour %s le %s (%s) à %.02f€.
Contacte-moi si tu es toujours intéressé·e !

%s (%s)""" % (spectacle.title, spectacle.date_no_seconds(),
              spectacle.location, spectacle.price,
              request.user.get_full_name(), request.user.email)
        send_mail("BdA-Revente : %s" % spectacle.title, mail,
                  request.user.email,
                  [revente.seller.user.email],
                  fail_silently=False)
        return render(request, "bda-success.html",
                      {"seller": revente.attribution.participant.user,
                       "spectacle": spectacle})

    return render(request, "revente-confirm.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())
    shotgun = []
    for spectacle in spectacles:
        revente_objects = SpectacleRevente.objects.filter(
                attribution__spectacle=spectacle,
                soldTo__isnull=True)
        revente_count = 0
        for revente in revente_objects:
            if revente.shotgun:
                revente_count += 1
        if revente_count:
            shotgun.append(spectacle)

    return render(request, "bda-shotgun.html",
                  {"shotgun": 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.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 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)


def descriptions_spectacles(request, tirage_id):
    tirage = get_object_or_404(Tirage, id=tirage_id)
    shows_qs = tirage.spectacle_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.all()})