"""
Ce fichier contient des fonctions qui permet aux utilisateurs de filtrer un ensemble de questions sur la base des champs d'un modèle.
"""

from django.db.models import Q
from django_filters import rest_framework as filters
from requests import request
from rest_framework.filters import OrderingFilter


from .models import Administre
from .models import AvisPosteChoices as AvisPoste
from .models import StatutPamChoices as StatutPam

from .models import (Decision, FormationEmploi, Poste, RefGest, RefOrg,
                     RefSvFil, SpecifiqueChoices, Administres_Pams,
                     Postes_Pams)
from .utils.decorators import class_logger
from .utils.logging import get_logger
from .utils.permissions import (KEY_READ, KEY_WRITE, Profiles,
                                get_adm_filter_by_lvl4_codes_fil,
                                get_adm_filter_by_lvl4_codes_future_pcp,
                                get_adm_filter_by_lvl4_codes_pcp,
                                get_lvl4_org_codes_by_any_code,
                                get_poste_filter_by_lvl4_codes_fil,
                                get_poste_filter_by_lvl4_codes_pcp,
                                get_profile_summary, get_queryset_org_by_user,
                                is_truthy)

# paramètre de requête pour restreindre par formation(s) d'emploi(s)
REQ_PARAM_FE = 'formation_emploi'

# paramètre de requête pour restreindre à un seul profil
REQ_PARAM_ONLY_PROFILE = 'profile'

# [poste] paramètre de requête pour restreindre par p_specifique
REQ_PARAM_POSTE_SPECIFIQUE = 'p_specifique'

# paramètre de requête pour restreindre par sous-vivier
REQ_PARAM_SOUS_VIVIER = 'sous_vivier'

@class_logger
class AdministreFilter(filters.FilterSet):
    """
    Cette classe contient tous les filtres qui peuvent être effectués sur la table des administrés.
    """
    aide_a_la_decision = filters.CharFilter(method='aide_a_la_decision_filter')
    formation_emploi__in = filters.CharFilter(method='formation_emploi_filter')
    sous_vivier__in = filters.CharFilter(method='sous_vivier_filter')
    pam__in = filters.CharFilter(method='pam_filter')

    class Meta:
        model = Administre
        fields = {
            Administre.Cols.REL_SOUS_VIVIER: ['exact'],
        }

    def aide_a_la_decision_filter(self, queryset, name, value):
        statut = [StatutPam.A_ETUDIER, StatutPam.A_MUTER]
        return queryset.filter(Q(**{f'{Administre.Cols.REL_PAM_INTER}__a_statut_pam_annee__in' : statut})
                             & Q(**{f'{Administre.Cols.REL_PAM_INTER}__{Administres_Pams.Cols.REL_DECISION}__de_decision__isnull' :True}) 
                             & Q(**{f'{Administre.Cols.REL_PAM_INTER}__pam_id__exact' : value}))

    def formation_emploi_filter(self, queryset, name, value):
        list_values = value.split(',')
        return queryset.filter(Q(**{f'{Administre.Cols.REL_FORMATION_EMPLOI}__fe_code__in': list_values}) \
                             | Q(**{f'{Administre.Cols.REL_PAM_INTER}__{Administres_Pams.Cols.REL_DECISION}__{Decision.Cols.REL_POSTE}__{Poste.Cols.REL_FORMATION_EMPLOI}__fe_code__in': list_values}))


    def pam_filter(self, queryset, name, value):
        return queryset.filter(Q(**{f'{Administre.Cols.M2M_PAM}__pam_id__icontains': value}))


    @property
    def qs(self):
        qs = super().qs


        summary = get_profile_summary(self.request.user)

        profiles = summary.profiles.get(KEY_READ, ())
        if not profiles:
            return qs.none()

        only_profile = self.request.query_params.get(REQ_PARAM_ONLY_PROFILE)

        org_code = summary.org_code
        is_fil   = Profiles.FILIERE in profiles and (not only_profile or only_profile == Profiles.FILIERE)
        is_pcp   = Profiles.PCP in profiles and (not only_profile or only_profile == Profiles.PCP)
        is_bvt   = Profiles.BVT in profiles and (not only_profile or only_profile == Profiles.BVT)
        if not org_code or (not is_fil and not is_pcp and not is_bvt):
            return qs.none()

        codes_lvl4 = get_lvl4_org_codes_by_any_code(org_code)
        adm_filter = None
        if is_fil:
            adm_filter = get_adm_filter_by_lvl4_codes_fil(codes_lvl4)
        if is_pcp:
            pcp_filter = get_adm_filter_by_lvl4_codes_pcp(codes_lvl4) \
                         | get_adm_filter_by_lvl4_codes_future_pcp(codes_lvl4)
            adm_filter = adm_filter | pcp_filter if adm_filter else pcp_filter
        if is_bvt:
            bvt_filter = Q(**{f'{Administre.Cols.REL_SOUS_VIVIER}': 'BVT'})
            adm_filter = adm_filter | bvt_filter if adm_filter else bvt_filter

        return qs.filter(adm_filter)


@class_logger
class AdministrePAMFilter(filters.FilterSet):
    """
    Cette classe contient tous les filtres qui peuvent être effectués sur la table des administrés.
    """
    aide_a_la_decision = filters.CharFilter(method='aide_a_la_decision_filter')
    formation_emploi__in = filters.CharFilter(method='formation_emploi_filter')
    pam__in = filters.CharFilter(method='pam_filter')
    sous_vivier = filters.CharFilter(method='sous_vivier_filter')
    adm__in = filters.CharFilter(method='administre_filter')

    class Meta:
        model = Administres_Pams
        fields = {
        }

    def aide_a_la_decision_filter(self, queryset, name, value):
        statut = [StatutPam.A_ETUDIER, StatutPam.A_MUTER]
        return queryset.filter(Q(a_statut_pam_annee__in = statut)
                             & Q(**{f'{Administres_Pams.Cols.REL_DECISION}__de_decision__isnull' :True}))

    def administre_filter(self,queryset, name, value):
        values_list = value.split(',')
        if values_list:
            return queryset.filter(id__in=values_list)

    def formation_emploi_filter(self, queryset, name, value):
        list_values = value.split(',')
        return queryset.filter(Q(**{f'{Administres_Pams.Cols.REL_ADMINISTRE}__{Administre.Cols.REL_FORMATION_EMPLOI}__fe_mere_credo__in': list_values}) \
                             | Q(**{f'{Administres_Pams.Cols.REL_DECISION}__{Decision.Cols.REL_POSTE}__{Poste.Cols.REL_FORMATION_EMPLOI}__fe_mere_credo__in': list_values}))

    def pam_filter(self, queryset, name, value):
        return queryset.filter(pam_id = value)

    def sous_vivier_filter(self, queryset, name, value):
        list_values = value.split('-')
        return queryset.filter(administre__sous_vivier__in = list_values)

    

    @property
    def qs(self):
        qs = super().qs
        summary = get_profile_summary(self.request.user)

        profiles = summary.profiles.get(KEY_READ, ())
        if not profiles:
            return qs.none()

        only_profile = self.request.query_params.get(REQ_PARAM_ONLY_PROFILE)

        org_code = summary.org_code
        is_fil   = Profiles.FILIERE in profiles and (not only_profile or only_profile == Profiles.FILIERE)
        is_pcp   = Profiles.PCP in profiles and (not only_profile or only_profile == Profiles.PCP)
        is_bvt   = Profiles.BVT in profiles and (not only_profile or only_profile == Profiles.BVT)
        if not org_code or (not is_fil and not is_pcp and not is_bvt):
            return qs.none()

        codes_lvl4 = get_lvl4_org_codes_by_any_code(org_code)
        adm_filter = None
        if is_fil:
            adm_filter = get_adm_filter_by_lvl4_codes_fil(codes_lvl4)
        if is_pcp:
            pcp_filter = get_adm_filter_by_lvl4_codes_pcp(codes_lvl4) \
                         | get_adm_filter_by_lvl4_codes_future_pcp(codes_lvl4)
            adm_filter = adm_filter | pcp_filter if adm_filter else pcp_filter
        if is_bvt:
            bvt_filter = Q(**{f'{Administres_Pams.Cols.REL_ADMINISTRE}__{Administre.Cols.REL_SOUS_VIVIER}': 'BVT'})
            adm_filter = adm_filter | bvt_filter if adm_filter else bvt_filter

        return qs.filter(adm_filter)

class DecisionFilter(filters.FilterSet):
    """
    Cette classe contient tous les filtres qui peuvent être effectués sur la table des decisions.
    """
    class Meta:
        model = Decision
        fields = ['administre_id', 'poste_id']


@class_logger
class PosteFilter(filters.FilterSet):
    """
    Cette classe contient tous les filtres qui peuvent être effectués sur la table des postes.
    """
    aide_a_la_decision = filters.CharFilter(method='aide_a_la_decision_filter')
    pam__in = filters.CharFilter(method='pam_filter')

    class Meta:
        model = Poste
        
        fields = [
            'p_specifique',
            'p_itd_affecte',
            Poste.Cols.REL_FORMATION_EMPLOI
          ]

    def aide_a_la_decision_filter(self, queryset, name, value):
        statut = [AvisPoste.P1, AvisPoste.P2, AvisPoste.P3, AvisPoste.P4]
        return queryset.filter(Q(p_avis__in=statut) & Q(poste__decisions__isnull=True) & Q(poste__p_pam_id__exact = value))

    def pam_filter(self, queryset, name, value):
        values_list = value.split(',')
        return queryset.filter(Q(**{f'{Poste.Cols.M2M_PAM}__pam_id__in': values_list}))

    @property
    def qs(self):
        qs = super().qs

        summary = get_profile_summary(self.request.user)

        profiles = summary.profiles.get(KEY_READ, ())
        if not profiles:
            return qs.none()

        only_profile = self.request.query_params.get(REQ_PARAM_ONLY_PROFILE)

        org_code = summary.org_code
        is_fil   = Profiles.FILIERE in profiles and (not only_profile or only_profile == Profiles.FILIERE)
        is_pcp   = Profiles.PCP in profiles and (not only_profile or only_profile == Profiles.PCP)
        is_bvt   = Profiles.BVT in profiles and (not only_profile or only_profile == Profiles.BVT)
        is_itd   = Profiles.ITD in profiles and (not only_profile or only_profile == Profiles.ITD)
        if not org_code or (not is_fil and not is_pcp and not is_bvt and not is_itd):
            return qs.none()

        codes_lvl4 = get_lvl4_org_codes_by_any_code(org_code)

        poste_filter = None
        if is_fil:
            poste_filter = get_poste_filter_by_lvl4_codes_fil(codes_lvl4) \
                           | Q(p_specifique=SpecifiqueChoices.ITD)
        if is_pcp:
            pcp_filter = get_poste_filter_by_lvl4_codes_pcp(codes_lvl4) \
                         | Q(p_specifique=SpecifiqueChoices.ITD)
            poste_filter = poste_filter | pcp_filter if poste_filter else pcp_filter
        if is_itd:
            itd_filter = Q(p_specifique=SpecifiqueChoices.ITD)
            poste_filter = poste_filter | itd_filter if poste_filter else itd_filter
        if is_bvt:
            bvt_filter = Q(**{f'{Poste.Cols.M2M_SOUS_VIVIERS}': 'BVT'})
            poste_filter = poste_filter | bvt_filter if poste_filter else bvt_filter

        return qs.filter(poste_filter)


    @classmethod
    def get_fields(cls):
        """
        Cette fonction renvoie toutes les valeurs de recherche dans
        les champs qui peuvent être filtrés. Les champs qui peuvent être filtrés sont spécifiés dans la classe Meta variable fields.
        """
        fields = super().get_fields()
        for field_name in fields.copy():
            lookup_list = cls.Meta.model._meta.get_field(field_name).get_lookups().keys()
            fields[field_name] = lookup_list
        return fields



@class_logger
class PostePAMFilter(filters.FilterSet):
    """
    Cette classe contient tous les filtres qui peuvent être effectués sur la table des postes.
    """
    aide_a_la_decision = filters.CharFilter(method='aide_a_la_decision_filter')
    pam__in = filters.CharFilter(method='pam_filter')
    p_specifique = filters.CharFilter(method='p_specifique_filter')
    p_itd_affecte = filters.CharFilter(method='p_itd_affecte_filter')
    formation_emploi__in = filters.CharFilter(method='formation_emploi_filter')
    # sous_vivier = filters.CharFilter(method='sous_vivier_filter')
    poste__in = filters.CharFilter(method='poste_filter')

    class Meta:
        model = Postes_Pams
        
        fields = [
          ]

    def aide_a_la_decision_filter(self, queryset, name, value):
        statut = [AvisPoste.P1, AvisPoste.P2, AvisPoste.P3, AvisPoste.P4]
        return queryset.filter(Q(p_avis_pam__in=statut)  & Q(**{f'{Postes_Pams.Cols.O2M_DECISION}__de_decision__isnull' :True}) & Q(p_pam = value))

    def poste_filter(self,queryset, name, value):
        values_list = value.split(',')
        if values_list:
            return queryset.filter(id__in=values_list)

    def pam_filter(self, queryset, name, value):
        values_list = value.split(',')
        return queryset.filter(Q(p_pam_id__in = values_list)).distinct('poste_id')

    def p_specifique_filter(self, queryset, name, value):
        return queryset.filter(Q(**{f'{Postes_Pams.Cols.REL_POSTE}__p_specifique': value}))

    def p_itd_affecte_filter(self, queryset, name, value):
        return queryset.filter(Q(**{f'{Postes_Pams.Cols.REL_POSTE}__p_itd_affecte': value}))

    def formation_emploi_filter(self, queryset, name, value):
        list_values = value.split(',')
        return queryset.filter(Q(**{f'{Postes_Pams.Cols.REL_POSTE}__formation_emploi__fe_mere_credo__in': list_values}))
    
    @property
    def qs(self):
        qs = super().qs

        summary = get_profile_summary(self.request.user)

        profiles = summary.profiles.get(KEY_READ, ())
        if not profiles:
            return qs.none()

        only_profile = self.request.query_params.get(REQ_PARAM_ONLY_PROFILE)

        org_code = summary.org_code
        is_fil   = Profiles.FILIERE in profiles and (not only_profile or only_profile == Profiles.FILIERE)
        is_pcp   = Profiles.PCP in profiles and (not only_profile or only_profile == Profiles.PCP)
        is_bvt   = Profiles.BVT in profiles and (not only_profile or only_profile == Profiles.BVT)
        is_itd   = Profiles.ITD in profiles and (not only_profile or only_profile == Profiles.ITD)
        if not org_code or (not is_fil and not is_pcp and not is_bvt and not is_itd):
            return qs.none()

        codes_lvl4 = get_lvl4_org_codes_by_any_code(org_code)

        
        itd_filter_add = Q(**{f'{Postes_Pams.Cols.REL_POSTE}__p_specifique': SpecifiqueChoices.ITD})
        poste_filter = None
        if is_fil:
            poste_filter = get_poste_filter_by_lvl4_codes_fil(codes_lvl4) \
                           | itd_filter_add
        if is_pcp:
            list_a_pcp_ok = Administres_Pams.objects.filter(
                get_adm_filter_by_lvl4_codes_pcp(codes_lvl4) 
                | get_adm_filter_by_lvl4_codes_future_pcp(codes_lvl4)
            ).values_list('administre_id', flat=True)

            pcp_filter = get_poste_filter_by_lvl4_codes_pcp(codes_lvl4) \
                         | itd_filter_add \
                         | Q(**{f'{Postes_Pams.Cols.O2M_DECISION}__{Decision.Cols.REL_ADMINISTRE}__in': list_a_pcp_ok})
            poste_filter = poste_filter | pcp_filter if poste_filter else pcp_filter
        
        if is_itd:
            itd_filter = itd_filter_add
            poste_filter = poste_filter | itd_filter if poste_filter else itd_filter
        
        if is_bvt:
            bvt_filter = Q(**{f'{Postes_Pams.Cols.REL_POSTE}__{Poste.Cols.M2M_SOUS_VIVIERS}': 'BVT'})
            poste_filter = poste_filter | bvt_filter if poste_filter else bvt_filter
        
        return qs.filter(poste_filter).distinct()


    @classmethod
    def get_fields(cls):
        """
        Cette fonction renvoie toutes les valeurs de recherche dans
        les champs qui peuvent être filtrés. Les champs qui peuvent être filtrés sont spécifiés dans la classe Meta variable fields.
        """
        fields = super().get_fields()
        for field_name in fields.copy():
            lookup_list = cls.Meta.model._meta.get_field(field_name).get_lookups().keys()
            fields[field_name] = lookup_list
        return fields


class RelatedOrderingFilter(OrderingFilter):
    _max_related_depth = 3

    @staticmethod
    def _get_verbose_name(field, non_verbose_name):
        return field.verbose_name if hasattr(field, 'verbose_name') else non_verbose_name.replace('_', ' ')

    def _retrieve_all_related_fields(self, fields, model, depth=0):
        valid_fields = []
        if depth > self._max_related_depth:
            return valid_fields
        for field in fields:
            if field.related_model and field.related_model != model:
                rel_fields = self._retrieve_all_related_fields(
                    field.related_model._meta.get_fields(),
                    field.related_model,
                    depth + 1)
                for rel_field in rel_fields:
                    valid_fields.append((
                        f'{field.name}__{rel_field[0]}',
                        self._get_verbose_name(field, rel_field[1])
                    ))
            else:
                valid_fields.append((
                    field.name,
                    self._get_verbose_name(field, field.name),
                ))
        return valid_fields

    def get_valid_fields(self, queryset, view, context=None):
        valid_fields = getattr(view, 'ordering_fields', self.ordering_fields)
        if not valid_fields == '__all_related__':
            if not context:
                context = {}
            valid_fields = super().get_valid_fields(queryset, view, context)
        else:
            valid_fields = [
                *self._retrieve_all_related_fields(queryset.model._meta.get_fields(), queryset.model),
                *[(key, key.title().split('__')) for key in queryset.query.annotations]
            ]
        return valid_fields
