# coding: utf-8

import re
from operator import attrgetter
from typing import Optional

from django.db.models import F, Q
from rest_framework.filters import (
    BaseFilterBackend,
    OrderingFilter,
    SearchFilter,
)

from procu.api.enums import ES, PRIORITY
from procu.api.utils import strtobool
from procu.rest.filters import BaseListFilter

ID_REGEX = re.compile(r'YP-?(\d+)', flags=re.I)


class CFOFilter(BaseListFilter):
    query_param = 'cfo'
    field = 'cfo'


class AddressFilter(BaseListFilter):
    query_param = 'address'
    field = 'address'

    def apply_filter(self, queryset, condition):
        return queryset.filter(condition)


class LegalEntityFilter(BaseListFilter):
    query_param = 'legal_entity'
    field = 'legal_entity'

    def apply_filter(self, queryset, condition):
        return queryset.filter(condition)


class CategoryFilter(BaseListFilter):
    query_param = 'category'
    field = 'category'

    def apply_filter(self, queryset, condition):
        return queryset.filter(condition)


class AuthorFilter(BaseListFilter):
    query_param = 'author'
    field = 'author'

    def apply_filter(self, queryset, condition):
        return queryset.filter(condition)


class ManagerFilter(BaseListFilter):
    query_param = 'manager'
    field = 'manager'

    def apply_filter(self, queryset, condition):
        return queryset.filter(condition)


class YPSearchFilter(SearchFilter):
    def filter_queryset(self, request, queryset, view):
        search_terms = self.get_search_terms(request)

        try:
            term, = search_terms
            enquiry_id = int(ID_REGEX.match(term).group(1))
            return queryset.filter(id=enquiry_id)

        except (ValueError, AttributeError):
            pass

        return super().filter_queryset(request, queryset, view)


class EnquiryOrderingFilter(OrderingFilter):
    def filter_queryset(self, request, queryset, view):
        ordering = list(self.get_ordering(request, queryset, view))

        if ordering:
            if '-updated_at' not in ordering:
                ordering.extend(('-updated_at', 'id'))

            final_ordering = []

            for field in ordering:

                if field.endswith('deadline_at'):
                    func = attrgetter('desc' if field[0] == '-' else 'asc')
                    field = func(F('deadline_at'))(nulls_last=True)

                elif field.endswith('delivery_at'):
                    func = attrgetter('desc' if field[0] == '-' else 'asc')
                    field = func(F('delivery_at'))(nulls_last=True)

                final_ordering.append(field)

            return queryset.order_by(*final_ordering)

        return queryset


class StatusFilter(BaseFilterBackend):
    def filter_queryset(self, request, queryset, view):

        statuses = request.GET.getlist('status')
        statuses = set(ES.keys.values()).intersection(statuses)

        if not statuses:
            return queryset

        return queryset.filter(status__in=map(ES.get_by_key, statuses))


class PriorityFilter(BaseFilterBackend):
    def filter_queryset(self, request, queryset, view):

        priorities = request.GET.getlist('priority')
        priorities = set(PRIORITY.keys.values()).intersection(priorities)

        if not priorities:
            return queryset

        return queryset.filter(
            priority__in=map(PRIORITY.get_by_key, priorities)
        )


class SupplierFilter(BaseFilterBackend):
    def filter_queryset(self, request, queryset, view):

        # ----------------------------------------------------------------------
        # Extract supplier ids as positive integers from query field `suppliers`

        suppliers_raw = request.GET.getlist('supplier')

        supplier_ids = []
        for value in suppliers_raw:
            try:
                value = int(value)
                if value > 0:
                    supplier_ids.append(value)

            except Exception:
                pass

        # ----------------------------------------------------------------------

        has_won: Optional[bool] = strtobool(
            request.GET.get('has_won'), default=None
        )
        has_offer: Optional[bool] = strtobool(
            request.GET.get('has_offer'), default=None
        )

        # ----------------------------------------------------------------------

        supplier_condition = Q()
        flag_condition = Q()

        if not supplier_ids:
            if has_won is not None:
                cond = Q(filter_idx__has_won__isnull=False) & ~Q(
                    filter_idx__has_won=[]
                )
                if not has_won:
                    cond = ~cond

                flag_condition &= cond

            if has_offer is not None:
                cond = Q(filter_idx__has_offer__isnull=False) & ~Q(
                    filter_idx__has_offer=[]
                )
                if not has_offer:
                    cond = ~cond

                flag_condition &= cond

        for id_ in supplier_ids:

            supplier_condition |= Q(filter_idx__suppliers__contains=[id_])

            flag_cond = Q()

            if has_won is not None:
                cond = Q(filter_idx__has_won__contains=[id_])
                if not has_won:
                    cond = ~cond
                flag_cond &= cond

            if has_offer is not None:
                cond = Q(filter_idx__has_offer__contains=[id_])
                if not has_offer:
                    cond = ~cond
                flag_cond &= cond

            flag_condition |= flag_cond

        return queryset.filter(supplier_condition, flag_condition)
