# -*- coding: utf-8 -*-
import logging

from django.db import models
from django.db.models.query import QuerySet
from django.db.models.sql import EmptyResultSet

from .mixins import (
    CacheBackendMixin,
    CacheInvalidateMixin,
    CacheKeyMixin,
)

logger = logging.getLogger(__name__)


class CacheManager(CacheInvalidateMixin, models.Manager):
    """
    Custom model manager that returns CachingQuerySet
    """

    # Use this manager when accessing objects that are related to from some other model.
    # Works only for one-to-one relationships not for many-to-many or foreign keys. See https://code.djangoproject.com/ticket/14891
    # so post_save, post_delete signals are used for cache invalidation. Signals can be removed when this bug is fixed.
    use_for_related_fields = True

    # django <=1.5
    def get_query_set(self):
        return CachingQuerySet(self.model, using=self._db)

    def get_queryset(self):
        return CachingQuerySet(self.model, using=self._db)


class CachingQuerySet(CacheBackendMixin, CacheKeyMixin, CacheInvalidateMixin, QuerySet):
    """
    Custom query set that caches results on load. This query set will force iteration of the result set
    so that the results can be cached for future calls.

    Query set invalidates model cache for any calls to bulk_create or update.
    """

    def iterator(self):
        try:
            key = self.generate_key()
        # workaround for Django bug # 12717
        except EmptyResultSet:
            return
        result_set = self.cache_backend.get(key)
        if result_set is None:
            logger.debug('cache miss for key {0}'.format(key))
            result_set = list(super(CachingQuerySet, self).iterator())
            self.cache_backend.set(key, result_set)
        for result in result_set:
            yield result

    def bulk_create(self, *args, **kwargs):
        self.invalidate_model_cache()
        return super(CachingQuerySet, self).bulk_create(*args, **kwargs)

    def update(self, **kwargs):
        self.invalidate_model_cache()
        return super(CachingQuerySet, self).update(**kwargs)