import waffle

from typing import Type

from django.db.models import F, Subquery, OuterRef, QuerySet
from django.db.models.functions import Coalesce
from rest_framework import status

from intranet.femida.src.api.core.views import ResponseError
from intranet.femida.src.core.db import Cardinality, SubstringLevenshtein
from intranet.femida.src.jobs.public_professions.models import PublicProfession
from intranet.femida.src.professions.models import Profession
from intranet.femida.src.publications.choices import PUBLICATION_FACETS, JOBS_SUGGEST_TYPES
from intranet.femida.src.publications.helpers import get_publications_lang
from intranet.femida.src.publications.models import PublicationFacet, PublicationSuggest


class JobsSuggestRegistry:

    _suggest_types: dict[str, Type['BaseJobsSuggest']] = {}

    @classmethod
    def register_suggest_type(cls, suggest_cls):
        assert issubclass(suggest_cls, BaseJobsSuggest)
        assert suggest_cls.suggest_type is not None
        assert suggest_cls.suggest_type not in cls._suggest_types
        cls._suggest_types[suggest_cls.suggest_type] = suggest_cls
        return suggest_cls

    @classmethod
    def get_suggest_data(cls, suggest_type, query, limit):
        return cls._suggest_types[suggest_type].get_suggest_data(query, limit)


register_suggest_type = JobsSuggestRegistry.register_suggest_type


class BaseJobsSuggest:

    queryset: QuerySet = None
    suggest_type: str = None

    @classmethod
    def get_suggest_data(cls, query, limit):
        raise NotImplementedError()


class BaseFacetSuggest(BaseJobsSuggest):

    facet = None
    facet_value_field = 'slug'

    @classmethod
    def get_annotations(cls):
        return {}

    @classmethod
    def get_name_field(cls):
        return 'name_{}'.format(get_publications_lang())

    @classmethod
    def get_suggest_data(cls, query, limit):
        if cls.queryset is None or cls.facet is None:
            return []
        publications_count_subquery = Subquery(
            PublicationFacet.objects
            .annotate(publications_count=Cardinality('publication_ids'))
            .filter(
                facet=cls.facet,
                value=OuterRef(cls.facet_value_field),
                lang=get_publications_lang(),
            )
            .values('publications_count')
        )
        query_result = (
            cls.queryset
            .annotate(**cls.get_annotations())
            .annotate(result_name=F(cls.get_name_field()))
            .annotate(publications_count=Coalesce(publications_count_subquery, 0))
            .filter(result_name__icontains=query)
            .order_by('-publications_count', 'result_name')
            .values('result_name', cls.facet_value_field)
        )[:limit]
        data = []
        for row in query_result:
            data.append({
                'text': row.get('result_name'),
                'facets': [
                    {'facet': cls.facet, 'value': row[cls.facet_value_field]},
                ],
            })
        return data


@register_suggest_type
class ProfessionSuggest(BaseFacetSuggest):

    queryset = Profession.active.all()
    suggest_type = JOBS_SUGGEST_TYPES.professions
    facet = PUBLICATION_FACETS.professions

    @classmethod
    def get_annotations(cls):
        return {
            'name_ru': F('name'),
        }


@register_suggest_type
class PublicProfessionSuggest(BaseFacetSuggest):

    queryset = PublicProfession.active.all()
    suggest_type = JOBS_SUGGEST_TYPES.public_professions
    facet = PUBLICATION_FACETS.public_professions


@register_suggest_type
class DBSuggest(BaseJobsSuggest):
    """ Подсказки для сайта ваканский по табличке (универсальные) """
    queryset = PublicationSuggest.objects.active()
    suggest_type = JOBS_SUGGEST_TYPES.db_suggest
    _levenshtein_distance = 1

    @classmethod
    def get_suggest_data(cls, query, limit):
        if not waffle.switch_is_active('enable_jobs_db_suggest'):
            raise ResponseError(status=status.HTTP_404_NOT_FOUND)

        query_result = (
            PublicationSuggest.objects.active()
            .annotate(
                distance=SubstringLevenshtein(
                    source_field='text',
                    query=query,
                    case_insensitive=True,
                ),
            )
            .filter(
                lang=get_publications_lang(),
                distance__lte=cls._levenshtein_distance,
            )
            .prefetch_related('facets')
            .order_by('distance', '-priority', 'text')
        )[:limit]
        data = []
        for row in query_result:
            facets = [{'facet': facet.facet, 'value': facet.value} for facet in row.facets.all()]
            data.append({
                'text': row.text,
                'facets': facets,
            })
        return data
