from datetime import timedelta

from authens.models import CASAccount
from authens.signals import post_cas_connect
from taggit_autosuggest.managers import TaggableManager
from tinymce.models import HTMLField as RichTextField

from django.contrib.auth.models import User
from django.contrib.gis.db import models as geomodels
from django.db import models
from django.db.models.signals import post_save
from django.forms.widgets import DateInput
from django.template.defaultfilters import slugify
from django.urls import reverse
from django.utils import timezone
from django.utils.functional import cached_property
from django.utils.html import strip_tags

from .statics import (
    DEPARTEMENTS_DEFAUT,
    NIVEAU_SCOL_DICT,
    NIVEAU_SCOL_OPTIONS,
    PAYS_OPTIONS,
    TYPE_LIEU_DICT,
    TYPE_LIEU_OPTIONS,
    TYPE_STAGE_DICT,
    TYPE_STAGE_OPTIONS,
)
from .utils import choices_length, is_email_ens


def _default_cas_login():
    return (timezone.now() - timedelta(days=365)).date()


#
# Profil Normalien (extension du modèle User)
#


class Normalien(models.Model):
    user = models.OneToOneField(
        User, related_name="profil", on_delete=models.SET_NULL, null=True
    )

    # Infos spécifiques
    nom = models.CharField("Nom complet", max_length=255, blank=True)
    promotion = models.CharField("Promotion", max_length=40, blank=True)
    contactez_moi = models.BooleanField(
        "Inviter les visiteurs à me contacter",
        default=True,
        help_text="Affiche votre adresse e-mail principale sur votre profil public",
    )
    bio = models.TextField("À propos de moi", blank=True, default="")
    last_cas_login = models.DateField(default=_default_cas_login)

    class Meta:
        verbose_name = "Profil élève"
        verbose_name_plural = "Profils élèves"

    def __str__(self):
        return "%s (%s)" % (self.nom, self.user.username)

    # Liste des stages publiés
    def stages_publics(self):
        return self.stages.filter(public=True).order_by("-date_debut")

    def has_nonENS_email(self):
        return (
            self.user.email_address_set.exclude(confirmed_at__isnull=True)
            .exclude(email__endswith="ens.fr")
            .exclude(email__endswith="ens.psl.eu")
            .exists()
        )

    def nom_complet(self):
        if self.nom.strip():
            return self.nom
        return self.user.username

    @property
    def en_scolarite(self):
        return self.last_cas_login > (timezone.now() - timedelta(days=60)).date()

    @property
    def preferred_email(self):
        return self.user.email


# Hook à la création d'un nouvel utilisateur : information de base
def create_basic_user_profile(sender, instance, created, **kwargs):
    if created:
        profil, created = Normalien.objects.get_or_create(user=instance)

        if not created and profil.promotion != "":
            return

        if "@" in instance.username:
            profil.promotion = instance.username.split("@")[1]
            profil.save()


post_save.connect(create_basic_user_profile, sender=User)

# Hook d'authENS : information du CAS
def handle_cas_connection(sender, instance, created, cas_login, attributes, **kwargs):
    profil, created = Normalien.objects.get_or_create(user=instance)

    profil.last_cas_login = timezone.now().date()
    if not created:
        profil.save()
        return

    dirs = attributes.get("homeDirectory", "").split("/")
    if len(dirs) < 4:
        print("HomeDirectory invalide", dirs)
        return

    year = dirs[2]
    departement = dirs[3]

    dep = dict(DEPARTEMENTS_DEFAUT).get(departement.lower(), "")

    profil.promotion = "%s %s" % (dep, year)
    profil.nom = attributes.get("name", "")
    profil.save()


post_cas_connect.connect(handle_cas_connection, sender=User)

#
# Lieu de stage
#


class Lieu(models.Model):
    # Général
    nom = models.CharField("Nom de l'institution d'accueil", max_length=250)
    type_lieu = models.CharField(
        "Type de structure d'accueil",
        default="universite",
        choices=TYPE_LIEU_OPTIONS,
        max_length=choices_length(TYPE_LIEU_OPTIONS),
    )

    # Infos géographiques
    ville = models.CharField("Ville", max_length=200)
    pays = models.CharField(
        "Pays", choices=PAYS_OPTIONS, max_length=choices_length(PAYS_OPTIONS)
    )

    # Coordonnées
    # objects = geomodels.GeoManager() # Requis par GeoDjango
    coord = geomodels.PointField("Coordonnées", geography=True, srid=4326)

    # Type du lieu en plus joli
    @property
    def type_lieu_fancy(self):
        return TYPE_LIEU_DICT.get(self.type_lieu, ("lieu", False))[0]

    @property
    def type_lieu_fem(self):
        return TYPE_LIEU_DICT.get(self.type_lieu, ("lieu", False))[1]

    def __str__(self):
        return "%s (%s)" % (self.nom, self.ville)

    class Meta:
        verbose_name = "Lieu"
        verbose_name_plural = "Lieux"


#
# Matières des stages
#


class StageMatiere(models.Model):
    nom = models.CharField("Nom", max_length=30)
    slug = models.SlugField()

    class Meta:
        verbose_name = "Matière des stages"
        verbose_name_plural = "Matières des stages"

    def __str__(self):
        return self.nom


#
# Un stage
#


class Stage(models.Model):
    # Misc
    auteur = models.ForeignKey(
        Normalien, related_name="stages", on_delete=models.SET_NULL, null=True
    )
    public = models.BooleanField("Visible publiquement", default=False)
    date_creation = models.DateTimeField("Créé le", default=timezone.now)
    date_maj = models.DateTimeField("Mis à jour le", default=timezone.now)
    len_avis_stage = models.IntegerField("Longueur des avis de stage", default=0)
    len_avis_lieux = models.IntegerField("Longueur des avis de lieu", default=0)

    # Caractéristiques du stage
    sujet = models.CharField("Sujet", max_length=500)

    date_debut = models.DateField("Date de début", null=True)
    date_fin = models.DateField("Date de fin", null=True)

    type_stage = models.CharField(
        "Type",
        default="stage",
        choices=TYPE_STAGE_OPTIONS,
        max_length=choices_length(TYPE_STAGE_OPTIONS),
    )
    niveau_scol = models.CharField(
        "Année de scolarité",
        default="",
        choices=NIVEAU_SCOL_OPTIONS,
        max_length=choices_length(NIVEAU_SCOL_OPTIONS),
        blank=True,
    )

    thematiques = TaggableManager("Thématiques", blank=True)
    matieres = models.ManyToManyField(
        StageMatiere, verbose_name="Matière(s)", related_name="stages"
    )
    encadrants = models.CharField("Encadrant⋅e⋅s", max_length=500, blank=True)
    structure = models.CharField("Structure d'accueil", max_length=500, blank=True)

    # Avis
    lieux = models.ManyToManyField(
        Lieu, related_name="stages", through="AvisLieu", blank=True
    )

    # Affichage des avis ordonnés
    @property
    def avis_lieux(self):
        return self.avislieu_set.order_by("order")

    # Shortcut pour affichage rapide
    @property
    def lieu_principal(self):
        avis_lieux = self.avis_lieux
        if len(avis_lieux) == 0:
            return None
        return self.avis_lieux[0].lieu

    # Type du stage en plus joli
    @property
    def type_stage_fancy(self):
        return TYPE_STAGE_DICT.get(self.type_stage, ("stage", False))[0]

    @property
    def type_stage_fem(self):
        return TYPE_STAGE_DICT.get(self.type_stage, ("stage", False))[1]

    # Niveau scolaire en plus joli
    @property
    def niveau_scol_fancy(self):
        return NIVEAU_SCOL_DICT.get(self.niveau_scol, "")

    # Optimisation de requêtes
    @cached_property
    def all_lieux(self):
        return self.lieux.all()

    def get_absolute_url(self):
        return reverse("avisstage:stage", self)

    def __str__(self):
        return "%s (par %s)" % (self.sujet, self.auteur.user.username)

    def update_stats(self, save=True):
        def get_len(obj):
            length = 0
            avis = obj.avis_all
            for k, av in avis:
                length += len(av.split())
            length += len(obj.chapo.split())
            length += len(obj.les_plus.split())
            length += len(obj.les_moins.split())
            return length

        if self.avis_stage:
            self.len_avis_stage = get_len(self.avis_stage)
        self.len_avis_lieux = 0
        for avis in self.avislieu_set.all():
            self.len_avis_lieux += get_len(avis)

        if save:
            self.save()

    class Meta:
        verbose_name = "Stage"


#
# Les avis
#


class AvisStage(models.Model):
    stage = models.OneToOneField(
        Stage, related_name="avis_stage", on_delete=models.CASCADE
    )

    chapo = models.TextField("En quelques mots", blank=True)
    avis_ambiance = RichTextField("L'ambiance de travail", blank=True)
    avis_sujet = RichTextField("La mission", blank=True)
    avis_admin = RichTextField("Formalités et administration", blank=True)
    avis_prestage = RichTextField("Avant le stage", blank=True, default="")

    les_plus = models.TextField("Les plus de cette expérience", blank=True)
    les_moins = models.TextField("Les moins de cette expérience", blank=True)

    def __str__(self):
        return "Avis sur {%s} par %s" % (
            self.stage.sujet,
            self.stage.auteur.user.username,
        )

    # Liste des champs d'avis, couplés à leur nom (pour l'affichage)
    @property
    def avis_all(self):
        fields = ["avis_sujet", "avis_ambiance", "avis_admin", "avis_prestage"]
        return [
            (AvisStage._meta.get_field(field).verbose_name, getattr(self, field, ""))
            for field in fields
        ]


class AvisLieu(models.Model):
    stage = models.ForeignKey(Stage, on_delete=models.CASCADE)
    lieu = models.ForeignKey(Lieu, on_delete=models.CASCADE)
    order = models.IntegerField("Ordre", default=0)

    chapo = models.TextField("En quelques mots", blank=True)
    avis_lieustage = RichTextField("Les lieux de travail", blank=True)
    avis_pratique = RichTextField("S'installer - conseils pratiques", blank=True)
    avis_tourisme = RichTextField("Dans les parages", blank=True)

    les_plus = models.TextField("Les plus du lieu", blank=True)
    les_moins = models.TextField("Les moins du lieu", blank=True)

    class Meta:
        verbose_name = "Avis sur un lieu de stage"
        verbose_name_plural = "Avis sur un lieu de stage"

    def __str__(self):
        return "Avis sur {%s} par %s" % (self.lieu.nom, self.stage.auteur.user_id)

    # Liste des champs d'avis, couplés à leur nom (pour l'affichage)
    @property
    def avis_all(self):
        fields = ["avis_lieustage", "avis_pratique", "avis_tourisme"]
        return [
            (AvisLieu._meta.get_field(field).verbose_name, getattr(self, field, ""))
            for field in fields
        ]