""" This module holds the views of the ``documents`` app. """

import logging
import os

from django.conf import settings
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.http import HttpResponse
from django.shortcuts import get_object_or_404, redirect
from django.utils.functional import cached_property
from django.utils.translation import ugettext_lazy as _
from django.views import generic

from mtp.apps.common.views_utils import (
    AjaxResponseMixin,
    RestrictToObjectOwner,
    SuccessMessageMixin,
    SyncableDeleteView
)
from mtp.apps.eventlog.models import ACTION_CHOICES as EVENT_ACTIONS
from mtp.apps.eventlog.helpers import event_log
from mtp.apps.trainings.models import Training
from mtp.apps.trainings.views import TrainingBaseView

from .models import Document, Note


logger = logging.getLogger('mtp.apps.documents')  # pylint: disable=C0103


class DocumentBase(TrainingBaseView):
    """Base view to use on all views related to the documents.

    Define the queryset to use for the current user.

    """

    def get_document_queryset_for_current_user(self):
        """Get all ``Document`` instances the current user is allowed to see.

        The user can only see documents instance he own.
        Also excludes deleted documents from the result.

        Returns
        -------
        Queryset of ``mtp.apps.workshops.models.Workshops``
            A lazy queryset with the wanted workshops.

        """
        return Document.objects.non_deleted().filter(
            training=self.get_training_queryset_for_current_user()
        )


class DocumentEditBase(AjaxResponseMixin, SuccessMessageMixin, DocumentBase):
    """Base view to create or update a ``Document`` instance.

    """
    model = Document
    fields = [
        'name',
        'trainer_document_file',
        'trainee_document_file',
        'trainer_only',
        'needs_approval'
    ]
    pk_url_kwarg = 'document_uuid'

    def get_queryset(self):
        return super().get_document_queryset_for_current_user()


class DocumentDetailView(DocumentEditBase, generic.DetailView):
    """The details view of a ``mtp.apps.documents.models.Document`` instance."""


class DocumentCreateView(DocumentEditBase, generic.CreateView):
    """The view to create a ``mtp.apps.documents.models.Document`` instance.

    Notes
    -----
    We need to get the current ``Training`` and add it to the context.

    """

    success_message = _("Your document was successfully created.")

    def get_context_data(self, **kwargs):
        """Add ``training`` to the context."""

        return super().get_context_data(training=self.training, **kwargs)

    @cached_property
    def training(self):
        """Returns the current training based on the ``training_uuid``
        argument in the url.

        Raises
        ------
        ``django.http.Http404``
            If the training doesn't exists.

        """
        return get_object_or_404(
            Training.objects.non_deleted().filter(owner=self.request.user),
            pk=self.kwargs['training_uuid']
        )

    def form_valid(self, form):
        """Set the ``training`` save the document and redirect the user.

        Parameters
        ----------
        form : instance of ``django.forms.models.ModelForm``
            The validated form to save.

        Returns
        -------
        instance of django.http.HttpResponseRedirect
            A redirect to the detail url of the document.

        Notes
        -----
        This method doesn't call ``super`` but implements all the necessary
        stuff (saving and redirecting)

        """
        self.object = form.save(commit=False)
        self.object.training = self.training
        self.object.save()

        return super().form_valid(form)


class DocumentUpdateView(DocumentEditBase, generic.UpdateView):
    """The view to update a ``mtp.apps.documents.models.Document`` instance."""

    success_message = _("Your document was successfully updated.")

    def get_context_data(self, **kwargs):
        """Add the ``Training`` to the context."""

        return super().get_context_data(
            training=self.object.training,
            **kwargs)


class DocumentDeleteView(DocumentEditBase, SyncableDeleteView):
    """The view to soft-delete a ``mtp.apps.documents.models.Document`` instance."""

    success_message = _('Your document has been successfully deleted.')


def _trigger_document_download(user, document, document_url):
    """
    Launch the download of the document at the specified URL.
    If we are behind Nginx, we can use X-Accel-Redirect, otherwise we
    simply redirect to the URL.

    Also, we can log the download, which is pretty neat.

    """
    # Log the download for statistics purposes.
    event_log(subject=document, sender=user, action=EVENT_ACTIONS.DOCUMENT_DOWNLOAD)

    # Trigger the download.
    if settings.USE_REVERSE_PROXY:

        # Use X-Accel-Redirect to trigger the download, thus not
        # revealing the actual document URL.
        document_filename = document.file.name
        http_headers = {
            'Content-Disposition': 'attachment; filename=%s' % document_filename,
            'X-Accel-Redirect': document_url,
        }
        response = HttpResponse()
        for key, value in http_headers.items():
            response[key] = value

        return response

    else:

        # Issue a simple redirection to the document URL.
        return redirect(document_url)


@login_required
def document_download(request, document_id):
    user = request.user
    document = get_object_or_404(Document, pk=document_id)
    document_url = document.doc_file.orig_file.url

    # If the document is not a PDF, no need to worry about notes: download.
    if document.doc_file.doc_type != DOCUMENT_TYPE.PDF:
        return _trigger_document_download(user, document, document_url)

    # If the document hasn't been annotated by the current user: download.
    notes = Note.objects.filter(user=user, document=document)
    if not notes.exists():
        return _trigger_document_download(user, document, document_url)

    # So. We have an annotated PDF. Does it exist yet?
    annotated_doc_filename = '%s-%s.pdf' % (document.id, user.id)
    annotated_doc_path = os.path.join(settings.ANNOTATIONS_DATA_ROOT, annotated_doc_filename)
    if os.path.exists(annotated_doc_path):
        annotated_doc_url = os.path.join(settings.ANNOTATIONS_DATA_URL, annotated_doc_filename)
        return _trigger_document_download(user, document, annotated_doc_url)

    # No, it doesn't. Is it being created?
    annotated_doc_path_tmp = '%s.tmp' % annotated_doc_path
    if os.path.exists(annotated_doc_path_tmp):
        messages.success(request, _(
            "Your document is currently being generated. Please try again in a few seconds."
        ))
        return redirect('trainings:training_contents', training_id=document.training.id)

    # No, it isn't. Let's create it.
    try:
        annotated_doc_path = document.compose_annotated_version(user, notes)

    except:  # FIXME: except what?
             # Not sure right now how this may fail, but it can probably
             # do so in a number of entertaining ways.

        logger.exception("Annotated document generation failed.")

        messages.error(request, _(
            "We are unable to generate your personalized document. "
            "We apologize for the inconvenience."
        ))
        return redirect('trainings:training_contents', training_id=document.training.id)

    annotated_doc_url = os.path.join(settings.ANNOTATIONS_DATA_URL, annotated_doc_path)
    return _trigger_document_download(user, document, annotated_doc_url)
