from __future__ import absolute_import, unicode_literals

from django.conf import settings
from django.contrib import messages
from django.core.exceptions import PermissionDenied
from django.core.urlresolvers import reverse, reverse_lazy
from django.http import HttpResponseRedirect, Http404
from django.shortcuts import get_object_or_404, render_to_response
from django.template import RequestContext
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, SingleObjectCreateView, SingleObjectDeleteView,
    SingleObjectEditView, SingleObjectListView
)
from documents.models import Document, DocumentType
from documents.permissions import (
    permission_document_type_edit
)
from permissions import Permission

from .api import save_metadata_list
from .forms import (
    AddMetadataForm, MetadataFormSet, MetadataRemoveFormSet, 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
)


def metadata_edit(request, document_id=None, document_id_list=None):
    if document_id:
        document_id_list = unicode(document_id)

    documents = Document.objects.select_related('metadata').filter(
        pk__in=document_id_list.split(',')
    )

    try:
        Permission.check_permissions(
            request.user, (permission_metadata_document_edit,)
        )
    except PermissionDenied:
        documents = AccessControlList.objects.filter_by_access(
            permission_metadata_document_edit, request.user, documents
        )

    if not documents:
        if document_id:
            raise Http404
        else:
            messages.error(request, _('Must provide at least one document.'))
            return HttpResponseRedirect(
                request.META.get(
                    'HTTP_REFERER', reverse(settings.LOGIN_REDIRECT_URL)
                )
            )

    if len(set([document.document_type.pk for document in documents])) > 1:
        messages.error(request, _('Only select documents of the same type.'))
        return HttpResponseRedirect(
            request.META.get(
                'HTTP_REFERER', reverse(settings.LOGIN_REDIRECT_URL)
            )
        )

    if set(documents.values_list('metadata__value', flat=True)) == set([None]):
        message = ungettext(
            'The selected document doesn\'t have any metadata.',
            'The selected documents don\'t have any metadata.',
            len(documents)
        )
        messages.warning(request, message)
        return HttpResponseRedirect(
            request.META.get(
                'HTTP_REFERER', reverse(settings.LOGIN_REDIRECT_URL)
            )
        )

    post_action_redirect = reverse('documents:document_list_recent')

    next = request.POST.get(
        'next', request.GET.get(
            'next', request.META.get('HTTP_REFERER', post_action_redirect)
        )
    )

    metadata = {}
    initial = []

    for document in documents:
        document.add_as_recent_document_for_user(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 []

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

    formset = MetadataFormSet(initial=initial)
    if request.method == 'POST':
        formset = MetadataFormSet(data=request.POST, initial=initial)
        if formset.is_valid():
            for document in documents:

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

                if errors:
                    for error in errors:
                        if settings.DEBUG:
                            raise
                        else:
                            messages.error(
                                request, _(
                                    'Error editing metadata for document: '
                                    '%(document)s; %(exception)s.'
                                ) % {
                                    'document': document,
                                    'exception': ', '.join(exception.messages)
                                }
                            )
                else:
                    messages.success(
                        request,
                        _(
                            'Metadata for document %s edited successfully.'
                        ) % document
                    )

            return HttpResponseRedirect(next)

    context = {
        'form_display_mode_table': True,
        'form': formset,
        'next': next,
    }

    if len(documents) == 1:
        context['object'] = documents[0]

    context['title'] = ungettext(
        'Edit document metadata',
        'Edit documents metadata',
        len(documents)
    )

    return render_to_response(
        'appearance/generic_form.html', context,
        context_instance=RequestContext(request)
    )


def metadata_multiple_edit(request):
    return metadata_edit(
        request, document_id_list=request.GET.get('id_list', '')
    )


def metadata_add(request, document_id=None, document_id_list=None):
    if document_id:
        documents = [get_object_or_404(Document, pk=document_id)]
    elif document_id_list:
        documents = [
            get_object_or_404(
                Document.objects.select_related('document_type'),
                pk=document_id
            ) for document_id in document_id_list.split(',')
        ]
        if len(set([document.document_type.pk for document in documents])) > 1:
            messages.error(
                request, _('Only select documents of the same type.')
            )
            return HttpResponseRedirect(
                request.META.get(
                    'HTTP_REFERER', reverse(settings.LOGIN_REDIRECT_URL)
                )
            )

    try:
        Permission.check_permissions(
            request.user, (permission_metadata_document_add,)
        )
    except PermissionDenied:
        documents = AccessControlList.objects.filter_by_access(
            permission_metadata_document_add, request.user, documents
        )

    if not documents:
        messages.error(request, _('Must provide at least one document.'))
        return HttpResponseRedirect(
            request.META.get(
                'HTTP_REFERER', reverse(settings.LOGIN_REDIRECT_URL)
            )
        )

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

    post_action_redirect = reverse('documents:document_list_recent')

    next = request.POST.get(
        'next',
        request.GET.get(
            'next', request.META.get('HTTP_REFERER', post_action_redirect)
        )
    )

    if request.method == 'POST':
        form = AddMetadataForm(
            data=request.POST, document_type=document.document_type
        )
        if form.is_valid():
            metadata_type = form.cleaned_data['metadata_type']
            for document in documents:
                try:
                    document_metadata, created = DocumentMetadata.objects.get_or_create(
                        document=document,
                        metadata_type=metadata_type.metadata_type,
                        defaults={'value': ''}
                    )
                except Exception as exception:
                    if getattr(settings, 'DEBUG', False):
                        raise
                    else:
                        messages.error(
                            request,
                            _(
                                'Error adding metadata type '
                                '"%(metadata_type)s" to document: '
                                '%(document)s; %(exception)s'
                            ) % {
                                'metadata_type': metadata_type,
                                'document': document,
                                'exception': ', '.join(
                                    getattr(exception, 'messages', exception)
                                )
                            }
                        )
                else:
                    if created:
                        messages.success(
                            request,
                            _(
                                'Metadata type: %(metadata_type)s '
                                'successfully added to document %(document)s.'
                            ) % {
                                'metadata_type': metadata_type,
                                'document': document
                            }
                        )
                    else:
                        messages.warning(
                            request, _(
                                'Metadata type: %(metadata_type)s already '
                                'present in document %(document)s.'
                            ) % {
                                'metadata_type': metadata_type,
                                'document': document
                            }
                        )

            if len(documents) == 1:
                return HttpResponseRedirect('%s?%s' % (
                    reverse('metadata:metadata_edit', args=(document.pk,)),
                    urlencode({'next': next}))
                )
            elif len(documents) > 1:
                return HttpResponseRedirect('%s?%s' % (
                    reverse('metadata:metadata_multiple_edit'),
                    urlencode({'id_list': document_id_list, 'next': next}))
                )

    else:
        form = AddMetadataForm(document_type=document.document_type)

    context = {
        'form': form,
        'next': next,
    }

    if len(documents) == 1:
        context['object'] = documents[0]

    context['title'] = ungettext(
        'Add metadata types to document',
        'Add metadata types to documents',
        len(documents)
    )

    return render_to_response('appearance/generic_form.html', context,
                              context_instance=RequestContext(request))


def metadata_multiple_add(request):
    return metadata_add(
        request, document_id_list=request.GET.get('id_list', [])
    )


def metadata_remove(request, document_id=None, document_id_list=None):
    if document_id:
        document_id_list = unicode(document_id)

    documents = Document.objects.select_related('metadata').filter(
        pk__in=document_id_list.split(',')
    )

    try:
        Permission.check_permissions(
            request.user, (permission_metadata_document_remove,)
        )
    except PermissionDenied:
        documents = AccessControlList.objects.filter_by_access(
            permission_metadata_document_remove, request.user, documents
        )

    if not documents:
        if document_id:
            raise Http404
        else:
            messages.error(request, _('Must provide at least one document.'))
            return HttpResponseRedirect(
                request.META.get(
                    'HTTP_REFERER', reverse(settings.LOGIN_REDIRECT_URL)
                )
            )

    if len(set([document.document_type.pk for document in documents])) > 1:
        messages.error(request, _('Only select documents of the same type.'))
        return HttpResponseRedirect(
            request.META.get(
                'HTTP_REFERER', reverse(settings.LOGIN_REDIRECT_URL)
            )
        )

    if set(documents.values_list('metadata__value', flat=True)) == set([None]):
        message = ungettext(
            'The selected document doesn\'t have any metadata.',
            'The selected documents doesn\'t have any metadata.',
            len(documents)
        )
        messages.warning(request, message)
        return HttpResponseRedirect(
            request.META.get(
                'HTTP_REFERER', reverse(settings.LOGIN_REDIRECT_URL)
            )
        )

    post_action_redirect = reverse('documents:document_list_recent')

    next = request.POST.get(
        'next', request.GET.get(
            'next', request.META.get('HTTP_REFERER', post_action_redirect)
        )
    )

    metadata = {}
    for document in documents:
        document.add_as_recent_document_for_user(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({
            'metadata_type': key,
            'value': ', '.join(value)
        })

    formset = MetadataRemoveFormSet(initial=initial)
    if request.method == 'POST':
        formset = MetadataRemoveFormSet(request.POST)
        if formset.is_valid():
            for document in documents:

                for form in formset.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=document, metadata_type=metadata_type
                            )
                            document_metadata.delete()
                            messages.success(
                                request,
                                _(
                                    'Successfully remove metadata type "%(metadata_type)s" from document: %(document)s.'
                                ) % {
                                    'metadata_type': metadata_type,
                                    'document': document
                                }
                            )
                        except Exception as exception:
                            messages.error(
                                request,
                                _(
                                    'Error removing metadata type "%(metadata_type)s" from document: %(document)s; %(exception)s'
                                ) % {
                                    'metadata_type': metadata_type,
                                    'document': document,
                                    'exception': ', '.join(exception.messages)
                                }
                            )

            return HttpResponseRedirect(next)

    context = {
        'form_display_mode_table': True,
        'form': formset,
        'next': next,
    }

    if len(documents) == 1:
        context['object'] = documents[0]

    context['title'] = ungettext(
        'Remove metadata types from the document',
        'Remove metadata types from the documents',
        len(documents)
    )

    return render_to_response('appearance/generic_form.html', context,
                              context_instance=RequestContext(request))


def metadata_multiple_remove(request):
    return metadata_remove(
        request, document_id_list=request.GET.get('id_list', [])
    )


class DocumentMetadataListView(SingleObjectListView):
    def dispatch(self, request, *args, **kwargs):
        try:
            Permission.check_permissions(
                self.request.user, (permission_metadata_document_view,)
            )
        except PermissionDenied:
            AccessControlList.objects.check_access(
                permission_metadata_document_view, self.request.user,
                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()


# 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(),
        }
