import datetime

from django.db import connection
from django.db.models import Q, Count
from django.db.models.query import QuerySet
from django.db.utils import OperationalError
from django.utils.timezone import now


class InvoiceQuerySet(QuerySet):
    def overdue(self):
        return self.unpaid() \
            .filter(date_due__lt=datetime.datetime.combine(now().date(), datetime.time.max)) \
            .exclude(type=self.model.TYPE.CREDIT_NOTE)

    def not_overdue(self):
        return self.filter(Q(date_due__gt=datetime.datetime.combine(now().date(), datetime.time.max)) | Q(status__in=[self.model.STATUS.PAID, self.model.STATUS.CANCELED, self.model.STATUS.CREDITED]))

    def paid(self):
        return self.filter(status=self.model.STATUS.PAID)

    def unpaid(self):
        return self.exclude(Q(status__in=[self.model.STATUS.PAID, self.model.STATUS.CANCELED, self.model.STATUS.CREDITED]) | Q(total=0))

    def valid(self):
        return self.exclude(status__in=[self.model.STATUS.RETURNED, self.model.STATUS.CANCELED, self.model.STATUS.CREDITED, self.model.STATUS.UNCOLLECTIBLE])

    def accountable(self):
        return self.exclude(type__in=[self.model.TYPE.PROFORMA, self.model.TYPE.ADVANCE])

    def in_collection(self):
        return self.filter(status=self.model.STATUS.IN_COLLECTION)

    def not_in_collection(self):
        return self.exclude(status=self.model.STATUS.IN_COLLECTION)

    def collectible(self):
        return self.exclude(status=self.model.STATUS.UNCOLLECTIBLE)

    def uncollectible(self):
        return self.filter(status=self.model.STATUS.UNCOLLECTIBLE)

    def having_related_invoices(self):
        return self.exclude(related_invoices=None)

    def not_having_related_invoices(self):
        return self.filter(related_invoices=None)

    def duplicate_numbers(self):
        return list(self
                    .values('number')
                    .order_by()
                    .annotate(count=Count('id'))
                    .filter(count__gt=1)
                    .values_list('number', flat=True))

    def received(self):
        return self.filter(origin=self.model.ORIGIN.RECEIVED)

    def issued(self):
        return self.filter(origin=self.model.ORIGIN.ISSUED)

    def lock(self):
        """Lock the model table for atomic updates.

        Uses LOCK TABLE ... IN SHARE ROW EXCLUSIVE MODE. Backends that do not
        support it (e.g. SQLite) are skipped: the lock is attempted and
        OperationalError is ignored, so no vendor check is needed.
        """
        cursor = connection.cursor()
        table = self.model._meta.db_table
        try:
            cursor.execute("LOCK TABLE %s IN SHARE ROW EXCLUSIVE MODE" % table)
        except OperationalError:
            pass  # LOCK TABLE not supported (e.g. SQLite)


class ItemQuerySet(QuerySet):
    def with_tag(self, tag):
        return self.filter(tag=tag)
