from __future__ import unicode_literals

from datetime import timedelta
import logging

from django.apps import apps
from django.contrib.auth import get_user_model
from django.db import models
from django.db.models import F, Max
from django.utils.encoding import force_text
from django.utils.timezone import now

from .literals import STUB_EXPIRATION_INTERVAL
from .settings import setting_favorite_count, setting_recent_access_count

logger = logging.getLogger(__name__)


class DocumentManager(models.Manager):
    def get_by_natural_key(self, uuid):
        return self.model.passthrough.get(uuid=force_text(uuid))

    def get_queryset(self):
        return TrashCanQuerySet(
            model=self.model, using=self._db
        ).filter(in_trash=False).filter(is_stub=False)


class DocumentPageManager(models.Manager):
    def get_by_natural_key(self, page_number, document_version_natural_key):
        DocumentVersion = apps.get_model(
            app_label='documents', model_name='DocumentVersion'
        )
        try:
            document_version = DocumentVersion.objects.get_by_natural_key(*document_version_natural_key)
        except DocumentVersion.DoesNotExist:
            raise self.model.DoesNotExist

        return self.get(document_version__pk=document_version.pk, page_number=page_number)

    def get_queryset(self):
        return models.QuerySet(
            model=self.model, using=self._db
        ).filter(enabled=True)


class DocumentTypeManager(models.Manager):
    def check_delete_periods(self):
        logger.info(msg='Executing')

        for document_type in self.all():
            logger.info(
                'Checking deletion period of document type: %s', document_type
            )
            if document_type.delete_time_period and document_type.delete_time_unit:
                delta = timedelta(
                    **{
                        document_type.delete_time_unit: document_type.delete_time_period
                    }
                )
                logger.info(
                    'Document type: %s, has a deletion period delta of: %s',
                    document_type, delta
                )
                for document in document_type.deleted_documents.filter(deleted_date_time__lt=now() - delta):
                    logger.info(
                        'Document "%s" with id: %d, trashed on: %s, exceded '
                        'delete period', document, document.pk,
                        document.deleted_date_time
                    )
                    document.delete()
            else:
                logger.info(
                    'Document type: %s, has a no retention delta', document_type
                )

        logger.info(msg='Finshed')

    def check_trash_periods(self):
        logger.info(msg='Executing')

        for document_type in self.all():
            logger.info(
                'Checking trash period of document type: %s', document_type
            )
            if document_type.trash_time_period and document_type.trash_time_unit:
                delta = timedelta(
                    **{
                        document_type.trash_time_unit: document_type.trash_time_period
                    }
                )
                logger.info(
                    'Document type: %s, has a trash period delta of: %s',
                    document_type, delta
                )
                for document in document_type.documents.filter(date_added__lt=now() - delta):
                    logger.info(
                        'Document "%s" with id: %d, added on: %s, exceded '
                        'trash period', document, document.pk,
                        document.date_added
                    )
                    document.delete()
            else:
                logger.info(
                    'Document type: %s, has a no retention delta', document_type
                )

        logger.info(msg='Finshed')

    def get_by_natural_key(self, label):
        return self.get(label=label)


class DocumentVersionManager(models.Manager):
    def get_by_natural_key(self, checksum, document_natural_key):
        Document = apps.get_model(
            app_label='documents', model_name='Document'
        )
        try:
            document = Document.objects.get_by_natural_key(*document_natural_key)
        except Document.DoesNotExist:
            raise self.model.DoesNotExist

        return self.get(document__pk=document.pk, checksum=checksum)


class DuplicatedDocumentManager(models.Manager):
    def clean_empty_duplicate_lists(self):
        self.filter(documents=None).delete()

    def get_duplicated_documents(self):
        Document = apps.get_model(
            app_label='documents', model_name='Document'
        )
        return Document.objects.filter(
            pk__in=self.filter(documents__in_trash=False).values_list(
                'document_id', flat=True
            )
        )

    def scan(self):
        """
        Find duplicates by iterating over all documents and then
        find matching latest version checksums
        """
        Document = apps.get_model(
            app_label='documents', model_name='Document'
        )

        for document in Document.objects.all():
            self.scan_for(document=document, scan_children=False)

    def scan_for(self, document, scan_children=True):
        """
        Find duplicates by matching latest version checksums
        """
        if not document.latest_version:
            return None

        Document = apps.get_model(
            app_label='documents', model_name='Document'
        )

        # Get the documents whose latest version matches the checksum
        # of the current document and exclude the current document
        duplicates = Document.objects.annotate(
            max_timestamp=Max('versions__timestamp')
        ).filter(
            versions__timestamp=F('max_timestamp'),
            versions__checksum=document.checksum
        ).exclude(pk=document.pk)

        if duplicates.exists():
            instance, created = self.get_or_create(document=document)
            instance.documents.add(*duplicates)
        else:
            self.filter(document=document).delete()

        if scan_children:
            for document in duplicates:
                self.scan_for(document=document, scan_children=False)


class FavoriteDocumentManager(models.Manager):
    def add_for_user(self, user, document):
        favorite_document, created = self.model.objects.get_or_create(
            user=user, document=document
        )

        old_favorites_to_delete = self.filter(user=user).values_list('pk', flat=True)[setting_favorite_count.value:]
        self.filter(pk__in=list(old_favorites_to_delete)).delete()

    def get_by_natural_key(self, datetime_accessed, document_natural_key, user_natural_key):
        Document = apps.get_model(
            app_label='documents', model_name='Document'
        )
        User = get_user_model()
        try:
            document = Document.objects.get_by_natural_key(*document_natural_key)
        except Document.DoesNotExist:
            raise self.model.DoesNotExist
        else:
            try:
                user = User.objects.get_by_natural_key(*user_natural_key)
            except User.DoesNotExist:
                raise self.model.DoesNotExist

        return self.get(document__pk=document.pk, user__pk=user.pk)

    def get_for_user(self, user):
        Document = apps.get_model(
            app_label='documents', model_name='Document'
        )

        return Document.objects.filter(favorites__user=user)

    def remove_for_user(self, user, document):
        self.get(user=user, document=document).delete()


class PassthroughManager(models.Manager):
    def delete_stubs(self):
        for stale_stub_document in self.filter(is_stub=True, date_added__lt=now() - timedelta(seconds=STUB_EXPIRATION_INTERVAL)):
            stale_stub_document.delete(to_trash=False)


class RecentDocumentManager(models.Manager):
    def add_document_for_user(self, user, document):
        if user.is_authenticated:
            new_recent, created = self.model.objects.get_or_create(
                user=user, document=document
            )
            if not created:
                # document already in the recent list, just save to force
                # accessed date and time update
                new_recent.save()

            recent_to_delete = self.filter(user=user).values_list('pk', flat=True)[setting_recent_access_count.value:]
            self.filter(pk__in=list(recent_to_delete)).delete()
        return new_recent

    def get_by_natural_key(self, datetime_accessed, document_natural_key, user_natural_key):
        Document = apps.get_model(
            app_label='documents', model_name='Document'
        )
        User = get_user_model()
        try:
            document = Document.objects.get_by_natural_key(*document_natural_key)
        except Document.DoesNotExist:
            raise self.model.DoesNotExist
        else:
            try:
                user = User.objects.get_by_natural_key(*user_natural_key)
            except User.DoesNotExist:
                raise self.model.DoesNotExist

        return self.get(
            document__pk=document.pk, user__pk=user.pk,
            datetime_accessed=datetime_accessed
        )

    def get_for_user(self, user):
        Document = apps.get_model(
            app_label='documents', model_name='Document'
        )

        if user.is_authenticated:
            return Document.objects.filter(
                recent__user=user
            ).order_by('-recent__datetime_accessed')
        else:
            return Document.objects.none()


class TrashCanManager(models.Manager):
    def get_queryset(self):
        return super(
            TrashCanManager, self
        ).get_queryset().filter(in_trash=True)


class TrashCanQuerySet(models.QuerySet):
    def delete(self, to_trash=True):
        for instance in self:
            instance.delete(to_trash=to_trash)
