from datetime import date, timedelta

import nh3
from dal import autocomplete
from dal.widgets import WidgetMixin
from tinymce.widgets import TinyMCE

from django import forms
from django.contrib import messages
from django.contrib.postgres.search import SearchQuery, SearchVector
from django.core.exceptions import ObjectDoesNotExist
from django.db.models import Count, Q
from django.utils.dates import MONTHS

from city_ch_autocomplete.forms import CityChField, CityChMixin

from common.choices import PROVENANCE_DESTINATION_CHOICES
from .models import (
    Bilan, Contact, Document, EquipeChoices, Famille, Formation, Intervenant,
    Niveau, Personne, Prestation, Region, Rapport, Role, Service, Suivi, Utilisateur,
)
from .utils import format_nom_prenom, ANTICIPATION_POUR_DEBUT_SUIVI


class BootstrapMixin:
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        for ffield in self.fields.values():
            if (isinstance(ffield.widget, (PickSplitDateTimeWidget, PickDateWidget, WidgetMixin)) or
                    getattr(ffield.widget, '_bs_enabled', False)):
                continue
            elif isinstance(ffield.widget, (forms.Select, forms.NullBooleanSelect)):
                self.add_attr(ffield.widget, 'form-select')
            elif isinstance(ffield.widget, (forms.CheckboxInput, forms.RadioSelect)):
                self.add_attr(ffield.widget, 'form-check-input')
            else:
                self.add_attr(ffield.widget, 'form-control')

    @staticmethod
    def add_attr(widget, class_name):
        if 'class' in widget.attrs:
            widget.attrs['class'] += f' {class_name}'
        else:
            widget.attrs.update({'class': class_name})


class BootstrapChoiceMixin:
    """
    Mixin to customize choice widgets to set 'form-check' on container and
    'form-check-input' on sub-options.
    """
    _bs_enabled = True

    def get_context(self, *args, **kwargs):
        context = super().get_context(*args, **kwargs)
        if 'class' in context['widget']['attrs']:
            context['widget']['attrs']['class'] += ' form-check'
        else:
            context['widget']['attrs']['class'] = 'form-check'
        return context

    def create_option(self, *args, attrs=None, **kwargs):
        attrs = attrs or {}
        if 'class' in attrs:
            attrs['class'] += ' form-check-input'
        else:
            attrs.update({'class': 'form-check-input'})
        return super().create_option(*args, attrs=attrs, **kwargs)


class BSRadioSelect(BootstrapChoiceMixin, forms.RadioSelect):
    pass


class ReadOnlyableMixin:
    def __init__(self, *args, readonly=False, **kwargs):
        self.readonly = readonly
        super().__init__(*args, **kwargs)
        if self.readonly:
            for field in self.fields.values():
                field.disabled = True


class BSCheckboxSelectMultiple(forms.CheckboxSelectMultiple):
    """
    Custom widget to set 'form-check' on container and 'form-check-input' on sub-options.
    """
    _bs_enabled = True

    def get_context(self, *args, **kwargs):
        context = super().get_context(*args, **kwargs)
        context['widget']['attrs']['class'] = 'form-check'
        return context

    def create_option(self, *args, attrs=None, **kwargs):
        attrs = attrs.copy() if attrs else {}
        if 'class' in attrs:
            attrs['class'] += ' form-check-input'
        else:
            attrs.update({'class': 'form-check-input'})
        return super().create_option(*args, attrs=attrs, **kwargs)


class RichTextField(forms.CharField):
    widget = TinyMCE

    def __init__(self, *args, **kwargs):
        kwargs['widget'] = self.widget
        super().__init__(*args, **kwargs)

    def clean(self, value):
        value = super().clean(value)
        return nh3.clean(
            value, tags={'p', 'br', 'b', 'strong', 'u', 'i', 'em', 'ul', 'li'}
        )


class HMDurationField(forms.DurationField):
    """A duration field taking HH:MM as input."""
    widget = forms.TextInput(attrs={'placeholder': 'hh:mm'})

    def to_python(self, value):
        if value in self.empty_values or isinstance(value, timedelta):
            return super().to_python(value)
        value += ':00'  # Simulate seconds
        return super().to_python(value)

    def prepare_value(self, value):
        if isinstance(value, timedelta):
            seconds = value.days * 24 * 3600 + value.seconds
            hours = seconds // 3600
            minutes = seconds % 3600 // 60
            value = '{:02d}:{:02d}'.format(hours, minutes)
        return value


class PickDateWidget(forms.DateInput):
    class Media:
        js = [
            'admin/js/core.js',
            'admin/js/calendar.js',
            # Include the Django 3.2 version without today link.
            'js/DateTimeShortcuts.js',
        ]

    def __init__(self, attrs=None, **kwargs):
        attrs = {'class': 'vDateField vDateField-rounded', 'size': '10', **(attrs or {})}
        super().__init__(attrs=attrs, **kwargs)


class PickSplitDateTimeWidget(forms.SplitDateTimeWidget):
    def __init__(self, attrs=None):
        widgets = [PickDateWidget, forms.TimeInput(attrs={'class': 'TimeField'}, format='%H:%M')]
        forms.MultiWidget.__init__(self, widgets, attrs)


class ContactForm(CityChMixin, BootstrapMixin, forms.ModelForm):
    service = forms.ModelChoiceField(queryset=Service.objects.exclude(sigle='CRNE'), required=False)
    roles = forms.ModelMultipleChoiceField(
        label='Rôles',
        queryset=Role.objects.exclude(est_famille=True).order_by('nom'),
        widget=BSCheckboxSelectMultiple,
        required=False
    )
    city_auto = CityChField(required=False)
    postal_code_model_field = 'npa'
    city_model_field = 'localite'

    class Meta:
        model = Contact
        fields = [
            'nom', 'prenom', 'profession', 'service', 'roles', 'rue', 'city_auto', 'npa', 'localite',
            'tel_prof', 'tel_prive', 'email', 'remarque'
        ]


class RoleForm(BootstrapMixin, forms.ModelForm):
    class Meta:
        model = Role
        fields = '__all__'


class ServiceForm(BootstrapMixin, forms.ModelForm):
    class Meta:
        model = Service
        fields = ['sigle', 'nom_complet']

    def clean_sigle(self):
        return self.cleaned_data['sigle'].upper()


class FormationForm(BootstrapMixin, ReadOnlyableMixin, forms.ModelForm):
    class Meta:
        model = Formation
        exclude = ('personne',)


class GroupSelectMultiple(BSCheckboxSelectMultiple):
    option_template_name = "widgets/group_checkbox_option.html"

    def create_option(self, name, value, *args, **kwargs):
        try:
            help_ = value.instance.groupinfo.description
        except ObjectDoesNotExist:
            help_= ''
        return {
            **super().create_option(name, value, *args, **kwargs),
            'help': help_,
        }


class UtilisateurForm(BootstrapMixin, forms.ModelForm):
    roles = forms.ModelMultipleChoiceField(
        label="Rôles",
        queryset=Role.objects.exclude(
            Q(est_famille=True) | Q(nom__in=['Personne significative', 'Référent'])
        ).order_by('nom'),
        widget=BSCheckboxSelectMultiple,
        required=False
    )

    class Meta:
        model = Utilisateur
        fields = [
            'nom', 'prenom', 'sigle', 'profession', 'tel_prof', 'tel_prive', 'username',
            'taux_activite', 'decharge', 'email', 'equipe', 'roles', 'groups'
        ]
        widgets = {'groups': GroupSelectMultiple}
        labels = {'profession': 'Titre'}

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.fields['groups'].queryset = self.fields['groups'].queryset.order_by('name')


class PersonneForm(CityChMixin, BootstrapMixin, ReadOnlyableMixin, forms.ModelForm):
    role = forms.ModelChoiceField(label="Rôle", queryset=Role.objects.filter(est_famille=True), required=True)

    city_auto = CityChField(required=False)
    postal_code_model_field = 'npa'
    city_model_field = 'localite'

    class Meta:
        model = Personne
        fields = (
            'role', 'nom', 'prenom', 'date_naissance', 'genre', 'filiation',
            'rue', 'npa', 'localite', 'telephone',
            'email', 'profession', 'pays_origine', 'decedee',
            'allergies', 'remarque', 'remarque_privee',
        )
        widgets = {
            'date_naissance': PickDateWidget,
        }
        labels = {
            'remarque_privee': 'Remarque privée (pas imprimée)',
        }

    def __init__(self, **kwargs):
        self.famille = kwargs.pop('famille', None)
        role_id = kwargs.pop('role', None)

        super().__init__(**kwargs)
        if self.famille:
            self.initial['nom'] = self.famille.nom
            self.initial['rue'] = self.famille.rue
            self.initial['npa'] = self.famille.npa
            self.initial['localite'] = self.famille.localite
            self.initial['telephone'] = self.famille.telephone

        if role_id:
            if role_id == 'ps':  # personne significative
                excl = Role.ROLES_PARENTS + ['Enfant suivi', 'Enfant non-suivi']
                self.fields['role'].queryset = Role.objects.filter(
                    famille=True
                ).exclude(nom__in=excl)
            elif role_id == 'parent':
                self.fields['role'].queryset = Role.objects.filter(nom__in=Role.ROLES_PARENTS)
            else:
                self.role = Role.objects.get(pk=role_id)

                if self.role.nom in Role.ROLES_PARENTS:
                    self.fields['role'].queryset = Role.objects.filter(nom=self.role.nom)
                    self.initial['genre'] = 'M' if self.role.nom == 'Père' else 'F'
                elif self.role.nom in ['Enfant suivi', 'Enfant non-suivi']:
                    self.fields['role'].queryset = Role.objects.filter(nom__in=['Enfant suivi', 'Enfant non-suivi'])
                    self.fields['profession'].label = 'Profession/École'
                self.initial['role'] = self.role.pk
        else:
            if self.instance.pk:
                famille = self.instance.famille
                # Cloisonnement des choix pour les rôles en fonction du rôle existant
                if self.instance.role.nom in Role.ROLES_PARENTS:
                    self.fields['role'].queryset = Role.objects.filter(nom__in=Role.ROLES_PARENTS)
                elif self.instance.role.nom in ['Enfant suivi', 'Enfant non-suivi']:
                    self.fields['role'].queryset = Role.objects.filter(nom__in=['Enfant suivi', 'Enfant non-suivi'])
                else:
                    excl = ['Enfant suivi', 'Enfant non-suivi']
                    if len(famille.parents()) == 2:
                        excl.extend(Role.ROLES_PARENTS)
                    self.fields['role'].queryset = Role.objects.filter(
                        famille=True
                    ).exclude(nom__in=excl)

    def clean_nom(self):
        return format_nom_prenom(self.cleaned_data['nom'])

    def clean_prenom(self):
        return format_nom_prenom(self.cleaned_data['prenom'])

    def clean_localite(self):
        localite = self.cleaned_data.get('localite')
        if localite and localite[0].islower():
            localite = localite[0].upper() + localite[1:]
        return localite

    def clean(self):
        cleaned_data = super().clean()
        if cleaned_data['decedee'] and cleaned_data['role'] == 'Enfant suivi':
            raise forms.ValidationError('Un enfant décédé ne peut pas être «Enfant suivi»')
        return cleaned_data

    def save(self, **kwargs):
        if self.instance.pk is None:
            self.instance.famille = self.famille
            pers = Personne.objects.create_personne(**self.instance.__dict__)
        else:
            pers = super().save(**kwargs)
            pers = Personne.objects.add_formation(pers)
        return pers


class AgendaFormBase(BootstrapMixin, forms.ModelForm):
    destination = forms.ChoiceField(choices=PROVENANCE_DESTINATION_CHOICES, required=False)

    class Meta:
        fields = (
            'date_demande', 'date_debut_evaluation', 'date_fin_evaluation',
            'date_debut_suivi', 'date_fin_suivi', 'motif_fin_suivi', 'destination'
        )
        widgets = {field: PickDateWidget for field in fields[:-2]}

    def __init__(self, *args, **kwargs):
        destination = kwargs.pop('destination', '')
        self.request = kwargs.pop('request', None)
        super().__init__(*args, **kwargs)
        if self.instance.date_debut_suivi is not None or self.instance.motif_fin_suivi:
            for fname in ('date_demande', 'date_debut_evaluation', 'date_fin_evaluation'):
                self.fields[fname].disabled = True
        if self.instance.motif_fin_suivi:
            self.fields['date_debut_suivi'].disabled = True
        else:
            # Choix 'Autres' obsolète (#435), pourrait être supprimé quand plus référencé
            self.fields['motif_fin_suivi'].choices = [
                ch for ch in self.fields['motif_fin_suivi'].choices if ch[0] != 'autres'
            ]

        self.fields['destination'].choices = [('', '------')] + [
            ch for ch in PROVENANCE_DESTINATION_CHOICES if (ch[0] != 'autre' or destination == 'autre')
        ]
        self.initial['destination'] = destination

    def clean(self):
        cleaned_data = super().clean()

        # Check date chronology
        date_preced = None
        for field_name in self._meta.fields[:-2]:
            dt = cleaned_data.get(field_name)
            if not dt:
                continue
            if date_preced and dt < date_preced:
                raise forms.ValidationError(
                    "La date «{}» ne respecte pas l’ordre chronologique!".format(self.fields[field_name].label)
                )
            date_preced = dt

        # Check mandatory dates
        workflow = self._meta.model.WORKFLOW
        for field, etape in reversed((workflow.items())):
            if field == 'archivage':
                continue
            date_etape = cleaned_data.get(etape.date_nom())
            etape_courante = etape
            etape_preced_oblig = workflow[etape.preced_oblig]
            date_preced_oblig = cleaned_data.get(etape_preced_oblig.date_nom())
            while True:
                etape_preced = workflow[etape_courante.precedente]
                date_preced = cleaned_data.get(etape_preced.date_nom())
                if date_preced is None and etape_courante.num > 1:
                    etape_courante = etape_preced
                else:
                    break
            if date_etape and date_preced_oblig is None:
                raise forms.ValidationError("La date «{}» est obligatoire".format(etape_preced_oblig.nom))

        # Check dates out of range
        for field_name in self.changed_data:
            value = self.cleaned_data.get(field_name)
            if isinstance(value, date):
                if value > date.today():
                    if field_name in ['date_debut_suivi', 'date_debut_evaluation', 'date_fin_evaluation']:
                        if value > date.today() + timedelta(days=ANTICIPATION_POUR_DEBUT_SUIVI):
                            self.add_error(
                                field_name,
                                forms.ValidationError(
                                    "La saisie de cette date ne peut être "
                                    f"anticipée de plus de {ANTICIPATION_POUR_DEBUT_SUIVI} jours !")
                            )
                    else:
                        self.add_error(field_name, forms.ValidationError("La saisie anticipée est impossible !"))
                elif not Prestation.check_date_allowed(self.request.user, value):
                    if self.request and self.request.user.has_perm('aemo.change_famille'):
                        messages.warning(
                            self.request,
                            'Les dates saisies peuvent affecter les statistiques déjà communiquées !'
                        )
                    else:
                        self.add_error(
                            field_name,
                            forms.ValidationError("La saisie de dates pour le mois précédent n’est pas permise !")
                        )

        ddebut = cleaned_data.get('date_debut_suivi')
        dfin = cleaned_data.get('date_fin_suivi')
        motif = cleaned_data.get('motif_fin_suivi')
        dest = cleaned_data.get('destination')

        if ddebut and dfin and motif and dest:  # dossier terminé
            return cleaned_data
        elif ddebut and (dfin is None or motif == '' or dest == ''):  # suivi en cours
            if any([dfin, motif, dest]):
                raise forms.ValidationError(
                    "Les champs «Fin de l'accompagnement», «Motif de fin» et «Destination» "
                    "sont obligatoires pour fermer le dossier."
                )
        elif ddebut is None and dfin is None:  # evaluation
            if motif != '':  # abandon
                cleaned_data['date_fin_suivi'] = date.today()
        return cleaned_data

    def save(self):
        instance = super().save()
        if instance.date_fin_suivi:
            instance.famille.destination = self.cleaned_data['destination']
            instance.famille.save()
        return instance


class PrestationRadioSelect(BSRadioSelect):
    option_template_name = 'widgets/prestation_radio.html'


class PrestationForm(BootstrapMixin, forms.ModelForm):
    class Meta:
        model = Prestation
        fields = ['date_prestation', 'duree', 'texte', 'manque', 'fichier', 'intervenants']
        widgets = {
            'date_prestation': PickDateWidget,
            'intervenants': BSCheckboxSelectMultiple,
            'lib_prestation': PrestationRadioSelect,
        }
        labels = {
            'lib_prestation': 'Prestation',
        }
        field_classes = {
            'duree': HMDurationField,
            'texte': RichTextField,
        }

    def __init__(self, *args, famille=None, user, **kwargs):
        self.user = user
        self.famille = famille
        super().__init__(*args, **kwargs)
        self.fields['intervenants'].queryset = self.defaults_intervenants()
        if famille:
            intervenants = list(famille.suivi.intervenants.all())
            if len(intervenants):
                if self.user not in intervenants:
                    intervenants.insert(0, self.user)
                self.fields['intervenants'].choices = [(i.pk, i.nom_prenom) for i in intervenants]

    def defaults_intervenants(self):
        return Utilisateur.intervenants()

    def clean_date_prestation(self):
        date_prestation = self.cleaned_data['date_prestation']
        today = date.today()
        if date_prestation > today:
            raise forms.ValidationError("La saisie anticipée est impossible !")

        if not Prestation.check_date_allowed(self.user, date_prestation):
            raise forms.ValidationError(
                "La saisie des prestations des mois précédents est close !"
            )
        return date_prestation

    def clean_texte(self):
        texte = self.cleaned_data['texte']
        for snip in ('<p></p>', '<p><br></p>', '<p> </p>'):
            while texte.startswith(snip):
                texte = texte[len(snip):].strip()
        for snip in ('<p></p>', '<p><br></p>', '<p> </p>'):
            while texte.endswith(snip):
                texte = texte[:-len(snip)].strip()
        return texte


class DocumentUploadForm(BootstrapMixin, forms.ModelForm):
    class Meta:
        model = Document
        fields = '__all__'
        widgets = {'famille': forms.HiddenInput}
        labels = {'fichier': ''}


class RapportEditForm(BootstrapMixin, forms.ModelForm):
    class Meta:
        model = Rapport
        exclude = ['famille', 'auteur']
        widgets = {
            'famille': forms.HiddenInput,
            'date': PickDateWidget,
            'pres_interv': BSCheckboxSelectMultiple,
            'sig_interv': BSCheckboxSelectMultiple,
        }
        field_classes = {
            'situation': RichTextField,
            'projet': RichTextField,
            'observations': RichTextField,
        }
    field_order = [
        'date', 'pres_interv', 'situation', 'observations', 'projet', 'sig_interv',
    ]

    def __init__(self, user=None, **kwargs):
        super().__init__(**kwargs)
        if 'sig_interv' in self.fields:
            interv_qs = Utilisateur.objects.filter(
                pk__in=kwargs['initial']['famille'].suivi.intervenant_set.actifs(
                    self.instance.date or date.today()
                ).values_list('intervenant', flat=True)
            )
            self.fields['pres_interv'].queryset = interv_qs
            self.fields['sig_interv'].queryset = interv_qs


class MonthSelectionForm(BootstrapMixin, forms.Form):
    mois = forms.ChoiceField(choices=((mois_idx, MONTHS[mois_idx]) for mois_idx in range(1, 13)))
    annee = forms.ChoiceField(
        label='Année',
        choices=((2019, 2019),
                 (2020, 2020),
                 (2021, 2021),
                 (2022, 2022),
                 (2023, 2023),
                 (2024, 2024))
    )


class DateYearForm(forms.Form):
    year = forms.ChoiceField(choices=[(str(y), str(y)) for y in range(2020, date.today().year + 1)])

    def __init__(self, data=None, **kwargs):
        if not data:
            data = {'year': date.today().year}
        super().__init__(data, **kwargs)


class ContactExterneAutocompleteForm(forms.ModelForm):

    class Meta:
        model = Personne
        fields = ('reseaux',)
        widgets = {'reseaux': autocomplete.ModelSelect2Multiple(url='contact-externe-autocomplete')}
        labels = {'reseaux': 'Contacts'}


class ContactFilterForm(forms.Form):
    service = forms.ModelChoiceField(label="Service", queryset=Service.objects.all(), required=False)
    role = forms.ModelChoiceField(label="Rôle", queryset=Role.objects.exclude(est_famille=True), required=False)
    texte = forms.CharField(
        widget=forms.TextInput(attrs={'placeholder': 'Recherche…', 'autocomplete': 'off'}),
        required=False
    )
    sort_by = forms.CharField(widget=forms.HiddenInput(), required=False)

    sort_by_mapping = {
        'nom': ['nom', 'prenom'],
        'service': ['service'],
        'role': ['roles__nom'],
        'activite': ['profession'],
    }

    def filter(self, contacts):
        if self.cleaned_data['service']:
            contacts = contacts.filter(service=self.cleaned_data['service'])
        if self.cleaned_data['role']:
            contacts = contacts.filter(roles=self.cleaned_data['role'])
        if self.cleaned_data['texte']:
            contacts = contacts.filter(nom__icontains=self.cleaned_data['texte'])
        if self.cleaned_data['sort_by']:
            order_desc = self.cleaned_data['sort_by'].startswith('-')
            contacts = contacts.order_by(*([
                ('-' if order_desc else '') + key
                for key in self.sort_by_mapping.get(self.cleaned_data['sort_by'].strip('-'), [])
            ]))
        return contacts


class FamilleAdresseForm(CityChMixin, BootstrapMixin, forms.ModelForm):
    city_auto = CityChField(required=False)
    postal_code_model_field = 'npa'
    city_model_field = 'localite'

    class Meta:
        model = Famille
        fields = ('rue', 'npa', 'localite')

    def __init__(self, **kwargs):
        self.famille = kwargs.pop('famille', None)
        super().__init__(**kwargs)

        membres = [
            (m.pk, f"{m.nom_prenom} ({m.role}), {m.adresse}")
            for m in self.famille.membres.all()
        ]
        self.fields['membres'] = forms.MultipleChoiceField(
            widget=BSCheckboxSelectMultiple,
            choices=membres,
            required=False
        )


class FamilleForm(BootstrapMixin, ReadOnlyableMixin, forms.ModelForm):
    equipe = forms.ChoiceField(label="Équipe", choices=[('', '------')] + Suivi.EQUIPES_CHOICES[:2])

    # Concernant 'npa' & 'location', ils sont en lecture seule ici => pas besoin de faire de l'autocomplete
    class Meta:
        model = Famille
        fields = ['nom', 'rue', 'npa', 'localite', 'telephone', 'region', 'autorite_parentale',
                  'monoparentale', 'statut_marital', 'connue', 'accueil', 'garde',
                  'provenance', 'statut_financier']

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        if self.instance and self.instance.pk:
            self.fields['rue'].widget.attrs['readonly'] = True
            self.fields['npa'].widget.attrs['readonly'] = True
            self.fields['localite'].widget.attrs['readonly'] = True
            self.initial['equipe'] = kwargs['instance'].suivi.equipe
        self.fields['region'] = forms.ModelChoiceField(
            label='Région',
            queryset=Region.objects.order_by('nom'),
            required=False,
            disabled=self.fields['region'].disabled,
            widget=forms.Select(attrs={'class': 'form-select'})
        )
        self.fields['provenance'].choices = [
            ch for ch in self.fields['provenance'].choices
            if (ch[0] != 'autre' or self.instance.provenance == 'autre')
        ]

    def save(self, **kwargs):
        famille = super().save(**kwargs)
        if famille.suivi.equipe != self.cleaned_data['equipe']:
            famille.suivi.equipe = self.cleaned_data['equipe']
            famille.suivi.save()
        return famille

    def clean_nom(self):
        return format_nom_prenom(self.cleaned_data['nom'])

    def clean_localite(self):
        localite = self.cleaned_data.get('localite')
        if localite and localite[0].islower():
            localite = localite[0].upper() + localite[1:]
        return localite


class FamilleCreateForm(CityChMixin, FamilleForm):
    motif_detail = forms.CharField(
        label="Motif de la demande", widget=forms.Textarea(), required=False
    )
    city_auto = CityChField(required=False)
    postal_code_model_field = 'npa'
    city_model_field = 'localite'
    field_order = ['nom', 'rue', 'city_auto']

    class Meta(FamilleForm.Meta):
        exclude = ['typ', 'destination']

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields['provenance'].choices = [
            ch for ch in self.fields['provenance'].choices if ch[0] != 'autre'
        ]

    def save(self, **kwargs):
        famille = self._meta.model.objects.create_famille(
            equipe=self.cleaned_data['equipe'], **self.instance.__dict__
        )
        famille.suivi.motif_detail = self.cleaned_data['motif_detail']
        famille.suivi.save()
        return famille


class SuiviForm(ReadOnlyableMixin, forms.ModelForm):
    equipe = forms.TypedChoiceField(
        choices=[('', '-------')] + Suivi.EQUIPES_CHOICES[:2], required=False
    )

    class Meta:
        model = Suivi
        fields = [
            'equipe', 'heure_coord', 'ope_referent', 'ope_referent_2',
            'mandat_ope', 'service_orienteur', 'service_annonceur', 'motif_demande',
            'motif_detail', 'demande_prioritaire', 'demarche', 'collaboration',
            'ressource', 'crise', 'remarque', 'remarque_privee',
        ]
        widgets = {
            'mandat_ope': BSCheckboxSelectMultiple,
            'motif_demande': BSCheckboxSelectMultiple,
        }

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.fields['ope_referent'].queryset = Contact.membres_ope()
        self.fields['ope_referent_2'].queryset = Contact.membres_ope()


class IntervenantForm(BootstrapMixin, forms.ModelForm):
    intervenant = forms.ModelChoiceField(
        queryset=Utilisateur.objects.filter(groups__name__startswith='aemo').distinct().order_by('nom')
    )
    date_debut = forms.DateField(
        label='Date de début', initial=date.today(), widget=PickDateWidget()
    )

    class Meta:
        model = Intervenant
        fields = ['intervenant', 'role', 'date_debut']

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields['role'].queryset = Role.objects.filter(est_intervenant=True)


class IntervenantEditForm(BootstrapMixin, forms.ModelForm):

    class Meta:
        model = Intervenant
        fields = ['intervenant', 'role', 'date_debut', 'date_fin']
        widgets = {
            'date_debut': PickDateWidget(),
            'date_fin': PickDateWidget(),
        }

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields['intervenant'].disabled = True
        self.fields['role'].disabled = True


class DemandeForm(BootstrapMixin, ReadOnlyableMixin, forms.ModelForm):
    class Meta:
        model = Suivi
        fields = (
            'dates_demande', 'ref_presents', 'pers_famille_presentes', 'autres_pers_presentes',
            'difficultes', 'aides', 'competences', 'disponibilites', 'autres_contacts', 'remarque'
        )
        field_classes = {
            'difficultes': RichTextField,
            'aides': RichTextField,
            'disponibilites': RichTextField,
            'competences': RichTextField,
        }
        widgets = {
            'autres_contacts': forms.Textarea(attrs={'cols': 120, 'rows': 4}),
            'remarque': forms.Textarea(attrs={'cols': 120, 'rows': 4}),
        }


class AgendaForm(ReadOnlyableMixin, AgendaFormBase):
    ope_referent = forms.ModelChoiceField(
        queryset=Contact.membres_ope(), required=False
    )

    class Meta(AgendaFormBase.Meta):
        model = Suivi


class BilanForm(BootstrapMixin, forms.ModelForm):
    class Meta:
        model = Bilan
        exclude = ['famille', 'auteur']
        field_classes = {
            'objectifs': RichTextField,
            'rythme': RichTextField,
        }
        widgets = {
            'famille': forms.HiddenInput,
            'date': PickDateWidget,
            'sig_interv': BSCheckboxSelectMultiple,
        }
        labels = {'fichier': ''}

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields['sig_interv'].queryset = Utilisateur.objects.filter(
            pk__in=kwargs['initial']['famille'].suivi.intervenant_set.actifs(
                self.instance.date or date.today()
            ).values_list('intervenant', flat=True)
        )


class NiveauForm(BootstrapMixin, forms.ModelForm):
    class Meta:
        model = Niveau
        fields = ['niveau_interv', 'date_debut', 'date_fin']
        widgets = {
            'date_debut': PickDateWidget(),
            'date_fin': PickDateWidget(),
        }

    def __init__(self, *args, **kwargs):
        self.famille = kwargs.pop('famille')
        super().__init__(*args, **kwargs)
        if self.instance.pk is None:
            self.fields.pop('date_fin')
            if self.famille.niveaux.count() > 0:
                self.fields['date_debut'].required = True

    def clean_niveau_interv(self):
        niv = self.cleaned_data['niveau_interv']
        if self.instance.pk is None:
            der_niv = self.famille.niveaux.last() if self.famille.niveaux.count() > 0 else None
            if der_niv and der_niv.niveau_interv == niv:
                raise forms.ValidationError(f"Le niveau {niv} est déjà actif.")
        return niv


class EquipeFilterForm(forms.Form):
    equipe = forms.ChoiceField(
        label="Équipe", choices=[('', 'Toutes')] + EquipeChoices.choices, required=False,
        widget=forms.Select(attrs={'class': 'form-select form-select-sm immediate-submit'}),
    )

    def filter(self, familles):
        if self.cleaned_data['equipe']:
            familles = familles.filter(suivi__equipe=self.cleaned_data['equipe'])
        return familles


class FamilleFilterForm(EquipeFilterForm):
    ressource = forms.ChoiceField(
        label='Ressources',
        required=False,
        widget=forms.Select(attrs={'class': 'form-select form-select-sm immediate-submit'})
    )
    niveau = forms.ChoiceField(
        label="Niv. d'interv.",
        required=False,
        widget=forms.Select(attrs={'class': 'form-select form-select-sm immediate-submit'})
    )
    interv = forms.ChoiceField(
        label="Intervenant-e",
        required=False,
        widget=forms.Select(attrs={'class': 'form-select form-select-sm immediate-submit'})
    )
    nom = forms.CharField(
        widget=forms.TextInput(
            attrs={'placeholder': 'Nom de famille…', 'autocomplete': 'off',
                   'class': 'form-control form-control-sm inline'}),
        required=False
    )
    duos = forms.ChoiceField(
        label="Duos Educ/Psy",
        required=False,
        widget=forms.Select(attrs={'class': 'form-select form-select-sm immediate-submit'})
    )

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        intervenants = Utilisateur.objects.filter(is_active=True, groups__name='aemo')
        self.fields['interv'].choices = [
            ('', 'Tout-e-s'), ('0', 'Aucun')] + [
            (user.id, user.nom_prenom) for user in intervenants
        ]
        self.fields['niveau'].choices = [
            ('', 'Tous')
        ] + [(key, val) for key, val in Niveau.INTERV_CHOICES]

        self.fields['ressource'].choices = [
            ('', 'Toutes')
        ] + [(el.pk, el.nom) for el in Role.objects.filter(
                nom__in=['ASE', 'IPE', 'Coach APA', 'Assistant-e social-e']
            )]
        self.fields['duos'].choices = [
            ('', 'Tous')
        ] + [
            (duo, duo) for duo in Famille.objects.exclude(
                suivi__date_fin_suivi__isnull=False
            ).with_duos().exclude(duo='').values_list('duo', flat=True).distinct().order_by('duo')
        ]

    def filter(self, familles):
        if self.cleaned_data['interv']:
            if self.cleaned_data['interv'] == '0':
                familles = familles.annotate(
                    num_interv=Count('suivi__intervenants', filter=(
                        Q(suivi__intervenant__date_fin__isnull=True) |
                        Q(suivi__intervenant__date_fin__gt=date.today())
                    ))
                ).filter(num_interv=0)
            else:
                familles = familles.filter(
                    Q(suivi__intervenant__intervenant=self.cleaned_data['interv']) & (
                        Q(suivi__intervenant__date_fin__isnull=True) |
                        Q(suivi__intervenant__date_fin__gt=date.today())
                    )
                )

        if self.cleaned_data['nom']:
            familles = familles.filter(nom__istartswith=self.cleaned_data['nom'])

        familles = super().filter(familles)

        if self.cleaned_data['niveau']:
            familles = familles.with_niveau_interv().filter(niveau_interv=self.cleaned_data['niveau'])

        if self.cleaned_data['ressource']:
            ress = Intervenant.objects.actifs().filter(
                role=self.cleaned_data['ressource'],
            ).values_list('intervenant', flat=True)
            familles = familles.filter(suivi__intervenants__in=ress).distinct()

        if self.cleaned_data['duos']:
            familles = familles.with_duos().filter(duo=self.cleaned_data['duos'])
        return familles


class JournalAuteurFilterForm(forms.Form):
    recherche = forms.CharField(
        widget=forms.TextInput(attrs={
            'class': 'search-form-fields form-control d-inline-block',
            'placeholder': 'recherche',
        }),
        required=False,
    )
    auteur = forms.ModelChoiceField(
        queryset=Utilisateur.objects.all(),
        widget=forms.Select(attrs={'class': 'search-form-fields form-select d-inline-block immediate-submit'}),
        required=False,
    )

    def __init__(self, famille=None, **kwargs):
        super().__init__(**kwargs)
        self.fields['auteur'].queryset = Utilisateur.objects.filter(prestations__famille=famille).distinct()

    def filter(self, prestations):
        if self.cleaned_data['auteur']:
            prestations = prestations.filter(auteur=self.cleaned_data['auteur'])
        if self.cleaned_data['recherche']:
            prestations = prestations.annotate(
                search=SearchVector("texte", config="french_unaccent")
            ).filter(
                search=SearchQuery(self.cleaned_data['recherche'], config="french_unaccent")
            )
        return prestations
