from django.db import transaction
from django.db.models import F
from django.http import Http404, HttpResponse, HttpResponseRedirect
from django.shortcuts import get_object_or_404
from django.urls import reverse, reverse_lazy
from django.utils.translation import gettext as _
from django.views import static
from django.views.generic import (
    CreateView,
    DeleteView,
    DetailView,
    TemplateView,
)
from django.views.generic.detail import (
    SingleObjectMixin,
    SingleObjectTemplateResponseMixin,
)
from django.views.generic.edit import FormMixin, ProcessFormView

from .compat import LoginRequiredMixin
from .conf import settings
from .forms import (
    ColleagueFolderShareForm,
    DocumentCreateForm,
    DocumentCreateFormWithName,
    FolderCreateForm,
)
from .hooks import hookset
from .models import Document, Folder, UserStorage


class IndexView(LoginRequiredMixin, TemplateView):

    template_name = "pinax/documents/index.html"

    def get_context_data(self, **kwargs):
        ctx = kwargs
        ctx.update({
            "members": Folder.objects.members(None, user=self.request.user),
            "storage": self.request.user.storage,
            "can_share": False,
        })
        return ctx


class FolderCreate(LoginRequiredMixin, CreateView):
    model = Folder
    form_class = FolderCreateForm
    template_name = "pinax/documents/folder_create.html"
    parent = None

    def get(self, request, *args, **kwargs):
        if "p" in request.GET:
            qs = self.model.objects.for_user(request.user)
            self.parent = get_object_or_404(qs, pk=request.GET["p"])
        else:
            self.parent = None
        return super().get(request, *args, **kwargs)

    def get_context_data(self, **kwargs):
        kwargs.setdefault("parent", self.parent)
        return super().get_context_data(**kwargs)

    def get_initial(self):
        if self.parent:
            self.initial["parent"] = self.parent
        return super().get_initial()

    def get_form_kwargs(self):
        kwargs = super().get_form_kwargs()
        kwargs.update({"folders": self.model.objects.for_user(self.request.user)})
        return kwargs

    def create_folder(self, **kwargs):
        folder = self.model.objects.create(**kwargs)
        folder.touch(self.request.user)
        # if folder is not amongst anything shared it will share with no
        # users which share will no-op; perhaps not the best way?
        folder.share(folder.shared_parent().shared_with())
        return folder

    def form_valid(self, form):
        kwargs = {
            "name": form.cleaned_data["name"],
            "author": self.request.user,
            "parent": form.cleaned_data["parent"],
        }
        self.object = self.create_folder(**kwargs)
        hookset.folder_created_message(self.request, self.object)
        return HttpResponseRedirect(self.get_success_url())


class FolderDetail(LoginRequiredMixin, DetailView):
    model = Folder
    template_name = "pinax/documents/folder_detail.html"

    def get_queryset(self):
        qs = super().get_queryset()
        qs = qs.for_user(self.request.user)
        return qs

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        ctx = {
            "members": self.object.members(user=self.request.user),
            "can_share": self.object.can_share(self.request.user),
        }
        context.update(ctx)
        return context


class FolderShare(LoginRequiredMixin,
                  SingleObjectTemplateResponseMixin,
                  FormMixin,
                  SingleObjectMixin,
                  ProcessFormView):
    model = Folder
    context_object_name = "folder"
    form_class = ColleagueFolderShareForm
    template_name = "pinax/documents/folder_share.html"

    def get(self, request, *args, **kwargs):
        self.object = self.get_object()
        return super().get(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        self.object = self.get_object()
        return super().post(request, *args, **kwargs)

    def get_queryset(self):
        qs = super().get_queryset()
        qs = qs.for_user(self.request.user)
        return qs

    def get_object(self):
        folder = super().get_object()
        if not folder.can_share(self.request.user):
            raise Http404(_(f"Cannot share folder '{folder}'."))
        return folder

    def get_form_kwargs(self):
        kwargs = super().get_form_kwargs()
        can_share_with = hookset.share_with_options(self.request.user, self.object)
        kwargs.update({"colleagues": can_share_with})
        return kwargs

    def form_valid(self, form):
        self.object.share(form.cleaned_data["participants"])
        hookset.folder_shared_message(self.request, self.request.user, self.object)
        return HttpResponseRedirect(self.get_success_url())

    def get_success_url(self):
        return reverse("pinax_documents:folder_detail", args=[self.object.pk])

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        ctx = {
            "participants": self.object.shared_with(),
        }
        context.update(ctx)
        return context


class FolderDelete(LoginRequiredMixin, DeleteView):
    model = Folder
    success_url = reverse_lazy("pinax_documents:document_index")
    template_name = "pinax/documents/folder_confirm_delete.html"

    def delete(self, request, *args, **kwargs):
        hookset.folder_pre_delete(self.request, self.get_object())
        success_url = super().delete(request, *args, **kwargs)
        hookset.folder_deleted_message(self.request, self.object)
        return success_url


class DocumentCreate(LoginRequiredMixin, CreateView):
    model = Document
    form_class = DocumentCreateForm
    template_name = "pinax/documents/document_create.html"
    folder = None

    def get(self, request, *args, **kwargs):
        if "f" in request.GET:
            qs = Folder.objects.for_user(request.user)
            self.folder = get_object_or_404(qs, pk=request.GET["f"])
        else:
            self.folder = None
        return super().get(request, *args, **kwargs)

    def get_context_data(self, **kwargs):
        kwargs.setdefault("folder", self.folder)
        return super().get_context_data(**kwargs)

    def get_initial(self):
        if self.folder:
            self.initial["folder"] = self.folder
        return super().get_initial()

    def get_form_kwargs(self):
        kwargs = super().get_form_kwargs()
        kwargs.update({"folders": Folder.objects.for_user(self.request.user),
                       "storage": self.request.user.storage})
        return kwargs

    def create_document(self, **kwargs):
        document = self.model.objects.create(**kwargs)
        document.touch(self.request.user)
        if document.folder is not None:
            # if folder is not amongst anything shared it will share with no
            # users which share will no-op; perhaps not the best way?
            document.share(document.folder.shared_parent().shared_with())
        return document

    def increase_usage(self, bytes):
        # increase usage for this user based on document size
        storage_qs = UserStorage.objects.filter(pk=self.request.user.storage.pk)
        storage_qs.update(bytes_used=F("bytes_used") + bytes)

    def get_create_kwargs(self, form):
        return {
            "name": form.cleaned_data["file"].name,
            "original_filename": form.cleaned_data["file"].name,
            "folder": form.cleaned_data["folder"],
            "author": self.request.user,
            "file": form.cleaned_data["file"],
        }

    def form_valid(self, form):
        with transaction.atomic():
            kwargs = self.get_create_kwargs(form)
            self.object = self.create_document(**kwargs)
            hookset.document_created_message(self.request, self.object)
            bytes = form.cleaned_data["file"].size
            self.increase_usage(bytes)
            return HttpResponseRedirect(self.get_success_url())


class DocumentWithCustomNameCreate(DocumentCreate):

    form_class = DocumentCreateFormWithName

    def get_create_kwargs(self, form):
        return {
            "name": form.cleaned_data["name"],
            "original_filename": form.cleaned_data["file"].name,
            "folder": form.cleaned_data["folder"],
            "author": self.request.user,
            "file": form.cleaned_data["file"],
        }


class DocumentDetail(LoginRequiredMixin, DetailView):
    model = Document
    template_name = "pinax/documents/document_detail.html"

    def get_queryset(self):
        qs = super().get_queryset()
        qs = qs.for_user(self.request.user)
        return qs


class DocumentDownload(LoginRequiredMixin, DetailView):
    model = Document

    def get_queryset(self):
        qs = super().get_queryset()
        qs = qs.for_user(self.request.user)
        return qs

    def get(self, request, *args, **kwargs):
        self.object = self.get_object()
        if settings.DOCUMENTS_USE_X_ACCEL_REDIRECT:
            response = HttpResponse()
            response["X-Accel-Redirect"] = self.object.file.url
            # delete content-type to allow Gondor to determine the filetype and
            # we definitely don't want Django's crappy default :-)
            del response["content-type"]
        else:
            # Note:
            #
            # The 'django.views.static.py' docstring states:
            #
            #     Views and functions for serving static files. These are only to be used
            #     during development, and SHOULD NOT be used in a production setting.
            #
            response = static.serve(request, self.object.file.name,
                                    document_root=settings.MEDIA_ROOT)
        return response


class DocumentDelete(LoginRequiredMixin, DeleteView):
    model = Document
    success_url = reverse_lazy("pinax_documents:document_index")
    template_name = "pinax/documents/document_confirm_delete.html"

    def delete(self, request, *args, **kwargs):
        success_url = super().delete(request, *args, **kwargs)
        hookset.document_deleted_message(self.request, self.object)
        return success_url