from django.contrib import admin
from django import forms
from django.core.cache import cache
from django.core.paginator import Paginator
from django.db.models import Count, Value, Case, When, Value
from django.db.models.functions import Concat
from . import models
from . import autocomplete_extras
from djaa_list_filter.admin import AjaxAutocompleteListFilterModelAdmin
from django.contrib.admin.views.main import ChangeList

# Modified version of a GIST I found in a SO thread
# cfr. http://masnun.rocks/2017/03/20/django-admin-expensive-count-all-queries/
class CachingPaginator(Paginator):
    def _get_count(self):
        if not hasattr(self, "_count"):
            self._count = None
        if self._count is None:
            try:
                key = "adm:{0}:count".format(
                    hash(self.object_list.query.__str__()))
                self._count = cache.get(key, -1)
                if self._count == -1:
                    self._count = super().count
                    cache.set(key, self._count, 60)
            except:
                self._count = len(self.object_list)
        return self._count
    count = property(_get_count)

def prefix_match_len(str1, str2):
    tot = 0
    for char1, char2 in zip(str1, str2):
        if char1 == char2:
            tot += 1
        else:
            break
    return tot

class RicercaOrdinataChangeList(ChangeList):    
    # super() è django.contrib.admin.views.main.ChangeList
    def get_queryset(self, request):
        qs = super().get_queryset(request)
        if not(hasattr(request, 'search_term') and hasattr(request, 'disable_default_ordering') and request.disable_default_ordering):
            return qs

        search_term_cf = request.search_term.casefold()
        tok_q = set(x.strip()
                    for x in search_term_cf.split(' ') if x.strip())
        search_cache = {x.pk: str(x) for x in qs}
        scores = []
        l_s_t = len(search_term_cf) or 1
        for obj_pk, obj_str in search_cache.items():
            m_l = prefix_match_len(
                obj_str.casefold(), search_term_cf)
            score_non_tok = m_l / max(l_s_t, len(obj_str))
            tok = set(x.strip().casefold()
                        for x in obj_str.split(' ') if x.strip())
            if tok and tok_q:
                score_tok = sum(prefix_match_len(token1, token2) / len(token1)
                                for token1 in tok_q
                                for token2 in tok) / (len(tok_q) + len(tok))
            else:
                score_tok = 0
            scores.append((-score_non_tok, -score_tok, obj_pk))
        scores.sort()
        rank_by_pk = {obj_pk: rank for rank,
                        (_, _, obj_pk) in enumerate(scores)}
        whens = [When(pk=-1, then=None)]
        for obj_pk, rank in rank_by_pk.items():
            whens.append(When(pk=obj_pk, then=Value(rank)))
        qs = qs.annotate(briq_search_results_rank=Case(*whens, default=None))
        ordering = self.get_ordering(request,qs)
        qs = qs.order_by('briq_search_results_rank',*ordering)
        return qs


class RicercaOrdinataMixin(admin.ModelAdmin):
    def get_changelist(self, *args, **kwargs):
        return RicercaOrdinataChangeList

    def get_search_results(self, request, qs, search_term):
        qs, may_have_duplicates = super().get_search_results(
            request, qs, search_term,
        )
        if search_term and qs:
            request.disable_default_ordering = True
            request.search_term = search_term
            if not request.resolver_match._func_path.split('.')[-1].lower().startswith('changelist'):
                search_term_cf = request.search_term.casefold()
                tok_q = set(x.strip()
                            for x in search_term_cf.split(' ') if x.strip())
                search_cache = {x.pk: str(x) for x in qs}
                scores = []
                l_s_t = len(search_term_cf) or 1
                for obj_pk, obj_str in search_cache.items():
                    m_l = prefix_match_len(
                        obj_str.casefold(), search_term_cf)
                    score_non_tok = m_l / max(l_s_t, len(obj_str))
                    tok = set(x.strip().casefold()
                                for x in obj_str.split(' ') if x.strip())
                    if tok and tok_q:
                        score_tok = sum(prefix_match_len(token1, token2) / len(token1)
                                        for token1 in tok_q
                                        for token2 in tok) / (len(tok_q) + len(tok))
                    else:
                        score_tok = 0
                    scores.append((-score_non_tok, -score_tok, obj_pk))
                scores.sort()
                rank_by_pk = {obj_pk: rank for rank,
                                (_, _, obj_pk) in enumerate(scores)}
                whens = [When(pk=-1, then=None)]
                for obj_pk, rank in rank_by_pk.items():
                    whens.append(When(pk=obj_pk, then=Value(rank)))
                qs = qs.annotate(briq_search_results_rank=Case(*whens, default=None))
                qs = qs.order_by('briq_search_results_rank')
        return qs, may_have_duplicates


# # Main reusable Admin class for only viewing
# class ViewAdminMixin(admin.ModelAdmin):
#     def has_add_permission(self, request):
#         return False
#
#     def has_change_permission(self, request, obj=None):
#         return False
#
#     def has_delete_permission(self, request, obj=None):
#         return False


@admin.register(models.Comune)
class ComuneAdmin(RicercaOrdinataMixin, AjaxAutocompleteListFilterModelAdmin):
    list_per_page = 15
    paginator = CachingPaginator
    show_full_result_count = False
    fields=('nome','provincia','cap','codice_istat','codice_nazionale',)
    search_fields = ('nome','cap__codice__exact','codice_istat__exact','codice_nazionale__iexact',)
    autocomplete_fields = ('provincia','cap',)
    list_display = ('nome', 'provincia','regione_key','lista_cap',)
    ordering = ('nome','provincia_id',)
    autocomplete_list_filter = ('provincia',)
    list_filter = ('provincia__regione_id',)

    def regione_key(self, obj):
        return obj.provincia.regione_id
    regione_key.short_description = 'Regione'
    regione_key.admin_order_field = 'regione_id'

    def get_queryset(self, request):
        queryset = super().get_queryset(request).prefetch_related('provincia').prefetch_related('cap')
        return queryset

    def lista_cap(self,obj):
        lista = sorted((c.codice for c in obj.cap.all()),key=int)
        aggr_cap=''
        if len(lista) > 0:
            gruppi = [[lista[0]]*2]
            for i in lista[1:]:
                if int(i)-1 == int(gruppi[-1][1]):
                    gruppi[-1][1] = i
                else:
                    gruppi.append([i]*2)
            aggr_cap=', '.join(f'{str(g[0])}-{str(g[1])}' if g[1]!=g[0] else str(g[0]) for g in gruppi)
        return aggr_cap

# from django.db.models.fields.related import ManyToManyRel
# import django.db.models as dj_models

# asd = dj_models.ManyToManyField(models.Comune, through=models.Comune.cap.through)
# asd.model = models.CAP
# asd.name = "comuni2"
# asd.remote_field = models.Comune.cap.through._meta.get_field('comune')
# models.CAP._meta.add_field(asd)

class CAPAdminForm(forms.ModelForm):
    #asd = ManyToManyRel(models.Comune.cap, to=models.Comune, related_name="asd",through=models.Comune.cap.through, related_query_name="comuni")
    comuni = forms.ModelMultipleChoiceField(
        models.Comune.objects.all(),
        #widget=admin.widgets.AutocompleteSelectMultiple(models.CAP._meta.get_field("comuni2"), admin_site= None),
        widget=admin.widgets.FilteredSelectMultiple('Comune', False),
        required=False,
    )

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        #print(self.base_fields)
        if self.instance.pk:
            self.initial['comuni'] = self.instance.comuni.values_list('pk', flat=True)

    def save(self, *args, **kwargs):
        instance = super().save(*args, **kwargs)
        if instance.pk:
            instance.comuni.clear()
            instance.comuni.add(*self.cleaned_data['comuni'])
        return instance

    class Meta:
        model = models.CAP
        fields = "__all__"



@admin.register(models.CAP)
class CAPAdmin(RicercaOrdinataMixin, AjaxAutocompleteListFilterModelAdmin):
    list_per_page = 15
    paginator = CachingPaginator
    show_full_result_count = False
    fields=('codice', 'comuni')
    search_fields=('codice','comuni__nome__iexact')
    ordering = ('codice',)
    list_display = ('codice','numero_comuni','lista_comuni',)
    list_filter = ('comuni__provincia__regione_id',)

    form = CAPAdminForm
    # def get_form(self, request, obj=None, change=False, **kwargs):
    #     form = super().get_form(request, obj, change, **kwargs)
    #     form.base_fields['comuni'].widget.admin_site = self.admin_site
    #     return form
    
    def get_queryset(self, request):
        queryset = super().get_queryset(request).prefetch_related('comuni')
        queryset = queryset.annotate(
            conteggio_comuni=Count('comuni')
        )
        return queryset

    def numero_comuni(self, obj):
        return obj.conteggio_comuni
    numero_comuni.admin_order_field = 'conteggio_comuni'

    def lista_comuni(self,obj):
        return ', '.join(str(c) for c in obj.comuni.all())


@admin.register(models.Provincia)
class ProvinciaAdmin(RicercaOrdinataMixin, admin.ModelAdmin):
    list_per_page = 15
    paginator = CachingPaginator
    show_full_result_count = False
    search_fields = ('sigla', 'nome_esteso',)
    list_display = ('nome_esteso', 'sigla', 'numero_comuni', 'regione_key',)
    list_filter = ('regione_id',)
    #ordering = ('regione_id', 'nome_esteso',)

    def regione_key(self, obj):
        return obj.regione_id
    regione_key.short_description = 'Regione'
    regione_key.admin_order_field = 'regione_id'

    def get_queryset(self, request):
        queryset = super().get_queryset(request)
        queryset = queryset.annotate(
            conteggio_comuni=Count('comuni')
        )
        return queryset

    def numero_comuni(self, obj):
        return obj.conteggio_comuni
    numero_comuni.admin_order_field = 'conteggio_comuni'


@admin.register(models.Regione)
class RegioneAdmin(RicercaOrdinataMixin, admin.ModelAdmin):
    # list_per_page = 15
    # paginator = CachingPaginator
    # show_full_result_count = False
    search_fields = ('nome',)
    list_display = ('nome', 'numero_province',)
    ordering = ('nome',)

    def get_queryset(self, request):
        queryset = super().get_queryset(request)
        queryset = queryset.annotate(
            conteggio_province=Count('province')
        )
        return queryset

    def numero_province(self, obj):
        return obj.conteggio_province
    numero_province.admin_order_field = 'conteggio_province'


@admin.register(models.DUG)
class DUGAdmin(RicercaOrdinataMixin, admin.ModelAdmin):
    list_per_page = 15
    paginator = CachingPaginator
    show_full_result_count = False
    fields=('nome',)
    search_fields=('nome',)
    ordering = ('nome',)
    list_display = ('nome',)

@admin.register(models.Nazione)
class NazioneAdmin(admin.ModelAdmin):
    # resource = resources.NazioneResource
    # list_per_page = 15
    # paginator = CachingPaginator
    # show_full_result_count = False
    search_fields = ('descrizione','codifica',)
    pass
