"""Base search backend."""

from django.core.exceptions import ImproperlyConfigured

from django_elasticsearch_dsl import fields
from rest_framework.filters import BaseFilterBackend
from rest_framework.settings import api_settings

from ..mixins import FilterBackendMixin
from ...compat import coreapi, coreschema
from ...constants import MATCHING_OPTIONS, DEFAULT_MATCHING_OPTION

__title__ = "django_elasticsearch_dsl_drf_alt.filter_backends.search.common"
__author__ = "Artur Barseghyan <artur.barseghyan@gmail.com>"
__copyright__ = "2017-2020 Artur Barseghyan"
__license__ = "GPL 2.0/LGPL 2.1"
__all__ = ("BaseSearchFilterBackend",)


class BaseSearchFilterBackend(BaseFilterBackend, FilterBackendMixin):
    """Base search filter backend."""

    query_backends = []

    matching = DEFAULT_MATCHING_OPTION

    search_param = api_settings.SEARCH_PARAM

    def get_search_query_params(self, request):
        """Get search query params.

        :param request: Django REST framework request.
        :type request: rest_framework.request.Request
        :return: List of search query params.
        :rtype: list
        """
        query_params = request.query_params.copy()
        return query_params.getlist(self.search_param, [])

    def get_query_backends(self, request, view):
        """Get query backends.

        :return:
        """
        raise NotImplementedError(
            "You should define `get_query_backends` method in your {} class"
            "".format(self.__class__.__name__)
        )

    def _get_query_backends(self, request, view):
        """Get query backends internal.

        :param request:
        :param view:
        :return:
        """
        try:
            return self.get_query_backends(request, view)
        except NotImplementedError as err:
            pass

        if not self.query_backends:
            raise NotImplementedError(
                "Your search backend shall either implement "
                "`get_query_backends` method or define `query_backends`"
                "property."
            )
        return self.query_backends[:]

    def filter_queryset(self, request, queryset, view):
        """Filter the queryset.

        :param request: Django REST framework request.
        :param queryset: Base queryset.
        :param view: View.
        :type request: rest_framework.request.Request
        :type queryset: elasticsearch_dsl.search.Search
        :type view: rest_framework.viewsets.ReadOnlyModelViewSet
        :return: Updated queryset.
        :rtype: elasticsearch_dsl.search.Search
        """
        if self.matching not in MATCHING_OPTIONS:
            raise ImproperlyConfigured(
                "Your `matching` value does not match the allowed matching"
                "options: {}".format(", ".join(MATCHING_OPTIONS))
            )

        __query_backends = self._get_query_backends(request, view)

        if len(__query_backends) > 1:
            __queries = []
            for query_backend in __query_backends:
                __queries.extend(
                    query_backend.construct_search(
                        request=request, view=view, search_backend=self
                    )
                )

            if __queries:
                queryset = queryset.query("bool", **{self.matching: __queries})

        elif len(__query_backends) == 1:
            __query = __query_backends[0].construct_search(
                request=request, view=view, search_backend=self
            )
            queryset = queryset.query("bool", **{self.matching: __query})

        else:
            raise ImproperlyConfigured(
                "Search filter backend shall have at least one query_backend"
                "specified either in `query_backends` property or "
                "`get_query_backends` method. Make appropriate changes to"
                "your {} class".format(self.__class__.__name__)
            )

        return queryset

    def get_coreschema_field(self, field):
        if isinstance(field, fields.IntegerField):
            field_cls = coreschema.Number
        else:
            field_cls = coreschema.String
        return field_cls()

    def get_schema_fields(self, view):
        assert coreapi is not None, (
            "coreapi must be installed to " "use `get_schema_fields()`"
        )
        assert coreschema is not None, (
            "coreschema must be installed to " "use `get_schema_fields()`"
        )

        _search_fields = getattr(view, "search_fields", None)
        if isinstance(_search_fields, dict):
            search_fields = list(_search_fields.keys())
        else:
            search_fields = _search_fields

        return (
            []
            if not search_fields
            else [
                coreapi.Field(
                    name=self.search_param,
                    required=False,
                    location="query",
                    schema=coreschema.String(
                        description="Search in " "{}.".format(", ".join(search_fields))
                    ),
                )
            ]
        )
