from __future__ import absolute_import, unicode_literals

from django.conf import settings
from django.contrib import messages
from django.core.exceptions import ValidationError
from django.core.urlresolvers import reverse, reverse_lazy
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404
from django.utils.encoding import force_text
from django.utils.http import urlencode
from django.utils.translation import ugettext_lazy as _, ungettext

from acls.models import AccessControlList
from common.generics import (
    AssignRemoveView, MultipleObjectFormActionView, SingleObjectCreateView,
    SingleObjectDeleteView, SingleObjectEditView, SingleObjectListView
)
from documents.models import Document, DocumentType
from documents.permissions import (
    permission_document_type_edit
)

from .api import save_metadata_list
from .forms import (
    DocumentAddMetadataForm, DocumentMetadataFormSet,
    DocumentMetadataRemoveFormSet, MetadataTypeForm
)
from .models import DocumentMetadata, MetadataType
from .permissions import (
    permission_metadata_document_add, permission_metadata_document_edit,
    permission_metadata_document_remove, permission_metadata_document_view,
    permission_metadata_type_create, permission_metadata_type_delete,
    permission_metadata_type_edit, permission_metadata_type_view
)


class DocumentMetadataAddView(MultipleObjectFormActionView):
    form_class = DocumentAddMetadataForm
    model = Document
    object_permission = permission_metadata_document_add
    success_message = _('Metadata add request performed on %(count)d document')
    success_message_plural = _(
        'Metadata add request performed on %(count)d documents'
    )

    def dispatch(self, request, *args, **kwargs):
        result = super(DocumentMetadataAddView, self).dispatch(request, *args, **kwargs)

        queryset = self.get_queryset()

        for document in queryset:
            document.add_as_recent_document_for_user(request.user)

        if len(set([document.document_type.pk for document in queryset])) > 1:
            messages.error(
                request, _('Selected documents must be of the same type.')
            )
            return HttpResponseRedirect(self.previous_url)

        return result

    def form_valid(self, form):
        result = super(DocumentMetadataAddView, self).form_valid(form=form)

        queryset = self.get_queryset()

        if self.action_count == 1:
            return HttpResponseRedirect(
                reverse('metadata:metadata_edit', args=(queryset.first().pk,)),
            )
        elif self.action_count > 1:
            return HttpResponseRedirect(
                '%s?%s' % (
                    reverse('metadata:metadata_multiple_edit'),
                    urlencode(
                        {
                            'id_list': ','.join(
                                map(
                                    force_text,
                                    queryset.values_list('pk', flat=True)
                                )
                            )
                        }
                    )
                )
            )

        return result

    def get_extra_context(self):
        queryset = self.get_queryset()

        result = {
            'submit_icon': 'fa fa-plus',
            'submit_label': _('Add'),
            'title': ungettext(
                'Add metadata types to document',
                'Add metadata types to documents',
                queryset.count()
            )
        }

        if queryset.count() == 1:
            result.update(
                {
                    'object': queryset.first(),
                    'title': _(
                        'Add metadata types to document: %s'
                    ) % queryset.first()
                }
            )

        return result

    def get_form_extra_kwargs(self):
        queryset = self.get_queryset()

        result = {}

        if queryset.count():
            result.update(
                {
                    'document_type': queryset.first().document_type,
                }
            )

        if queryset.count() == 1:
            result.update(
                {
                    'queryset': MetadataType.objects.exclude(
                        pk__in=MetadataType.objects.get_for_document(
                            document=queryset.first()
                        )
                    )
                }
            )

        return result

    def object_action(self, form, instance):
        for metadata_type in form.cleaned_data['metadata_type']:
            document_metadata, created = DocumentMetadata.objects.get_or_create(
                document=instance,
                metadata_type=metadata_type,
                defaults={'value': ''}
            )

            if created:
                messages.success(
                    self.request,
                    _(
                        'Metadata type: %(metadata_type)s '
                        'successfully added to document %(document)s.'
                    ) % {
                        'metadata_type': metadata_type,
                        'document': instance
                    }
                )
            else:
                messages.warning(
                    self.request, _(
                        'Metadata type: %(metadata_type)s already '
                        'present in document %(document)s.'
                    ) % {
                        'metadata_type': metadata_type,
                        'document': instance
                    }
                )


class DocumentMetadataEditView(MultipleObjectFormActionView):
    form_class = DocumentMetadataFormSet
    model = Document
    object_permission = permission_metadata_document_edit
    success_message = _(
        'Metadata edit request performed on %(count)d document'
    )
    success_message_plural = _(
        'Metadata edit request performed on %(count)d documents'
    )

    def dispatch(self, request, *args, **kwargs):
        result = super(DocumentMetadataEditView, self).dispatch(request, *args, **kwargs)

        queryset = self.get_queryset()

        for document in queryset:
            document.add_as_recent_document_for_user(request.user)

        if len(set([document.document_type.pk for document in queryset])) > 1:
            messages.error(
                request, _('Selected documents must be of the same type.')
            )
            return HttpResponseRedirect(self.previous_url)

        return result

    def form_valid(self, form):
        result = super(DocumentMetadataEditView, self).form_valid(form=form)

        queryset = self.get_queryset()

        if self.action_count == 1:
            return HttpResponseRedirect(
                reverse('metadata:metadata_edit', args=(queryset.first().pk,)),
            )
        elif self.action_count > 1:
            return HttpResponseRedirect(
                '%s?%s' % (
                    reverse('metadata:metadata_multiple_edit'),
                    urlencode(
                        {
                            'id_list': ','.join(
                                queryset.value_list('pk', flat=True)
                            )
                        }
                    )
                )
            )

        return result

    def get_extra_context(self):
        queryset = self.get_queryset()

        result = {
            'form_display_mode_table': True,
            'submit_icon': 'fa fa-pencil',
            'submit_label': _('Edit'),
            'title': ungettext(
                'Edit document metadata',
                'Edit documents metadata',
                queryset.count()
            )
        }

        if queryset.count() == 1:
            result.update(
                {
                    'object': queryset.first(),
                    'title': _(
                        'Edit metadata for document: %s'
                    ) % queryset.first()
                }
            )

        return result

    def get_initial(self):
        queryset = self.get_queryset()

        metadata_dict = {}
        initial = []

        for document in queryset:
            document.add_as_recent_document_for_user(self.request.user)

            for document_metadata in document.metadata.all():
                metadata_dict.setdefault(document_metadata.metadata_type, set())

                if document_metadata.value:
                    metadata_dict[
                        document_metadata.metadata_type
                    ].add(document_metadata.value)

        for key, value in metadata_dict.items():
            initial.append({
                'document_type': document.document_type,
                'metadata_type': key,
                'value': ', '.join(value) if value else '',
            })

        return initial

    def object_action(self, form, instance):
        errors = []
        for form in form.forms:
            if form.cleaned_data['update']:
                try:
                    save_metadata_list([form.cleaned_data], instance)
                except Exception as exception:
                    errors.append(exception)

        for error in errors:
            if settings.DEBUG:
                raise
            else:
                if isinstance(error, ValidationError):
                    exception_message = ', '.join(error.messages)
                else:
                    exception_message = unicode(error)

                messages.error(
                    self.request, _(
                        'Error editing metadata for document: '
                        '%(document)s; %(exception)s.'
                    ) % {
                        'document': instance,
                        'exception': exception_message
                    }
                )
        else:
            messages.success(
                self.request,
                _(
                    'Metadata for document %s edited successfully.'
                ) % instance
            )


class DocumentMetadataListView(SingleObjectListView):
    def dispatch(self, request, *args, **kwargs):
        AccessControlList.objects.check_access(
            permissions=permission_metadata_document_view,
            user=self.request.user, obj=self.get_document()
        )

        return super(DocumentMetadataListView, self).dispatch(
            request, *args, **kwargs
        )

    def get_document(self):
        return get_object_or_404(Document, pk=self.kwargs['pk'])

    def get_extra_context(self):
        document = self.get_document()
        return {
            'hide_link': True,
            'object': document,
            'title': _('Metadata for document: %s') % document,
        }

    def get_queryset(self):
        return self.get_document().metadata.all()


class DocumentMetadataRemoveView(MultipleObjectFormActionView):
    form_class = DocumentMetadataRemoveFormSet
    model = Document
    object_permission = permission_metadata_document_remove
    success_message = _(
        'Metadata remove request performed on %(count)d document'
    )
    success_message_plural = _(
        'Metadata remove request performed on %(count)d documents'
    )

    def dispatch(self, request, *args, **kwargs):
        result = super(DocumentMetadataRemoveView, self).dispatch(request, *args, **kwargs)

        queryset = self.get_queryset()

        for document in queryset:
            document.add_as_recent_document_for_user(request.user)

        if len(set([document.document_type.pk for document in queryset])) > 1:
            messages.error(
                request, _('Selected documents must be of the same type.')
            )
            return HttpResponseRedirect(self.previous_url)

        return result

    def form_valid(self, form):
        result = super(DocumentMetadataRemoveView, self).form_valid(form=form)

        queryset = self.get_queryset()

        if self.action_count == 1:
            return HttpResponseRedirect(
                reverse('metadata:metadata_edit', args=(queryset.first().pk,)),
            )
        elif self.action_count > 1:
            return HttpResponseRedirect(
                '%s?%s' % (
                    reverse('metadata:metadata_multiple_edit'),
                    urlencode(
                        {
                            'id_list': ','.join(
                                queryset.value_list('pk', flat=True)
                            )
                        }
                    )
                )
            )

        return result

    def get_extra_context(self):
        queryset = self.get_queryset()

        result = {
            'form_display_mode_table': True,
            'submit_icon': 'fa fa-minus',
            'submit_label': _('Remove'),
            'title': ungettext(
                'Remove metadata types from the document',
                'Remove metadata types from the documents',
                queryset.count()
            )
        }

        if queryset.count() == 1:
            result.update(
                {
                    'object': queryset.first(),
                    'title': _(
                        'Remove metadata types from the document: %s'
                    ) % queryset.first()
                }
            )

        return result

    def get_initial(self):
        queryset = self.get_queryset()

        metadata = {}
        for document in queryset:
            document.add_as_recent_document_for_user(self.request.user)

            for item in document.metadata.all():
                value = item.value
                if item.metadata_type in metadata:
                    if value not in metadata[item.metadata_type]:
                        metadata[item.metadata_type].append(value)
                else:
                    metadata[item.metadata_type] = [value] if value else ''

        initial = []
        for key, value in metadata.items():
            initial.append(
                {
                    'document_type': queryset.first().document_type,
                    'metadata_type': key,
                    'value': ', '.join(value)
                }
            )
        return initial

    def object_action(self, form, instance):
        for form in form.forms:
            if form.cleaned_data['update']:
                metadata_type = get_object_or_404(
                    MetadataType, pk=form.cleaned_data['id']
                )
                try:
                    document_metadata = DocumentMetadata.objects.get(
                        document=instance, metadata_type=metadata_type
                    )
                    document_metadata.delete()
                    messages.success(
                        self.request,
                        _(
                            'Successfully remove metadata type "%(metadata_type)s" from document: %(document)s.'
                        ) % {
                            'metadata_type': metadata_type,
                            'document': instance
                        }
                    )
                except Exception as exception:
                    messages.error(
                        self.request,
                        _(
                            'Error removing metadata type "%(metadata_type)s" from document: %(document)s; %(exception)s'
                        ) % {
                            'metadata_type': metadata_type,
                            'document': instance,
                            'exception': ', '.join(exception.messages)
                        }
                    )


# Setup views
class MetadataTypeCreateView(SingleObjectCreateView):
    extra_context = {'title': _('Create metadata type')}
    form_class = MetadataTypeForm
    model = MetadataType
    post_action_redirect = reverse_lazy('metadata:setup_metadata_type_list')
    view_permission = permission_metadata_type_create


class MetadataTypeDeleteView(SingleObjectDeleteView):
    model = MetadataType
    post_action_redirect = reverse_lazy('metadata:setup_metadata_type_list')
    view_permission = permission_metadata_type_delete

    def get_extra_context(self):
        return {
            'delete_view': True,
            'object': self.get_object(),
            'title': _('Delete the metadata type: %s?') % self.get_object(),
        }


class MetadataTypeEditView(SingleObjectEditView):
    form_class = MetadataTypeForm
    model = MetadataType
    post_action_redirect = reverse_lazy('metadata:setup_metadata_type_list')
    view_permission = permission_metadata_type_edit

    def get_extra_context(self):
        return {
            'object': self.get_object(),
            'title': _('Edit metadata type: %s') % self.get_object(),
        }


class MetadataTypeListView(SingleObjectListView):
    view_permission = permission_metadata_type_view

    def get_queryset(self):
        return MetadataType.objects.all()

    def get_extra_context(self):
        return {
            'extra_columns': (
                {
                    'name': _('Internal name'),
                    'attribute': 'name',
                },
            ),
            'hide_link': True,
            'title': _('Metadata types'),
        }


class SetupDocumentTypeMetadataOptionalView(AssignRemoveView):
    decode_content_type = True
    view_permission = permission_document_type_edit
    left_list_title = _('Available metadata types')
    right_list_title = _('Metadata types assigned')

    def add(self, item):
        self.get_object().metadata.create(metadata_type=item, required=False)

    def get_object(self):
        return get_object_or_404(DocumentType, pk=self.kwargs['pk'])

    def left_list(self):
        return AssignRemoveView.generate_choices(
            set(MetadataType.objects.all()) - set(
                MetadataType.objects.filter(
                    id__in=self.get_object().metadata.values_list(
                        'metadata_type', flat=True
                    )
                )
            )
        )

    def right_list(self):
        return AssignRemoveView.generate_choices(
            self.get_object().metadata.filter(required=False)
        )

    def remove(self, item):
        item.delete()

    def get_extra_context(self):
        return {
            'object': self.get_object(),
            'title': _(
                'Optional metadata types for document type: %s'
            ) % self.get_object(),
        }


class SetupDocumentTypeMetadataRequiredView(SetupDocumentTypeMetadataOptionalView):
    def add(self, item):
        self.get_object().metadata.create(metadata_type=item, required=True)

    def right_list(self):
        return AssignRemoveView.generate_choices(
            self.get_object().metadata.filter(required=True)
        )

    def get_extra_context(self):
        return {
            'object': self.get_object(),
            'title': _(
                'Required metadata types for document type: %s'
            ) % self.get_object(),
        }
