import datetime
import calendar
import logging
from django.conf import settings
from django.utils import timezone
from django.shortcuts import redirect, reverse
from django.views import generic
from django.core.mail import send_mail
from django.forms import modelformset_factory
from django.contrib import messages
from utilisateurs.mixins import MaraudeurMixin
from .models import (Maraude, Maraudeur,
                     CompteRendu,
                     Lieu,
                     Planning,)
from .notes import Signalement
from .forms import (RencontreForm,
                    ObservationInlineFormSet,
                    MaraudeHiddenDateForm, MonthSelectForm,
                    NoteForm, AppelForm, SignalementForm,
                    SendMailForm)
from notes.mixins import NoteFormMixin

logger = logging.getLogger(__name__)

# How many month shall we look back
NUMBER_OF_MONTH_BACKWARDS = 1  # Must be lower than 12 !


def derniers_sujets_rencontres():
    """ Renvoie le 'set' des sujets rencontrés dans les deux dernières maraudes """
    sujets = set()
    for cr in list(CompteRendu.objects.filter(heure_fin__isnull=False))[-2:]:
        for obs in cr.get_observations():
            sujets.add(obs.sujet)
    return list(sujets)


def derniers_sujets_signales():
    def recent_filter(qs):
        current_date = timezone.localtime(timezone.now()).date()
        recent_dates = [(current_date.month, current_date.year)]
        for i in range(1, NUMBER_OF_MONTH_BACKWARDS + 1):
            prev_month, prev_year = current_date.month - i, current_date.year
            if prev_month <= 0:
                prev_month += 12
                prev_year -= 1
            recent_dates.append(
                (prev_month, prev_year)
                )

        # Select recent ones
        filtered = []
        for month, year in recent_dates:
            filtered += list(qs.filter(created_date__year=year, created_date__month=month))
        return filtered

    return recent_filter(Signalement.objects.all())


class IndexView(NoteFormMixin, MaraudeurMixin, generic.TemplateView):

    template_name = "maraudes/index.html"

    # NoteFormMixin
    forms = {
        'note': NoteForm,
        'appel': AppelForm,
        'signalement': SignalementForm,
    }

    def get_initial(self):
        now = timezone.localtime(timezone.now())
        return {'created_date': now.date(),
                'created_time': now.time()}

    def get_success_url(self):
        return reverse('maraudes:index')

    def get_context_data(self, *args, **kwargs):
        context = super().get_context_data(**kwargs)
        context['prochaine_maraude'] = Maraude.objects.get_next_of(self.request.user)
        context['derniers_sujets_rencontres'] = derniers_sujets_rencontres()
        context['derniers_sujets_signales'] = derniers_sujets_signales()

        if self.request.user.is_superuser:
            context['missing_cr'] = CompteRendu.objects.get_queryset().filter(
                    heure_fin__isnull=True,
                    date__lt=timezone.localtime(timezone.now()).date()
                )
        return context


# COMPTE-RENDU DE MARAUDE


def redirect_to_current_compterendu(request):
    maraude_en_cours = Maraude.objects.get_in_progress()

    if not maraude_en_cours:
        messages.warning(request, "Il n'y a actuellement aucune maraude en cours !")
        return redirect("maraudes:index")

    kwargs = {'pk': maraude_en_cours.pk}
    if not maraude_en_cours.est_terminee():
        return redirect("maraudes:create", **kwargs)
    else:
        return redirect("notes:details-maraude", **kwargs)


class CompteRenduCreateView(MaraudeurMixin, generic.DetailView):
    """ Vue pour la création d'un compte-rendu de maraude """

    model = CompteRendu
    template_name = "maraudes/compterendu.html"
    context_object_name = "maraude"

    form = None
    inline_formset = None

    def get_forms(self, *args, initial=None):
        self.form = RencontreForm(*args,
                                  initial=initial)
        self.inline_formset = ObservationInlineFormSet(
                                    *args,
                                    instance=self.form.instance
                                )

    def post(self, request, **kwargs):
        self.get_forms(request.POST, request.FILES)
        if self.form.has_changed():
            if not self.form.is_valid() or not self.inline_formset.is_valid():
                return self.get(request, new_form=False)
            rencontre = self.form.save(commit=False)
            rencontre.maraude = self.get_object()
            rencontre.save()
            self.inline_formset.save()

        return redirect('maraudes:create', pk=self.get_object().pk)

    def get(self, request, new_form=True, *args, **kwargs):

        def calculate_end_time(debut, duree):
            end_minute = debut.minute + duree
            hour = debut.hour + end_minute // 60
            if hour >= 24:
                hour -= 24
            elif hour < 0:
                hour += 24
            minute = end_minute % 60
            return datetime.time(
                                hour,
                                minute,
                                0
                        )
        if new_form:
            last_rencontre = self.get_object().rencontres.last()
            if last_rencontre:
                initial = {
                    'lieu': last_rencontre.lieu,
                    'heure_debut': calculate_end_time(
                                        last_rencontre.heure_debut,
                                        last_rencontre.duree),
                }
            else:
                initial = {
                    'heure_debut': self.get_object().heure_debut
                }
            self.get_forms(initial=initial)
        return super().get(request, *args, **kwargs)

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['form'] = self.form
        context['inline_formset'] = self.inline_formset
        context['rencontres'] = self.get_object().rencontres.order_by("-heure_debut")
        # Link there so that "Compte-rendu" menu item is not disabled
        context['prochaine_maraude'] = self.object
        return context


class FinalizeView(MaraudeurMixin,
                   generic.detail.SingleObjectMixin,
                   generic.edit.FormView):

    template_name = "maraudes/finalize.html"
    model = Maraude
    form_class = SendMailForm
    success_url = "/maraudes/"

    def get(self, *args, **kwargs):
        if bool(self.request.GET.get("no_mail", False)):
            messages.warning(self.request, "Aucun compte-rendu n'a été envoyé !")
            return self.finalize()
        return super().get(*args, **kwargs)

    def get_initial(self):
        maraude = self.get_object()
        objet = "%s - Compte-rendu de maraude" % maraude.date
        sujets_rencontres = set()
        for r in maraude.rencontres.all():
            for s in r.get_sujets():
                sujets_rencontres.add(s)
        message = "Nous avons rencontré : " + ", ".join(map(str, sujets_rencontres)) + ".\n\n"
        return {
            "subject": objet,
            "message": message
        }

    def finalize(self):
        maraude = self.get_object()
        maraude.heure_fin = timezone.localtime(timezone.now()).time()
        maraude.save()
        return redirect(self.get_success_url())

    def form_valid(self, form):
        # Send mail
        maraude = self.get_object()
        recipients = Maraudeur.objects.filter(
                                                is_active=True
                                            ).exclude(
                                                pk__in=(maraude.referent.pk,
                                                        maraude.binome.pk)
                                            )
        result = send_mail(
            form.cleaned_data['subject'],
            form.cleaned_data['message'],
            maraude.referent.email,
            [m.email for m in recipients],
        )

        if result == 1:
            if settings.DEBUG:
                messages.warning(self.request, "En mode DEBUG, le compte-rendu n'est pas réellement envoyé !")
            else:
                messages.success(self.request, "Le compte-rendu a été transmis à %s" % ", ".join(map(str, recipients)))
        else:
            messages.error(self.request, "Erreur lors de l'envoi du message !")
        return self.finalize()

    def get_context_data(self, **kwargs):
        self.object = self.get_object()
        context = super().get_context_data(**kwargs)
        if self.object.est_terminee is True:
            context['form'] = None  # Useless form
            return context
        # Link there so that "Compte-rendu" menu item is not disabled
        context['prochaine_maraude'] = self.object
        return context


class PlanningView(MaraudeurMixin, generic.TemplateView):
    """ Vue d'édition du planning des maraudes """

    template_name = "maraudes/planning.html"

    def _parse_request(self):
        self.current_date = datetime.date.today()
        self.month = int(
            self.request.GET.get(
                'month', 
                default=self.current_date.month))
        self.year = int(
            self.request.GET.get(
                'year',
                default=self.current_date.year))

    def _calculate_initials(self):
        self._parse_request()
        self.initials = []
        for day, time in Planning.get_maraudes_days_for_month(self.year, self.month):
            date = datetime.date(self.year, self.month, day)
            try:
                Maraude.objects.get(date=date)
            except Maraude.DoesNotExist:
                self.initials.append({
                        'date': date,
                        'heure_debut': time,
                        })

    def get_queryset(self):
        return Maraude.objects.filter(
                        date__month=self.month,
                        date__year=self.year,
                    )

    def get_formset(self, *args):
        self._calculate_initials()
        return modelformset_factory(
                            Maraude,
                            form=MaraudeHiddenDateForm,
                            extra=len(self.initials),
                        )(
                            *args,
                            queryset=self.get_queryset(),
                            initial=self.initials
                        )

    def post(self, request):
        formset = self.get_formset(request.POST, request.FILES)
        for form in formset.forms:
            if form.is_valid():
                form.save()
            else:
                logger.info("Form was ignored ! (%s)" % (form.errors.as_data()))
        return redirect('maraudes:planning')

    def get(self, request, *args, **kwargs):
        self.formset = self.get_formset()
        return super().get(request, *args, **kwargs)

    def get_weeks(self):
        """ List of (day, form) tuples, split by weeks """

        def form_generator(forms):
            """ Yields None until the generator receives the day of
                next form.
            """
            forms = iter(sorted(forms, key=lambda f: f.initial['date']))
            day = yield
            for form in forms:
                while day != form.initial['date'].day:
                    day = yield None
                day = yield form

            while True:  # Avoid StopIteration
                day = yield None

        form_or_none = form_generator(self.formset)
        form_or_none.send(None)

        return [
                [(day, form_or_none.send(day)) for day in week]
                for week in calendar.monthcalendar(self.year, self.month)
            ]

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['weekdays'] = ["Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi", "Dimanche"]
        context['weeks'] = self.get_weeks()

        context['formset'] = self.formset
        context['select_form'] = MonthSelectForm(month=self.month, year=self.year)
        context['month'], context['year'] = self.month, self.year
        return context


class LieuCreateView(generic.edit.CreateView):
    """ Vue de création d'un lieu """

    model = Lieu
    template_name = "maraudes/lieu_create.html"
    fields = "__all__"
    success_url = "/maraudes/"

    def post(self, request, *args, **kwargs):
        if 'next' in self.request.POST:
            self.success_url = self.request.POST["next"]
        return super().post(self, request, *args, **kwargs)

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        try:
            context['next'] = self.request.GET['next']
        except KeyError:
            context['next'] = None
        return context
