from django.conf import settings
from django.db.models import Q

from wagtail.search.utils import separate_filters_from_query

from oscar.apps.dashboard.catalogue import views as oscar
from oscar.core.loading import get_class, get_model

try:
    ProductElasticsearchIndex = get_class(
        "search.api.product", "ProductElasticsearchIndex"
    )
except Exception:
    ProductElasticsearchIndex = None
Product = get_model("catalogue", "Product")
try:
    paginate_result = get_class("search.api.pagination", "paginate_result")
except Exception:

    def paginate_result(rows, _count, _per_page):
        return rows


class SearchResultTableQuerysetSource(object):
    """
    This thing is needed to make django_tables2 think we are dealing with a queryset
    """

    def __init__(self, results, count):
        self._count = count
        self.results = results

    def __len__(self):
        return self._count

    def count(self):
        return self._count

    def exists(self):
        return self._count > 0

    @property
    def model(self):
        return self.results.model

    def order_by(self, *_):
        # there is no ordering possible here
        return self

    def __iter__(self):
        return self.results.iterator()

    def __getitem__(self, key):
        return self.results[key]

    def __call__(self, rows, per_page, *args, **kwargs):
        # this is the paginator
        return paginate_result(rows, self._count, per_page)


class ProductListView(oscar.ProductListView):
    SORT_BY_MAP = {"upc": "code"}

    def get_queryset(self):
        """
        Build the queryset for this list
        """
        queryset = Product.objects.browsable_dashboard().base_queryset()
        queryset = self.filter_queryset(queryset)
        queryset = self.apply_search(queryset)
        return queryset

    def get_table(self, **kwargs):
        if "q" in self.request.GET and not "sort" in self.request.GET:
            kwargs["order_by"] = ""
        return super().get_table(**kwargs)

    def get_table_pagination(self, table):
        table_pagination = super().get_table_pagination(table)
        original_data = table.data.data

        if isinstance(original_data, SearchResultTableQuerysetSource):
            table_pagination["paginator_class"] = original_data

        return table_pagination

    def apply_search(self, queryset):
        """
        Filter the queryset and set the description according to the search
        parameters given
        """
        # pylint: disable=attribute-defined-outside-init
        if "q" in self.request.GET:
            self.form = self.form_class(self.request.GET)
        else:
            self.form = self.form_class()

        if not self.form.is_valid():
            return queryset

        data = self.form.cleaned_data
        context = data["context"]
        q = data["q"]
        if q:
            if context == "all":
                filters, query_string = separate_filters_from_query(q)

                default_filters = [
                    {"terms": {"structure": [Product.PARENT, Product.STANDALONE]}},
                ]

                p = ProductElasticsearchIndex()
                try:
                    page = int(self.request.GET.get("page", 1)) - 1
                except ValueError:
                    page = 0

                from_ = page * settings.OSCAR_DASHBOARD_ITEMS_PER_PAGE

                results, num_found = p.search(
                    query_string=query_string,
                    filters=default_filters + p.format_filters(filters),
                    from_=from_,
                    sort_by=p.format_order_by(self.request.GET.getlist("sort")),
                    to=from_ + settings.OSCAR_DASHBOARD_ITEMS_PER_PAGE,
                )
                queryset = SearchResultTableQuerysetSource(results, num_found)
            elif context == "title":
                queryset = queryset.filter(title__icontains=q.strip())
            elif context == "upc":
                matches_upc = Product.objects.filter(upc=q)
                qs_match = queryset.filter(
                    Q(id__in=matches_upc.values("id"))
                    | Q(id__in=matches_upc.values("parent_id"))
                )

                if qs_match.exists():
                    queryset = qs_match
                else:
                    matches_upc = Product.objects.filter(upc__icontains=q.strip())
                    queryset = queryset.filter(
                        Q(id__in=matches_upc.values("id"))
                        | Q(id__in=matches_upc.values("parent_id"))
                    )

        return queryset
