# -*- coding: utf-8 -*-
u"""
Copyright 2012-2014 Olivier Cortès <oc@1flow.io>.

This file is part of the 1flow project.

1flow is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of
the License, or (at your option) any later version.

1flow is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public
License along with 1flow.  If not, see http://www.gnu.org/licenses/

"""

import csv
import logging

from constance.admin import ConstanceAdmin

from django.conf import settings
from django.http import HttpResponse
from django.template.defaultfilters import slugify
from django.contrib.admin.util import flatten_fieldsets
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.models import User as DjangoUser
from django.utils.translation import ugettext_lazy as _
from django.utils.datastructures import SortedDict


from django.contrib import admin
from mptt.admin import MPTTModelAdmin

import models

from sparks.django.utils import languages, truncate_field


LOGGER = logging.getLogger(__name__)

# Monkey-patch constance to create nice groups.
ConstanceAdmin.fieldsets = (

    (_(u'Worker control & maintenance mode'), {
        'classes': ('grp-collapse grp-open', ),
        'fields': (
            'FEED_GLOBAL_REFRESH_INTERVAL',
            'FEED_FETCH_DISABLED',
            'FEED_FETCH_RSSATOM_DISABLED',
            'FEED_FETCH_EMAIL_DISABLED',
            'FEED_FETCH_TWITTER_DISABLED',

            # TODO: activate these when we need them.
            # 'FEED_FETCH_FACEBOOK_DISABLED',
            # 'FEED_FETCH_GOOGLE_DISABLED',
        ),
    }),

    (_(u'1flow instance global settings'), {
        'classes': ('grp-collapse grp-open', ),
        'fields': (
            'SHARE_WITH_LOCAL_USERS',
            'POST_CREATE_TASKS_DELAY',
            'SEARCH_DB_EXTENDED_FIELDS',
            'SEARCH_DB_AVAILABLE_TO_USERS',
        ),
    }),

    (_(u'Interface modifications'), {
        'classes': ('grp-collapse grp-open', ),
        'fields': (
            'INTERFACE_SHOW_EXPORT_IDS',
            'INTERFACE_SHOW_SITECONFIG_ACCELS',
        ),
    }),

    (_(u'User announcements'), {
        'classes': ('grp-collapse grp-open', ),
        'fields': (
            'ANNOUNCEMENT_USER',
            'ANNOUNCEMENT_USER_PREFIX',
            'ANNOUNCEMENT_USER_PRIORITY',
            'ANNOUNCEMENT_USER_START',
            'ANNOUNCEMENT_USER_END',
        ),
    }),
    (_(u'Staff announcements'), {
        'classes': ('grp-collapse grp-closed', ),
        'fields': (
            'ANNOUNCEMENT_STAFF',
            'ANNOUNCEMENT_STAFF_PREFIX',
            'ANNOUNCEMENT_STAFF_PRIORITY',
            'ANNOUNCEMENT_STAFF_START',
            'ANNOUNCEMENT_STAFF_END',
        ),
    }),

    (_(u'Plain documents'), {
        'classes': ('grp-collapse grp-closed', ),
        'fields': (
            'DOCUMENTS_ARCHIVING_DISABLED',
        ),
    }),

    (_(u'Reading lists & subscriptions'), {
        'classes': ('grp-collapse grp-closed', ),
        'fields': (
            'READ_INFINITE_AUTOSCROLL_ENABLED',
            'READ_INFINITE_ITEMS_PER_FETCH',
            'READ_ARTICLE_MIN_LENGTH',
            'READ_AVERAGE_READING_SPEED',
            'SUBSCRIPTIONS_ITEMS_UNREAD_DAYS',
        ),
    }),

    (_(u'RSS feed refreshing'), {
        'classes': ('grp-collapse grp-closed', ),
        'fields': (
            # 'FEED_FETCH_DISABLED' is located in “worker control” section
            'FEED_FETCH_GHOST_ENABLED',
            'FEED_FETCH_MAX_ERRORS',
            'FEED_FETCH_DEFAULT_INTERVAL',
            'FEED_FETCH_MIN_INTERVAL',
            'FEED_FETCH_MAX_INTERVAL',
            'FEED_FETCH_RAISE_THRESHOLD',
            'FEED_FETCH_PARALLEL_LIMIT',
            'FEED_ADMIN_LIST_PER_PAGE',
            'FEED_ADMIN_MEANINGFUL_DELTA',
            'FEED_CLOSED_WARN_LIMIT',
        ),
    }),

    (_(u'Accounts (global/common)'), {
        'classes': ('grp-collapse grp-open', ),
        'fields': (
            'ACCOUNT_REFRESH_DISABLED',
            'ACCOUNT_REFRESH_PERIOD_DEFAULT',
        ),
    }),

    (_(u'Twitter accounts'), {
        'classes': ('grp-collapse grp-open', ),
        'fields': (
            'TWITTER_ACCOUNT_REFRESH_DISABLED',
            'TWITTER_ACCOUNT_REFRESH_PERIOD',
            'TWITTER_FEEDS_RELAUNCH_INTERVAL',

            'TWITTER_ACCOUNT_FETCH_OWNED_LISTS',
            'TWITTER_ACCOUNT_FETCH_SUBSCRIBED_LISTS',

            'TWITTER_FEEDS_BACKFILL_ENABLED_DEFAULT',
            'TWITTER_BACKFILL_ALLOWED_REWIND_RANGE',

            'TWITTER_DEFAULT_CACHE_EXPIRY',
            'TWITTER_LISTS_CACHE_EXPIRY',
        ),
    }),
    (_(u'e-Mail accounts & IMAP'), {
        'classes': ('grp-collapse grp-open', ),
        'fields': (
            'MAIL_ACCOUNT_REFRESH_DISABLED',
            'MAIL_ACCOUNT_REFRESH_PERIOD',
            'MAIL_RULES_GROUPS_MAX',
            'MAIL_IMAP_FETCH_MAX',
            'MAIL_IMAP_DECODE_FALLBACK',
            'MAIL_IMAP_CACHE_MESSAGES',
            'MAIL_IMAP_CACHE_EXPIRY',
            'MAIL_IMAP_CACHE_IDS_EXPIRY',
        ),
    }),

    (_(u'Article fetching & parsing'), {
        'classes': ('grp-collapse grp-closed', ),
        'fields': (
            'ARTICLE_CRAWL_CONTENT_ENABLED',
            'ARTICLE_FETCHING_DEBUG',
            'ARTICLE_REPROCESSING_DISABLED',
            'ARTICLE_ABSOLUTIZING_DISABLED',
            'ARTICLE_FETCHING_DISABLED',
            'ARTICLE_FETCHING_TEXT_DISABLED',
            'ARTICLE_FETCHING_VIDEO_DISABLED',
            'ARTICLE_FETCHING_IMAGE_DISABLED',
            'ARTICLE_MARKDOWN_DISABLED',
            'ARTICLE_ARCHIVE_BATCH_SIZE',
            'ARTICLE_ARCHIVE_OLDER_THAN',
            'EXCERPT_PARAGRAPH_MIN_LENGTH',
        ),
    }),

    (_(u'Logins & registration'), {
        'classes': ('grp-collapse grp-closed', ),
        'fields': (
            'SOCIAL_LOGIN_ENABLED',
            'SOCIAL_REGISTRATION_ENABLED',
            'LOCAL_LOGIN_ENABLED',
            'LOCAL_REGISTRATION_ENABLED',
        ),
    }),

    (_(u'Site theme & CDNs'), {
        'classes': ('grp-collapse grp-closed', ),
        'fields': (
            'WEB_CDNS_ENABLED',
            'WEB_BOOTSWATCH_THEME',
            'WEB_THEME_NAVBAR_INVERSE',
        ),
    }),


    (_(u'User support'), {
        'classes': ('grp-collapse grp-closed', ),
        'fields': (
            'SUPPORT_EMAIL_ADDRESS',
            'IRC_SUPPORT_CHANNEL',
            'IRC_SUPPORT_SERVER',
        ),
    }),

    (_(u'Check tasks'), {
        'classes': ('grp-collapse grp-closed', ),
        'fields': (
            'CHECK_DATABASE_MIGRATION_DISABLED',
            'CHECK_DATABASE_MIGRATION_DEFINIVE_RUN',
            'CHECK_DATABASE_MIGRATION_VACUUM_ENABLED',
            'CHECK_SUBSCRIPTIONS_DISABLED',
            'CHECK_SUBSCRIPTIONS_LIMIT',
            'CHECK_DUPLICATES_DISABLED',
            'CHECK_DUPLICATES_LIMIT',
            'CHECK_DUPLICATES_PURGE_AFTER_WEEKS',
            'CHECK_ORPHANED_DISABLED',
            'CHECK_ORPHANED_LIMIT',
            'CHECK_READS_DISABLED',
            'CHECK_READS_LIMIT',
            'CHECK_USERS_DISABLED',
            'CHECK_USERS_LIMIT',
        ),
    }),
    (_(u'Cleaning & archiving tasks'), {
        'classes': ('grp-collapse grp-closed', ),
        'fields': (
            'CLEANUP_FEEDS_OLD_NON_HTML_DELTA',
            'CLEANUP_WRITING_COMPRESS_VERSIONS',
            'CLEANUP_WRITING_KEEP_MAX_VERSIONS',
            'CLEANUP_PURGE_HTML_READ_DELTA',
            'CLEANUP_PURGE_VERSIONS_UNREAD_DELTA',
            'CLEANUP_ARCHIVE_HTML_TO_ORIGINAL_DATA',
        ),
    }),

    (_(u'Dangerous or debug'), {
        'classes': ('grp-collapse grp-closed', ),
        'fields': (
            'STAFF_HAS_FULL_ACCESS',
            'PROCESSING_FINE_GRAINED_TRANSACTIONS',
            'PROCESSING_ADBLOCK_MAXIMUM_MEMORY',
        ),
    }),

    (_(u'Google Reader (obsolete)'), {
        'classes': ('grp-collapse grp-closed', ),
        'fields': (
            'GR_MAX_ARTICLES',
            'GR_MAX_FEEDS',
            'GR_LOAD_LIMIT',
            'GR_WAVE_LIMIT',
            'GR_MAX_RETRIES',
            'GR_END_DATE',
            'GR_STORAGE_LIMIT',
        ),
    }),
)


# •••••••••••••••••••••••••••••••••••••••••••••••• Helpers and abstract classes


class NearlyReadOnlyAdmin(admin.ModelAdmin):

    """ borrowed from https://code.djangoproject.com/ticket/17295 .

    with enhancements from http://stackoverflow.com/a/12923739/654755
    """

    def get_readonly_fields(self, request, obj=None):
        if request.user.is_superuser:
            return self.readonly_fields

        if self.declared_fieldsets:
            fields = flatten_fieldsets(self.declared_fieldsets)
        else:
            fields = [field.name for field in self.model._meta.fields]
        return fields

    def has_add_permission(self, request):
        return request.user.is_superuser

    def has_delete_permission(self, request, obj=None):
        return request.user.is_superuser


class CSVAdminMixin(admin.ModelAdmin):
    """
    Adds a CSV export action to an admin view.
    Modified/updated version of http://djangosnippets.org/snippets/2908/

    http://stackoverflow.com/a/16198394/654755 could be a future cool thing
    to implement, to avoid selecting everyone before exporting.
    """

    # This is the maximum number of records that will be written.
    # Exporting massive numbers of records should be done asynchronously.
    # Excel is capped @ 65535 + 1 header line.
    csv_record_limit = 65535
    extra_csv_fields = ()

    def get_actions(self, request):

        try:
            self.actions.append('csv_export')

        except AttributeError:
            self.actions = ('csv_export', )

        actions = super(CSVAdminMixin, self).get_actions(request)

        return actions or SortedDict()

    def get_extra_csv_fields(self, request):
        return self.extra_csv_fields

    def csv_export(self, request, queryset, *args, **kwargs):
        response = HttpResponse(mimetype='text/csv')

        response['Content-Disposition'] = "attachment; filename={}.csv".format(
            slugify(self.model.__name__)
        )

        headers = list(self.list_display) + list(
            self.get_extra_csv_fields(request)
        )

        # BOM (Excel needs it to open UTF-8 file properly)
        response.write(u'\ufeff'.encode('utf8'))
        writer = csv.DictWriter(response, headers)

        # Write header.
        header_data = {}
        for name in headers:
            if hasattr(self, name) \
                    and hasattr(getattr(self, name), 'short_description'):
                header_data[name] = getattr(
                    getattr(self, name), 'short_description')

            else:
                field = self.model._meta.get_field_by_name(name)

                if field and field[0].verbose_name:
                    header_data[name] = field[0].verbose_name

                else:
                    header_data[name] = name

            header_data[name] = header_data[name].encode('utf-8', 'ignore')

        writer.writerow(header_data)

        # Write records.
        for row in queryset[:self.csv_record_limit]:
            data = {}
            for name in headers:
                if hasattr(row, name):
                    data[name] = getattr(row, name)
                elif hasattr(self, name):
                    data[name] = getattr(self, name)(row)
                else:
                    raise Exception('Unknown field: %s' % (name,))

                if callable(data[name]):
                    data[name] = data[name]()

                if isinstance(data[name], unicode):
                    data[name] = data[name].encode('utf-8', 'ignore')
                else:
                    data[name] = unicode(data[name]).encode('utf-8', 'ignore')

            writer.writerow(data)

        return response

    csv_export.short_description = _(
        u'Export selected %(verbose_name_plural)s to CSV'
    )


# ••••••••••••••••••••••••••••••••••••••••••••••• Base Django App admin classes


class OneFlowUserAdmin(UserAdmin, CSVAdminMixin):

    list_display = ('id', 'username', 'email', 'full_name_display',
                    'date_joined', 'last_login',
                    'email_announcements',
                    'is_active', 'is_staff', 'is_superuser',
                    'groups_display', )
    list_display_links = ('username', 'email', 'full_name_display', )
    list_filter = ('email_announcements',
                   'is_active', 'is_staff', 'is_superuser', )
    ordering = ('-date_joined',)
    date_hierarchy = 'date_joined'
    search_fields = ('email', 'first_name', 'last_name', )
    change_list_template = "admin/change_list_filter_sidebar.html"
    change_list_filter_template = "admin/filter_listing.html"
    # Don't display the UserProfile inline, this will conflict with the
    # post_save() signal in profiles.models. There is a race condition…
    #inlines = [] if UserProfile is None else [UserProfileInline, ]

    def groups_display(self, obj):
        return u', '.join([g.name for g in obj.groups.all()]
                          ) if obj.groups.count() else u'—'

    groups_display.short_description = _(u'Groups')

    def full_name_display(self, obj):
        return obj.get_full_name()

    full_name_display.short_description = _(u'Full name')

admin.site.register(models.User, OneFlowUserAdmin)

try:
    admin.site.unregister(DjangoUser)
except:
    pass


class ConfigurationAdmin(MPTTModelAdmin):

    """ Configuration admin class. """

    mptt_level_indent = 20
    mptt_indent_field = "name"

    list_display = ('parent', 'name', 'is_active', 'value', 'notes', )
    list_display_links = ('name', 'value', )
    list_filter = ('is_active', )
    # ordering = ('name', 'parent', )
    # date_hierarchy = 'date_joined'
    search_fields = ('name', 'value', 'notes', )
    change_list_template = "admin/change_list_filter_sidebar.html"
    change_list_filter_template = "admin/filter_listing.html"

    # Don't display the UserProfile inline, this will conflict with the
    # post_save() signal in profiles.models. There is a race condition…
    # inlines = [] if UserProfile is None else [UserProfileInline, ]

admin.site.register(models.Configuration, ConfigurationAdmin)


if settings.FULL_ADMIN:
    subject_fields_names = tuple(('subject_' + code)
                                 for code, lang in languages)
    subject_fields_displays = tuple((field + '_display')
                                    for field in subject_fields_names)

    class EmailContentAdmin(admin.ModelAdmin):
        list_display  = ('name', ) + subject_fields_displays
        search_fields = ('name', ) + subject_fields_names
        ordering      = ('name', )
        save_as       = True

    for attr, attr_name in zip(subject_fields_names,
                               subject_fields_displays):
        setattr(EmailContentAdmin, attr_name,
                truncate_field(models.EmailContent, attr))

    admin.site.register(models.EmailContent, EmailContentAdmin)
