import logging

from django.db.models import Q
from django.contrib.postgres.search import SearchQuery, SearchRank, SearchVector, TrigramSimilarity

from products.models import Product



def extract_search_filters(result_set):
    """
    Returned object should look something like:

    {
        "tags": {
            'singles': set(),
            'header1': []
        },
        "attributes": {
            'singles': set(),
            'header1': []
        },
    }
    """
    filter_dict = {
        "tags": {
            'singles': set(),
        },
        "attributes": {
            'singles': set(),
        }
    }
    for item in result_set:
        try:
            # extract tags
            tags = item.tags.all()
            for tag in tags:
                if len(tag.name.split('/')) == 1:
                    filter_dict['tags']['singles'].add(tag.name)
                else:
                    # set penultimate tag as header
                    chunks = tag.name.split('/')
                    header = chunks[-2]
                    name = chunks[-1]
                    # check if
                    entry = filter_dict['tags'].get(header)
                    if entry is None:
                        filter_dict['tags'][header] = set()
                    filter_dict['tags'][header].add(name)
            # extract attributes
            attributes = item.attributes.all()
            for tag in attributes:
                if len(tag.name.split('/')) == 1:
                    filter_dict['attributes']['singles'].add(tag.name)
                else:
                    # set penultimate tag as header
                    chunks = tag.name.split('/')
                    header = chunks[-2]
                    name = chunks[-1]
                    # check if
                    entry = filter_dict['attributes'].get(header)
                    if entry is None:
                        filter_dict['attributes'][header] = set()
                    filter_dict['attributes'][header].add(name)
        except Exception as e:
            logging.error(f'Extacting filters for {item}')
    return filter_dict


def find_related_products_v1(keyword):
    """
    Classical approach to the search

    Using Q objects

    """
    # search in tags
    tags = Product.tags.tag_model.objects.filter(name__icontains=keyword)
    # search in category
    categories = Product.category.tag_model.objects.filter(name__icontains=keyword)
    # search in attributes
    attributes = Product.attributes.tag_model.objects.filter(name__icontains=keyword)
    # unified tag search
    products_qs = Product.objects.filter(
        Q(name__icontains=keyword)|
        Q(description__icontains=keyword)|
        Q(tags__in=tags)|
        Q(category__in=categories)|
        Q(attributes__in=attributes)
    )
    return products_qs


def find_related_products_v5(keyword):
    """
    Single query solution, using Q objects
    """
    products_qs = Product.objects.filter(
        Q(name__icontains=keyword)|
        Q(description__icontains=keyword)|
        Q(tags__label__icontains=keyword)|
        Q(category__name__icontains=keyword)|
        Q(attributes__label__icontains=keyword)
    )
    return set(products_qs)


def find_related_products_v2(keyword):
    """
    More advanced: using search vectors
    """
    fields=('name', 'description', 'tags__label', 'attributes__label', 'category__name')
    vector = SearchVector(*fields)
    products_qs = Product.objects.annotate(
        search=vector
    ).filter(search=keyword)
    return set(products_qs)


def find_related_products_v3(keyword):
    """
    Ranked product search

    SearchVectors for the fields
    SearchQuery for the value
    SearchRank for relevancy scoring and ranking
    """
    vector = SearchVector('name') + SearchVector('description') + SearchVector('tags__label') + SearchVector('attributes__label') + SearchVector('category__name')
    query = SearchQuery(keyword)

    products_qs = Product.objects.annotate(
        rank=SearchRank(vector, query)
    ).filter(rank__gt=0.05)     # removed order_by because its lost in casting

    return set(products_qs)


def find_related_products_v6(keyword, shipping_cost=None, discount=None, category=None, tags=None, price_min=None,price_max=None):
    """
    Ranked product search

    SearchVectors for the fields
    SearchQuery for the value
    SearchRank for relevancy scoring and ranking

    allow filtering by:
    - shipping cost
    """
    vector = SearchVector('name') + SearchVector('description') + SearchVector('tags__label') + SearchVector('attributes__label') + SearchVector('category__name')
    query = SearchQuery(keyword)

    products_qs = Product.objects.annotate(
        rank=SearchRank(vector, query)
    ).filter(rank__gt=0.05)     # removed order_by because its lost in casting

    # filter by category
    if category is not None:
        products_qs = products_qs.filter(category=category)

    # filter by tags
    if tags is not None:
        products_qs = products_qs.filter(tags=tags)

    # filter by shipping cost
    if shipping_cost is True:
        # only instances with shipping costs
        products_qs = products_qs.filter(
            Q(shipping_cost__isnull=False)&
            Q(shipping_cost__gte=1)
        )
    elif shipping_cost is False:
        # only intances without shpping costs
        products_qs = products_qs.filter(Q(shipping_cost=None)|Q(shipping_cost=0.00))

    # filter by discount
    if discount is True:
        # only instances with shipping costs
        products_qs = products_qs.filter(
            Q(discount__isnull=False)&
            Q(discount__gte=1)
        )
    elif discount is False:
        # only intances without shpping costs
        products_qs = products_qs.filter(Q(discount=None)|Q(discount=0.00))

    # filter by price
    if price_min is not None:
        products_qs = products_qs.filter(price__gt=price_min)
    if price_max is not None:
        products_qs = products_qs.filter(price__lt=price_max)

    return set(products_qs)


def find_related_products_v4(keyword):
    """
    Similarity-ranked search using trigrams
    Not working
    """
    # fields=('name', 'description', 'tags__label', 'attributes__label', 'category__name')

    products_qs = Product.objects.annotate(
        similarity=TrigramSimilarity('name', keyword),
    ).order_by('-similarity')

    return set(products_qs)
