# -*- coding: utf-8 -*-
# Future imports
from __future__ import unicode_literals

# Standard libs
import copy
import itertools

# Django imports
from django.conf.urls import url
from django.contrib import admin
from django.contrib import messages
from django.contrib.admin.models import CHANGE
from django.contrib.admin.models import LogEntry
from django.contrib.contenttypes.models import ContentType
from django.templatetags.static import static

try:
    # Django imports
    from django.urls import reverse
except ImportError:
    from django.core.urlresolvers import reverse

# Django imports
from django.http import HttpResponseForbidden
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404
from django.utils import formats
from django.utils.html import format_html
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ungettext_lazy

# Third Party
import six


def get_fieldset_by_field(name, fieldsets):
    """
    Return fieldset containing a field given by name
    """
    for fieldset in fieldsets:
        options = fieldset[1]
        if name in options.get('fields', {}):
            return fieldset
    return None


def remove_field_from_fieldsets(name, fieldsets, remove_empty_fieldset=True):
    """
    Remove a field from all fieldset in fieldsets.
    """
    while True:
        fieldset = get_fieldset_by_field(name, fieldsets)
        if fieldset:
            fieldset[1]['fields'].remove(name)
            if remove_empty_fieldset and len(fieldset[1]['fields']) == 0:
                fieldsets.remove(fieldset)
        else:
            break


def add_fields_to_fieldset(
    fields,
    fieldsets,
    after_field=None,
    same_fieldset_as=None,
    default_fieldset_name=None,
    default_fieldset_classes=[],
    default_fieldset_position=None,
    replace_existing_field=False,
    remove_empty_fieldset=False,
):
    """
    Add fields in a fieldsets attribute

    Try to find a fieldset containing same_fieldset_as value in fields.  If not
    found a new fieldset is created with name `default_fieldset_name`, position
    `default_fieldset_position` and classes `default_fieldset_classes`.

    Fields are inserted just after after_field. If not found, they are inserted
    at the end.

    If replace_existing_field is True, existing fields in fieldsets with same
    name are removed.

    """
    if same_fieldset_as is None and after_field:
        same_fieldset_as = after_field

    # Find fieldset
    fieldset = get_fieldset_by_field(same_fieldset_as, fieldsets)

    if fieldset is None:
        fieldset = (default_fieldset_name, {
            'fields': [],
            'classes': default_fieldset_classes,
        })
        allready_inserted = False
    else:
        allready_inserted = True

    # Insert fields
    index = None
    if after_field:
        index = fieldset[1]['fields'].index(after_field) + 1

    index = index or len(fieldset[1]['fields'])
    for field in fields:
        if replace_existing_field:
            remove_field_from_fieldsets(
                field, fieldsets,
                remove_empty_fieldset=remove_empty_fieldset)
        elif get_fieldset_by_field(field, fieldsets) is not None:
            continue

        fieldset[1]['fields'].insert(index, field)
        index += 1

    # Insert fieldset if needed.
    if not allready_inserted and len(fieldset[1]['fields']) > 0:
        if default_fieldset_position is not None:
            fieldsets.insert(default_fieldset_position, fieldset)
        else:
            fieldsets.append(fieldset)


class NewsboxBaseAdmin(admin.ModelAdmin):

    list_filter = ('newsbox_publication_start_date', 'newsbox_status', )
    list_display = ('get_newsbox_title',)
    list_display_links = ('get_newsbox_title',)
    fieldsets = [
        (None, {
            'fields': ['newsbox_title', 'newsbox_slug', 'newsbox_date', 'newsbox_status', ], }),
        (_('Content'), {
            'fields': ['newsbox_summary', 'newsbox_body'], }), ]
    actions = ['publish', 'unpublish']
    save_as = True

    def get_list_display(self, request):
        if getattr(self.__class__, 'list_display', None) == admin.ModelAdmin.list_display:
            self.list_display = ['get_newsbox_title', 'newsbox_date_short', 'changestatus_link']
        return super(NewsboxBaseAdmin, self).get_list_display(request)

    def get_prepopulated_fields(self, request, object=None):
        pfields = super(NewsboxBaseAdmin, self).get_prepopulated_fields(request, object) or {}
        pfields.update({'newsbox_slug': ('newsbox_title',)})
        return pfields

    def changestatus_link(self, obj):
        if obj.newsbox_status == obj.STATUS_PUBLISHED:
            title = _('Unpublish this %s') % six.text_type(
                obj._meta.verbose_name)
            icon_url = static('admin/img/icon-yes.gif')
        else:
            title = _('Publish this %s') % six.text_type(
                obj._meta.verbose_name)
            icon_url = static('admin/img/icon-no.gif')
        url_name = '%s_%s_change_status' % (self.model._meta.model_name,
                                            self.model._meta.app_label)
        return format_html(
            '<a href="{}" title="{}"><img src="{}" alt="{}" /></a>',
            reverse('admin:%s' % url_name, args=[obj.pk, ]),
            title,
            icon_url,
            obj.get_newsbox_status_display()
        )

    changestatus_link.admin_order_field = 'newsbox_status'
    changestatus_link.short_description = _('published')

    def newsbox_date_short(self, obj):
        return format_html('<span title="{}">{}</span>',
            formats.date_format(obj.newsbox_publication_start_date, 'DATETIME_FORMAT'),
            formats.date_format(obj.newsbox_publication_start_date, 'SHORT_DATE_FORMAT')
        )
    newsbox_date_short.short_description = _('pub. date')

    def get_fieldsets(self, request, obj=None):
        """
        Prevent anoying modification of fieldsets class attribute.
        """
        fieldsets = super(NewsboxBaseAdmin, self).get_fieldsets(request, obj)
        return copy.deepcopy(fieldsets)

    def get_newsbox_title(self, obj):
        return obj.newsbox_title
    get_newsbox_title.short_description = _('Title')

    def get_urls(self):
        url_prefix = '%s_%s_' % (self.model._meta.model_name, self.model._meta.app_label)
        return [
            url(r'^([0-9]+)/change-status/$',
                self.admin_site.admin_view(self.change_status),
                name='%schange_status' % url_prefix,),
            url(r'^([0-9]+)/publish/$',
                self.admin_site.admin_view(self.publish_one),
                name='%spublish_one' % url_prefix,),
            url(r'^([0-9]+)/unpublish/$',
                self.admin_site.admin_view(self.unpublish_one),
                name='%sunpublish_one' % url_prefix,)
        ] + super(NewsboxBaseAdmin, self).get_urls()

    def change_status(self, request, news_id):
        """
        Switch the status of a news
        """

        obj = get_object_or_404(self.model, pk=news_id)
        if not obj.has_publish_permission(request.user):
            return HttpResponseForbidden(
                _('You do not have permission to publish this %s')
                % (six.text_type(self.model._meta.verbose_name),))

        old_status = obj.newsbox_status
        if obj.newsbox_status == obj.STATUS_PUBLISHED:
            obj.newsbox_status = obj.STATUS_ARCHIVED
        else:
            obj.newsbox_status = obj.STATUS_PUBLISHED
        obj.save()
        log_msg = _('publication status changed from %(old)s to %(new)s') % {
            'old': old_status,
            'new': obj.newsbox_status,
        }
        if obj.newsbox_status == obj.STATUS_PUBLISHED:
            msg = _('The %(objtype)s "%(objtitle)s" has been successfully published')
        else:
            msg = _('The %(objtype)s "%(objtitle)s" has been successfully unpublished')
        messages.info(request, msg % {'objtype': six.text_type(self.model._meta.verbose_name),
                                      'objtitle': six.text_type(obj)})
        LogEntry.objects.log_action(
            user_id=request.user.id,
            content_type_id=ContentType.objects.get_for_model(self.model).pk,
            object_id=news_id,
            object_repr=six.text_type(obj),
            action_flag=CHANGE,
            change_message=log_msg,
        )
        return HttpResponseRedirect(request.GET.get('next', '../../'))

    def publish_one(self, request, news_id):
        obj = get_object_or_404(self.model, pk=news_id)
        if not obj.has_publish_permission(request.user):
            return HttpResponseForbidden(
                _('You do not have permission to publish this %s')
                % (six.text_type(self.model._meta.verbose_name),))
        self.publish(request, self.model.objects.filter(pk=news_id))
        return HttpResponseRedirect(request.GET.get('next', '../../'))

    def unpublish_one(self, request, news_id):
        obj = get_object_or_404(self.model, pk=news_id)
        if not obj.has_publish_permission(request.user):
            return HttpResponseForbidden(
                _('You do not have permission to publish this %s')
                % (six.text_type(self.model._meta.verbose_name),))
        self.unpublish(request, self.model.objects.filter(pk=news_id))
        return HttpResponseRedirect(request.GET.get('next', '../../'))

    def publish(self, request, queryset):
        """
        Marks selected news items as published
        """
        ok = 0
        content_type_id = ContentType.objects.get_for_model(self.model).pk
        for obj in queryset:
            if obj.is_published:
                continue

            if not obj.has_publish_permission(request.user):
                messages.error(
                    request,
                    _(
                        'You do not have permission to publish the '
                        '%(objtype)s "%(objtitle)s"'
                    ) % {
                        'objtype': six.text_type(self.model._meta.verbose_name),
                        'objtitle': six.text_type(obj)
                    }
                )
                continue
            obj.publish()
            LogEntry.objects.log_action(user_id=request.user.id, content_type_id=content_type_id,
                                        object_id=obj.pk, object_repr=six.text_type(obj),
                                        action_flag=CHANGE, change_message=_('Published'),)
            ok += 1
            last_obj_ok = obj

        if ok == 1:
            messages.success(request, _('The %(objtype)s "%(objtitle)s" has been published') % {
                'objtype': self.model._meta.verbose_name,
                'objtitle': last_obj_ok, })
        else:
            messages.success(request, ungettext_lazy(
                '%(nb)d %(objtype)s has been published',
                '%(nb)d %(objtype)s have been published',
                ok) % {'nb': ok,
                       'objtype': self.model._meta.verbose_name_plural,})
    publish.short_description = _('Publish selected %(verbose_name_plural)s')

    def unpublish(self, request, queryset):
        """
        Marks selected news items as unpublished
        """
        ok = 0
        content_type_id = ContentType.objects.get_for_model(self.model).pk
        for obj in queryset:
            if not obj.is_published:
                continue

            if not obj.has_publish_permission(request.user):
                messages.error(
                    request,
                    _(
                        'You do not have permission to unpublish the '
                        '%(objtype)s "%(objtitle)s"'
                    ) % {
                        'objtype': six.text_type(self.model._meta.verbose_name),
                        'objtitle': six.text_type(obj)
                    }
                )
                continue
            obj.unpublish()
            LogEntry.objects.log_action(user_id=request.user.id, content_type_id=content_type_id,
                                        object_id=obj.pk, object_repr=six.text_type(obj),
                                        action_flag=CHANGE, change_message=_('Unpublished'),)
            ok += 1
            last_obj_ok = obj

        if ok == 1:
            messages.success(request, _('The %(objtype)s "%(objtitle)s" has been unpublished') % {
                'objtype': self.model._meta.verbose_name,
                'objtitle': last_obj_ok, })
        else:
            messages.success(request, ungettext_lazy(
                '%(nb)d %(objtype)s has been unpublished',
                '%(nb)d %(objtype)s have been unpublished',
                ok) % {'nb': ok,
                       'objtype': self.model._meta.verbose_name_plural})
    unpublish.short_description = _('Archive selected %(verbose_name_plural)s')

    def ensure_slug_uniq_queryset(self, request, obj, slug):
        y, m, d = (obj.newsbox_publication_start_date.year,
                   obj.newsbox_publication_start_date.month,
                   obj.newsbox_publication_start_date.day)
        queryset = type(obj).objects.filter(newsbox_publication_start_date__year=y,
                                            newsbox_publication_start_date__month=m,
                                            newsbox_publication_start_date__day=d,
                                            newsbox_slug=slug)
        if obj.pk:
            queryset = queryset.exclude(pk=obj.pk)
        return queryset

    def ensure_slug_uniq(self, request, obj):
        max_length = 50

        slug = obj.newsbox_slug
        for x in itertools.count(1):
            if not self.ensure_slug_uniq_queryset(request, obj, slug).count():
                break
            suffix = "-%d" % x
            slug = "%s%s" % (obj.newsbox_slug[:max_length-len(suffix)], suffix)

        obj.newsbox_slug = slug

    def save_model(self, request, obj, form, change):
        self.ensure_slug_uniq(request, obj)
        return super(NewsboxBaseAdmin, self).save_model(request, obj, form, change)


class NewsboxAdmin(NewsboxBaseAdmin):
    pass


class NewsboxExpiredAdmin(NewsboxBaseAdmin):

    def newsbox_publication_dates_short(self, obj):
        output = []
        title = []
        css_class = []
        title.append(formats.date_format(obj.newsbox_publication_start_date,
                                         'DATETIME_FORMAT'))
        output.append('<span class="from">%s</span>' %
                      formats.date_format(obj.newsbox_publication_start_date,
                                          'SHORT_DATE_FORMAT'))
        css_class.append('from')
        if not obj.newsbox_publication_end_date:
            title.append(_('never'))
        else:
            title.append(
                formats.date_format(obj.newsbox_publication_end_date, 'DATETIME_FORMAT'))
            output.append('<span class="to">%s</span>' %
                          formats.date_format(obj.newsbox_publication_end_date,
                                              'SHORT_DATE_FORMAT'))
            css_class.append('to')
        css_class = '_'.join(css_class)
        title = _('from %(from)s to %(to)s') % {'from': title[0], 'to': title[1]}
        output = ('<span> %s </span>' % _('to')).join(output)
        return format_html('<abbr class="newsbox_pub_dates %s" title="%s">%s</abbr>' % (
            css_class, title, output))
    newsbox_publication_dates_short.short_description = _('publication')

    def get_fieldsets(self, request, obj=None):
        fieldsets = super(NewsboxExpiredAdmin, self).get_fieldsets(request, obj)
        for fieldset in fieldsets:
            if 'newsbox_publication_end_date' in fieldset[1]['fields']:
                return fieldsets
        remove_field_from_fieldsets('newsbox_status', fieldsets)
        add_fields_to_fieldset(
            ['newsbox_status',
             'newsbox_publication_start_date',
             'newsbox_publication_end_date', ],
            fieldsets,
            default_fieldset_name=_('Publication settings'),
            default_fieldset_position=1)

        return fieldsets

    def get_list_display(self, request):
        list_display = list(super(NewsboxExpiredAdmin, self).get_list_display(request))
        if 'newsbox_publication_dates_short' not in list_display:
            try:
                index = list_display.index('newsbox_date_short')
                list_display[index] = 'newsbox_publication_dates_short'
            except ValueError:
                list_display.append('newsbox_publication_dates_short')
        return list_display


class NewsboxSEOAdmin(NewsboxBaseAdmin):

    def get_fieldsets(self, request, obj=None):
        fieldsets = super(NewsboxSEOAdmin, self).get_fieldsets(request, obj)

        add_fields_to_fieldset(
            ['newsbox_indexed',
             'newsbox_meta_description',
             'newsbox_meta_keywords', ],
            fieldsets,
            default_fieldset_name=_('SEO Settings'),
            default_fieldset_classes=('collapse',),
        )

        return fieldsets

    def get_list_display(self, request):
        list_display = super(NewsboxSEOAdmin, self).get_list_display(request)
        return list_display