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

import json
from datetime import datetime
from custommail.shortcuts import render_custom_mail

from django.shortcuts import render, get_object_or_404, redirect
from django.core import mail
from django.contrib.auth.models import User
from django.views.generic import ListView, DetailView
from django.views.decorators.csrf import csrf_exempt
from django.conf import settings
from django.contrib.auth.decorators import login_required
from django.contrib import messages

from gestioncof.models import CofProfile
from gestioncof.petits_cours_models import (
    PetitCoursDemande, PetitCoursAttribution, PetitCoursAttributionCounter,
    PetitCoursAbility, PetitCoursSubject
)
from gestioncof.petits_cours_forms import DemandeForm, MatieresFormSet
from gestioncof.decorators import buro_required
from gestioncof.shared import lock_table, unlock_tables


class DemandeListView(ListView):
    model = PetitCoursDemande
    template_name = "petits_cours_demandes_list.html"
    paginate_by = 20

    def get_queryset(self):
        return PetitCoursDemande.objects.order_by('traitee', '-id').all()


class DemandeDetailView(DetailView):
    model = PetitCoursDemande
    template_name = "gestioncof/details_demande_petit_cours.html"
    context_object_name = "demande"

    def get_context_data(self, **kwargs):
        context = super(DemandeDetailView, self).get_context_data(**kwargs)
        obj = self.object
        context['attributions'] = obj.petitcoursattribution_set.all()
        return context


@buro_required
def traitement(request, demande_id, redo=False):
    demande = get_object_or_404(PetitCoursDemande, id=demande_id)
    if demande.niveau == "other":
        return _traitement_other(request, demande, redo)
    if request.method == "POST":
        return _traitement_post(request, demande)
    proposals = {}
    proposed_for = {}
    unsatisfied = []
    attribdata = {}
    for matiere, candidates in demande.get_candidates(redo):
        if candidates:
            tuples = []
            for candidate in candidates:
                user = candidate.user
                tuples.append((
                    candidate,
                    PetitCoursAttributionCounter.get_uptodate(user, matiere)
                ))
            tuples = sorted(tuples, key=lambda c: c[1].count)
            candidates, _ = zip(*tuples)
            candidates = candidates[0:min(3, len(candidates))]
            attribdata[matiere.id] = []
            proposals[matiere] = []
            for candidate in candidates:
                user = candidate.user
                proposals[matiere].append(user)
                attribdata[matiere.id].append(user.id)
                if user not in proposed_for:
                    proposed_for[user] = [matiere]
                else:
                    proposed_for[user].append(matiere)
        else:
            unsatisfied.append(matiere)
    return _finalize_traitement(request, demande, proposals,
                                proposed_for, unsatisfied, attribdata, redo)


@buro_required
def retraitement(request, demande_id):
    return traitement(request, demande_id, redo=True)


def _finalize_traitement(request, demande, proposals, proposed_for,
                         unsatisfied, attribdata, redo=False, errors=None):
    proposals = proposals.items()
    proposed_for = proposed_for.items()
    attribdata = list(attribdata.items())
    proposed_mails = _generate_eleve_email(demande, proposed_for)
    mainmail = render_custom_mail("petits-cours-mail-demandeur", {
        "proposals": proposals,
        "unsatisfied": unsatisfied,
        "extra":
            '<textarea name="extra" '
            'style="width:99%; height: 90px;">'
            '</textarea>'
    })
    if errors is not None:
        for error in errors:
            messages.error(request, error)
    return render(request, "gestioncof/traitement_demande_petit_cours.html",
                  {"demande": demande,
                   "unsatisfied": unsatisfied,
                   "proposals": proposals,
                   "proposed_for": proposed_for,
                   "proposed_mails": proposed_mails,
                   "mainmail": mainmail,
                   "attribdata": json.dumps(attribdata),
                   "redo": redo,
                   })


def _generate_eleve_email(demande, proposed_for):
    return [
        (
            user,
            render_custom_mail('petit-cours-mail-eleve', {
                "demande": demande,
                "matieres": matieres
            })
        )
        for user, matieres in proposed_for
    ]


def _traitement_other_preparing(request, demande):
    redo = "redo" in request.POST
    unsatisfied = []
    proposals = {}
    proposed_for = {}
    attribdata = {}
    errors = []
    for matiere, candidates in demande.get_candidates(redo):
        if candidates:
            candidates = dict([(candidate.user.id, candidate.user)
                               for candidate in candidates])
            attribdata[matiere.id] = []
            proposals[matiere] = []
            for choice_id in range(min(3, len(candidates))):
                choice = int(
                    request.POST["proposal-{:d}-{:d}"
                                 .format(matiere.id, choice_id)]
                )
                if choice == -1:
                    continue
                if choice not in candidates:
                    errors.append("Choix invalide pour la proposition {:d}"
                                  "en {!s}".format(choice_id + 1, matiere))
                    continue
                user = candidates[choice]
                if user in proposals[matiere]:
                    errors.append("La proposition {:d} en {!s} est un doublon"
                                  .format(choice_id + 1, matiere))
                    continue
                proposals[matiere].append(user)
                attribdata[matiere.id].append(user.id)
                if user not in proposed_for:
                    proposed_for[user] = [matiere]
                else:
                    proposed_for[user].append(matiere)
            if not proposals[matiere]:
                errors.append("Aucune proposition pour {!s}".format(matiere))
            elif len(proposals[matiere]) < 3:
                errors.append("Seulement {:d} proposition{:s} pour {!s}"
                              .format(
                                  len(proposals[matiere]),
                                  "s" if len(proposals[matiere]) > 1 else "",
                                  matiere))
        else:
            unsatisfied.append(matiere)
    return _finalize_traitement(request, demande, proposals, proposed_for,
                                unsatisfied, attribdata, errors=errors)


def _traitement_other(request, demande, redo):
    if request.method == "POST":
        if "preparing" in request.POST:
            return _traitement_other_preparing(request, demande)
        else:
            return _traitement_post(request, demande)
    proposals = {}
    proposed_for = {}
    unsatisfied = []
    attribdata = {}
    for matiere, candidates in demande.get_candidates(redo):
        if candidates:
            tuples = []
            for candidate in candidates:
                user = candidate.user
                tuples.append((
                    candidate,
                    PetitCoursAttributionCounter.get_uptodate(user, matiere)
                ))
            tuples = sorted(tuples, key=lambda c: c[1].count)
            candidates, _ = zip(*tuples)
            attribdata[matiere.id] = []
            proposals[matiere] = []
            for candidate in candidates:
                user = candidate.user
                proposals[matiere].append(user)
                attribdata[matiere.id].append(user.id)
                if user not in proposed_for:
                    proposed_for[user] = [matiere]
                else:
                    proposed_for[user].append(matiere)
        else:
            unsatisfied.append(matiere)
    proposals = proposals.items()
    proposed_for = proposed_for.items()
    return render(request,
                  "gestioncof/traitement_demande_petit_cours_autre_niveau.html",
                  {"demande": demande,
                   "unsatisfied": unsatisfied,
                   "proposals": proposals,
                   "proposed_for": proposed_for,
                   })


def _traitement_post(request, demande):
    proposals = {}
    proposed_for = {}
    unsatisfied = []
    extra = request.POST["extra"].strip()
    redo = "redo" in request.POST
    attribdata = request.POST["attribdata"]
    attribdata = dict(json.loads(attribdata))
    for matiere in demande.matieres.all():
        if matiere.id not in attribdata:
            unsatisfied.append(matiere)
        else:
            proposals[matiere] = []
            for user_id in attribdata[matiere.id]:
                user = User.objects.get(pk=user_id)
                proposals[matiere].append(user)
                if user not in proposed_for:
                    proposed_for[user] = [matiere]
                else:
                    proposed_for[user].append(matiere)
    proposals_list = proposals.items()
    proposed_for = proposed_for.items()
    proposed_mails = _generate_eleve_email(demande, proposed_for)
    mainmail_object, mainmail_body = render_custom_mail(
        "petits-cours-mail-demandeur",
        {
            "proposals": proposals_list,
            "unsatisfied": unsatisfied,
            "extra": extra
        }
    )
    frommail = settings.MAIL_DATA['petits_cours']['FROM']
    bccaddress = settings.MAIL_DATA['petits_cours']['BCC']
    replyto = settings.MAIL_DATA['petits_cours']['REPLYTO']
    mails_to_send = []
    for (user, (mail_object, body)) in proposed_mails:
        msg = mail.EmailMessage(mail_object, body, frommail, [user.email],
                                [bccaddress], headers={'Reply-To': replyto})
        mails_to_send.append(msg)
    mails_to_send.append(mail.EmailMessage(mainmail_object, mainmail_body,
                                           frommail, [demande.email],
                                           [bccaddress],
                                           headers={'Reply-To': replyto}))
    connection = mail.get_connection(fail_silently=False)
    connection.send_messages(mails_to_send)
    lock_table(PetitCoursAttributionCounter, PetitCoursAttribution, User)
    for matiere in proposals:
        for rank, user in enumerate(proposals[matiere]):
            counter = PetitCoursAttributionCounter.objects.get(user=user,
                                                               matiere=matiere)
            counter.count += 1
            counter.save()
            attrib = PetitCoursAttribution(user=user, matiere=matiere,
                                           demande=demande, rank=rank + 1)
            attrib.save()
    unlock_tables()
    demande.traitee = True
    demande.traitee_par = request.user
    demande.processed = datetime.now()
    demande.save()
    return render(request,
                  "gestioncof/traitement_demande_petit_cours_success.html",
                  {"demande": demande,
                   "redo": redo,
                   })


@login_required
def inscription(request):
    profile, created = CofProfile.objects.get_or_create(user=request.user)
    if not profile.is_cof:
        return redirect("cof-denied")
    success = False
    if request.method == "POST":
        formset = MatieresFormSet(request.POST, instance=request.user)
        if formset.is_valid():
            formset.save()
            profile.petits_cours_accept = "receive_proposals" in request.POST
            profile.petits_cours_remarques = request.POST["remarques"]
            profile.save()
            lock_table(PetitCoursAttributionCounter, PetitCoursAbility, User,
                       PetitCoursSubject)
            abilities = (
                PetitCoursAbility.objects.filter(user=request.user).all()
            )
            for ability in abilities:
                PetitCoursAttributionCounter.get_uptodate(
                    ability.user,
                    ability.matiere
                )
            unlock_tables()
            success = True
            formset = MatieresFormSet(instance=request.user)
    else:
        formset = MatieresFormSet(instance=request.user)
    return render(request, "inscription-petit-cours.html",
                  {"formset": formset, "success": success,
                   "receive_proposals": profile.petits_cours_accept,
                   "remarques": profile.petits_cours_remarques})


@csrf_exempt
def demande(request):
    success = False
    if request.method == "POST":
        form = DemandeForm(request.POST)
        if form.is_valid():
            form.save()
            success = True
    else:
        form = DemandeForm()
    return render(request, "demande-petit-cours.html", {"form": form,
                                                        "success": success})


@csrf_exempt
def demande_raw(request):
    success = False
    if request.method == "POST":
        form = DemandeForm(request.POST)
        if form.is_valid():
            form.save()
            success = True
    else:
        form = DemandeForm()
    return render(request, "demande-petit-cours-raw.html",
                  {"form": form, "success": success})
