# coding: utf-8

from django.shortcuts import render, redirect, get_object_or_404

from django.views.generic import (
    DetailView, ListView, UpdateView, CreateView, TemplateView, DeleteView,
    FormView, View
)
from django.views.generic.detail import SingleObjectMixin
from django import forms
from django.urls import reverse, reverse_lazy
from django.conf import settings
from django.contrib.admin.views.decorators import staff_member_required
from django.contrib.auth.decorators import login_required
from django.contrib.auth.tokens import default_token_generator
from django.contrib.auth.views import PasswordResetConfirmView
from django.contrib import messages
from braces.views import LoginRequiredMixin
from django.http import JsonResponse, HttpResponseForbidden, Http404
from django.core.mail import send_mail
from django.db.models import Q, Count
from collections import Counter, defaultdict
from simple_email_confirmation.models import EmailAddress

from .models import Normalien, Stage, Lieu, AvisLieu, AvisStage
from .forms import (
    StageForm, LieuForm, AvisStageForm, AvisLieuForm, FeedbackForm, AdresseEmailForm,
    ReinitMdpForm
)
from .utils import en_scolarite

from .views_search import *

import random, math

#
# LECTURE
#

# Page d'accueil
def index(request):
    num_stages = Stage.objects.filter(public=True).count()
    return render(request, 'avisstage/index.html',
                  {"num_stages": num_stages})

# Espace personnel
@login_required
def perso(request):
    # HOTFIX (TODO rendre ça plus propre)
    # Vérifie que le profil existe bien
    # (suite à un cas où il n'avait pas été initialisé)
    if not hasattr(request.user, "profil"):
        profil, created = Normalien.objects.get_or_create(user=request.user)
        profil.save()

    return render(request, 'avisstage/perso.html')

# 403 Archicubes
@login_required
def archicubes_interdits(request):
    return render(request, 'avisstage/403-archicubes.html')

# Profil
#login_required
class ProfilView(LoginRequiredMixin, DetailView):
    model = Normalien
    template_name = 'avisstage/detail/profil.html'

    # Récupération du profil
    def get_object(self):
        
        # Restriction d'accès pour les archicubes
        if (en_scolarite(self.request.user) or
            self.kwargs.get('username') == self.request.user.username):
            return get_object_or_404(
                Normalien, user__username=self.kwargs.get('username'))
        else:
            raise Http404

# Stage
#login_required
class StageView(LoginRequiredMixin, DetailView):
    model = Stage
    template_name = 'avisstage/detail/stage.html'

    # Restriction aux stages publics ou personnels
    def get_queryset(self):
        filtre = Q(auteur__user_id=self.request.user.id)
        
        # Restriction d'accès pour les archicubes
        if en_scolarite(self.request.user):
            filtre |= Q(public=True)

        return Stage.objects.filter(filtre)

    def get_context_data(self, *args, **kwargs):
        context = super().get_context_data(*args, **kwargs)
        context['MAPBOX_API_KEY'] = settings.MAPBOX_API_KEY
        return context

# FAQ
def faq(request):
    return render(request, 'avisstage/faq.html')

#
# EDITION
#

# Profil
#login_required
class ProfilEdit(LoginRequiredMixin, UpdateView):
    model = Normalien
    fields = ['nom', 'promotion', 'contactez_moi', 'bio']
    template_name = 'avisstage/formulaires/profil.html'

    # Limitation à son propre profil
    def get_object(self):
        return self.request.user.profil
    
    def get_success_url(self):
        return reverse('avisstage:perso')

# Stage
@login_required
def manage_stage(request, pk=None):
    # Objet de base
    last_maj = None
    if pk is None:
        stage = Stage(auteur=request.user.profil)
        avis_stage = AvisStage(stage=stage)
        c_del = False
        last_creation = Stage.objects.filter(auteur=request.user.profil)\
                                     .order_by("-date_creation")[:1]
        if len(last_creation) != 0:
            last_maj = last_creation[0].date_creation
    else:
        try:
            stage = Stage.objects.filter(auteur=request.user.profil).get(pk=pk)
        except Stage.DoesNotExist:
            return HttpResponseForbidden()
        last_maj = stage.date_maj
        avis_stage, _ = AvisStage.objects.get_or_create(stage=stage)
        c_del = True

    # Formset pour les avis des lieux
    AvisLieuFormSet = forms.inlineformset_factory(
        Stage, AvisLieu, form=AvisLieuForm, can_delete=c_del, extra=0)

    if request.method == "POST":
        # Lecture des données
        form = StageForm(request.POST, request=request, instance=stage, prefix="stage")
        avis_stage_form = AvisStageForm(request.POST,
                                        instance=avis_stage, prefix="avis")
        avis_lieu_formset = AvisLieuFormSet(request.POST, instance=stage,
                                         prefix="lieux")

        # Validation et enregistrement
        if (form.is_valid() and
            avis_stage_form.is_valid() and
            avis_lieu_formset.is_valid()):
            stage = form.save()
            avis_stage_form.instance.stage = stage
            avis_stage_form.save()
            avis_lieu_formset.save()
            #print(request.POST)
            if "continuer" in request.POST:
                if pk is None:
                    return redirect(reverse('avisstage:stage_edit',kwargs={'pk':stage.id}))
            else:
                return redirect(reverse('avisstage:stage',
                                        kwargs={'pk':stage.id}))
    else:
        form = StageForm(instance=stage, prefix="stage")
        avis_stage_form = AvisStageForm(instance=avis_stage, prefix="avis")
        avis_lieu_formset = AvisLieuFormSet(instance=stage, prefix="lieux")

    # Affichage du formulaire
    return render(request, "avisstage/formulaires/stage.html",
                  {'form': form, 'avis_stage_form': avis_stage_form,
                   'avis_lieu_formset': avis_lieu_formset,
                   'creation': pk is None, "last_maj": last_maj,
                   'GOOGLE_API_KEY': settings.GOOGLE_API_KEY,
                   'MAPBOX_API_KEY': settings.MAPBOX_API_KEY})

# Ajout d'un lieu de stage
#login_required

# Stage
@login_required
def save_lieu(request):
    normalien = request.user.profil
    
    if request.method == "POST":
        pk = request.POST.get("id", None)
        #print(request.POST)
        jitter = False
        if pk is None or pk == '':
            lieu = Lieu()
        else:
            # Modification du lieu
            lieu = get_object_or_404(Lieu, pk=pk)

            # On regarde si les stages associés à ce lieu "appartiennent" tous à l'utilisateur
            not_same_user = lieu.stages.exclude(auteur=normalien).count()

            # Si d'autres personnes ont un stage à cet endroit, on crée un nouveau lieu, un peu à côté
            if not_same_user > 0:
                lieu = Lieu()
                # Servira à bouger un peu le lieu
                jitter = True
        
        # Lecture des données
        form = LieuForm(request.POST, instance=lieu)

        # Validation et enregistrement
        if form.is_valid():
            lieu = form.save(commit=False)
            if jitter:
                cdx, cdy = lieu.coord.get_coords()
                ang = random.random() * 6.29;
                rad = (random.random() + 0.5) * 3e-4
                cdx += math.cos(ang) * rad;
                cdy += math.sin(ang) * rad;
                lieu.coord.set_coords((cdx, cdy))
                lieu.save()

            # Élimination des doublons
            if pk is None or pk == "":
                olieux = Lieu.objects.filter(nom=lieu.nom, coord__distance_lte=(lieu.coord, 10))
                for olieu in olieux:
                    if olieu.type_lieu == lieu.type_lieu and \
                       olieu.ville == lieu.ville and \
                       olieu.pays == lieu.pays:
                        return JsonResponse({"success": True, "id": olieu.id})
            
            lieu.save()
            return JsonResponse({"success": True, "id": lieu.id})
        else:
            return JsonResponse({"success": False,
                                 "errors": form.errors})
    else:
        return JsonResponse({"erreur": "Aucune donnée POST"})

class LieuAjout(LoginRequiredMixin, CreateView):
    model = Lieu
    form_class = LieuForm
    template_name = 'avisstage/formulaires/lieu.html'

    # Retourne d'un JSON si requête AJAX
    def form_valid(self, form):
        if self.request.GET.get("format", "") == "json":
            self.object = form.save()
            return JsonResponse({"success": True,
                                 "id": self.object.id})
        else:
            super(LieuAjout, self).form_valid(form)

    def form_invalid(self, form):
        if self.request.GET.get("format", "") == "json":
            return JsonResponse({"success": False,
                                 "errors": form.errors})
        else:
            super(LieuAjout, self).form_valid(form)

# Passage d'un stage en mode public
@login_required
def publier_stage(request, pk):
    if request.method != "POST":
        return HttpResponseForbidden()
    stage = get_object_or_404(Stage, pk=pk)

    # Stage non possédé par l'utilisateur
    if stage.auteur != request.user.profil:
        return HttpResponseForbidden()

    # Mise à jour du statut
    if "publier" in request.POST:
        stage.public = True
    else:
        stage.public = False

    stage.save()
    
    return redirect(reverse("avisstage:stage", kwargs={"pk": pk}))

#
# FEEDBACK
#

@login_required
def feedback(request):
    if request.method == "POST":
        form = FeedbackForm(request.POST)
        if form.is_valid():
            objet   = form.cleaned_data['objet']
            header = "[From : %s <%s>]\n" % (request.user,
                                             request.user.email)
            message = header + form.cleaned_data['message']
            send_mail(
                "[experiENS] "+ objet,
                message,
                request.user.email,
                ['robin.champenois@ens.fr'],
                fail_silently=False,
            )
            if request.GET.get("format", None) == "json":
                return JsonResponse({"success": True})
            return redirect(reverse("avisstage:index"))
        else:
            if request.GET.get("format", None) == "json":
                return JsonResponse({"success": False,
                                     "errors": form.errors})
    else:
        form = FeedbackForm()
    raise Http404()


#
# STATISTIQUES
#

@login_required
@staff_member_required
def statistiques(request):
    nstages = Stage.objects.count()
    npubstages = Stage.objects.filter(public=True).count()
    nbymatiere_raw = Stage.objects.values('matieres__nom', 'public').annotate(scount=Count('matieres__nom'))
    nbymatiere = defaultdict(dict)
    for npm in nbymatiere_raw:
        nbymatiere[npm["matieres__nom"]]["publics" if npm["public"] else "drafts"] = npm["scount"]
    for mat, npm in nbymatiere.items():
        npm["matiere"] = mat
    nbymatiere = sorted(list(nbymatiere.values()), key=lambda npm: -npm.get("publics", 0))
    nbylength = [("Vide", Stage.objects.filter(len_avis_stage__lt=5).count(),
                  Stage.objects.filter(len_avis_lieux__lt=5).count()),
                 ("Court", Stage.objects.filter(len_avis_stage__lt=30, len_avis_stage__gt=4).count(),
                  Stage.objects.filter(len_avis_lieux__lt=30, len_avis_lieux__gt=4).count()),
                 ("Moyen", Stage.objects.filter(len_avis_stage__lt=100, len_avis_stage__gt=29).count(),
                  Stage.objects.filter(len_avis_lieux__lt=100, len_avis_lieux__gt=29).count()),
                 ("Long", Stage.objects.filter(len_avis_stage__gt=99).count(),
                  Stage.objects.filter(len_avis_lieux__gt=99).count())]
    nusers = Normalien.objects.count()
    nauts = Normalien.objects.filter(stages__isnull=False).distinct().count()
    nbyaut = Counter(Normalien.objects.filter(stages__isnull=False).annotate(scount=Count('stages')).values_list('scount', flat="True")).items()
    nlieux = Lieu.objects.filter(stages__isnull=False).distinct().count()
    return render(request, 'avisstage/moderation/statistiques.html',
                  {'num_stages': nstages,
                   'num_stages_pub': npubstages,
                   'num_par_matiere': nbymatiere,
                   'num_users': nusers,
                   'num_auteurs': nauts,
                   'num_par_auteur': nbyaut,
                   'num_lieux_utiles': nlieux,
                   'num_par_longueur': nbylength,
                  })

#
# Compte
#

class MesAdressesMixin(LoginRequiredMixin):
    slug_url_kwarg = "email"
    slug_field = "email"
    confirmed_only = False
    
    def get_queryset(self, *args, **kwargs):
        qs = self.request.user.email_address_set.all()
        if self.confirmed_only:
            qs = qs.filter(confirmed_at__isnull=False)
        return qs

def _send_confirm_mail(email, request):
    confirm_url = request.build_absolute_uri(
            reverse("avisstage:emails_confirme", kwargs={"key": email.key}))
    send_mail(
        "[ExperiENS] Confirmez votre adresse a-mail",
"""Bonjour,

Vous venez d'ajouter cette adresse e-mail à votre compte ExperiENS.

Pour la vérifier, merci de cliquer sur le lien suivant, ou de copier l'adresse dans votre navigateur :

    {confirm_url}

Cordialement,
L'équipe ExperiENS""".format(confirm_url=confirm_url),
        'experiens-nepasrepondre@eleves.ens.fr',
        [email.email],
        fail_silently=False,
    )
    return redirect(reverse("avisstage:emails_aconfirmer",
                            kwargs={"email": email.email}))
    
class MesParametres(LoginRequiredMixin, FormView):
    model = EmailAddress
    template_name = "avisstage/compte/parametres.html"
    form_class = AdresseEmailForm

    def get_form_kwargs(self, *args, **kwargs):
        kwargs = super().get_form_kwargs(*args, **kwargs)
        kwargs["_user"] = self.request.user
        return kwargs
   
    def form_valid(self, form):
        new = EmailAddress.objects.create_unconfirmed(
            form.cleaned_data["email"], self.request.user)
        return _send_confirm_mail(new, self.request)
    
class RendAdressePrincipale(MesAdressesMixin, SingleObjectMixin, View):
    model = EmailAddress
    confirmed_only = True
    
    def post(self, *args, **kwargs):
        if not hasattr(self, "object"):
            self.object = self.get_object()
        self.request.user.email = self.object.email
        self.request.user.save()
        return redirect(reverse("avisstage:parametres"))

class AdresseAConfirmer(MesAdressesMixin, DetailView):
    model = EmailAddress
    template_name = "avisstage/compte/aconfirmer.html"
    
class ReConfirmeAdresse(MesAdressesMixin, DetailView):
    model = EmailAddress

    def post(self, *args, **kwargs):
        email = self.get_object()
        if email.confirmed_at is None:
            return _send_confirm_mail(email, self.request)
        return redirect(reverse("avisstage:parametres"))

class ConfirmeAdresse(LoginRequiredMixin, View):
    def get(self, *args, **kwargs):
        try:
            email = EmailAddress.objects.confirm(self.kwargs["key"],
                                                 self.request.user, True)
        except Exception as e:
            raise Http404()
        messages.add_message(
            self.request, messages.SUCCESS,
            "L'adresse email {email} a bien été confirmée".format(email=email.email))
        return redirect(reverse("avisstage:parametres"))

class SupprimeAdresse(MesAdressesMixin, DeleteView):
    model = EmailAddress
    template_name = "avisstage/compte/email_supprime.html"
    success_url = reverse_lazy("avisstage:parametres")

    def get_queryset(self, *args, **kwargs):
        qs = super().get_queryset(*args, **kwargs)
        return qs.exclude(email=self.request.user.email)

class EnvoieLienMotDePasse(LoginRequiredMixin, View):
    def post(self, *args, **kwargs):
        form = ReinitMdpForm({"email": self.request.user.email})
        form.is_valid()
        form.save(
            email_template_name = 'avisstage/mails/reinit_mdp.html',
            from_email = 'experiens-nepasrepondre@eleves.ens.fr',
            subject_template_name = 'avisstage/mails/reinit_mdp.txt',
        )
        messages.add_message(
            self.request, messages.INFO,
            "Un mail a été envoyé à {email}. Merci de vérifier vos indésirables si vous ne le recevez pas bientôt".format(email=self.request.user.email)
        )
        return redirect(reverse("avisstage:parametres"))

class DefinirMotDePasse(PasswordResetConfirmView):
    template_name = "avisstage/compte/edit_mdp.html"
    success_url = reverse_lazy("avisstage:perso")

    def get_user(self, *args, **kwargs):
        user = super().get_user(*args, **kwargs)
        if self.request.user.is_authenticated and user != self.request.user:
            raise Http404("Ce token n'est pas valide pour votre compte")
        return user