from __future__ import unicode_literals

import logging
import os

from django.conf import settings
from django.conf.urls import include, url
from django.contrib import messages
from django.core.files.storage import default_storage
from django.db.models import Q
from django.http import Http404, HttpResponseRedirect
from django.shortcuts import get_object_or_404
from django.urls import resolve, reverse
from django.utils.encoding import python_2_unicode_compatible
from django.utils.functional import cached_property
from django.utils.translation import ugettext_lazy as _
from first import first
from touchtechnology.admin.base import AdminComponent
from touchtechnology.admin.mixins import AdminUrlMixin
from touchtechnology.admin.sites import site
from touchtechnology.common.decorators import require_POST_m, staff_login_required_m
from touchtechnology.common.models import SitemapNode
from touchtechnology.content.forms import (
    FileUploadForm, NewFolderForm, NewPlaceholderSitemapNodeForm, NewSitemapNodeForm, Page,
    PageContentFormset, PageForm, PlaceholderConfigurationBase, PlaceholderContentFormset,
    PlaceholderSitemapNodeForm, RedirectEditForm, SitemapNodeForm,
)
from touchtechnology.content.models import Chunk, PageContent, Placeholder, Redirect

logger = logging.getLogger(__name__)


class ContentAdminComponent(AdminComponent):
    verbose_name = _('Content')

    filter = Q(content_type__app_label='content') | \
        Q(content_type__isnull=True)

    def __init__(self, app, name="content", app_name="content"):
        super(ContentAdminComponent, self).__init__(app, name, app_name)

    def get_urls(self):
        folder_patterns = ([
            url(r'^add/$', self.edit_folder, name='add'),
            url(r'^(?P<pk>\d+)/$', self.edit_folder, name='edit'),
            url(r'^(?P<pk>\d+)/delete/$',
                self.delete_folder, name='delete'),
        ], self.app_name)

        page_patterns = ([
            url(r'^add/$',
                self.edit_page, name='add'),
            url(r'^(?P<pk>\d+)/$',
                self.edit_page, name='edit'),
            url(r'^(?P<pk>\d+)/delete/$',
                self.delete_page, name='delete'),
            url(r'^(?P<pk>\d+)/permission/$',
                self.perms_sitemapnode, name='perms'),
        ], self.app_name)

        redirect_patterns = ([
            url(r'^$', self.list_redirects, name='list'),
            url(r'^create/$', self.edit_redirect, name='add'),
            url(r'^(?P<pk>\d+)/$', self.edit_redirect, name='edit'),
            url(r'^(?P<pk>\d+)/delete/$',
                self.delete_redirect, name='delete'),
            url(r'^(?P<pk>\d+)/permission/$',
                self.perms_redirect, name='perms'),
        ], self.app_name)

        placeholder_patterns = ([
            url(r'^$', self.list_placeholders, name='list'),
            url(r'^create/$', self.edit_placeholder_x, name='add'),
            url(r'^(?P<pk>\d+)/$', self.edit_placeholder_x, name='edit'),
            url(r'^(?P<pk>\d+)/delete/$',
                self.delete_placeholder_x, name='delete'),
            # url(r'^(?P<pk>\d+)/permission/$',
            #     self.perms_placeholder, name='delete'),
        ], self.app_name)

        application_patterns = ([
            url(r'^add/$', self.edit_application_node, name='add'),
            url(r'^(?P<pk>\d+)/$',
                self.edit_application_node, name='edit'),
            url(r'^(?P<pk>\d+)/delete/$',
                self.delete_application_node, name='delete'),
            url(r'^(?P<pk>\d+)/permission/$',
                self.perms_sitemapnode, name='perms'),
        ], self.app_name)

        chunk_patterns = ([
            url(r'^$', self.list_chunks, name='list'),
            url(r'^create/$', self.edit_chunk, name='add'),
            url(r'^(?P<pk>\d+)/$', self.edit_chunk, name='edit'),
            url(r'^(?P<pk>\d+)/delete/$',
                self.delete_chunk, name='delete'),
            url(r'^(?P<pk>\d+)/permission/$',
                self.perms_chunk, name='perms'),
        ], self.app_name)

        files_patterns = ([
            url(r'^(?P<path>.+/)?(?P<filename>[^/]+?)/rm/$',
                self.rm, name='delete'),
            url(r'^(?P<path>.+/)rmdir/$', self.rm, name='delete'),
            url(r'^$', self.ls, name='list'),
            url(r'^(?P<path>.+)$', self.ls, name='edit'),
        ], self.app_name)

        urlpatterns = [
            url(r'^$', self.list_pages, name='index'),
            url(r'^folder/', include(folder_patterns, namespace='folder')),
            url(r'^page/', include(page_patterns, namespace='page')),
            url(r'^redirect/', include(redirect_patterns, namespace='redirect')),
            url(r'^placeholder/', include(placeholder_patterns, namespace='real_placeholder')),
            url(r'^application/', include(application_patterns, namespace='placeholder')),
            url(r'^chunk/', include(chunk_patterns, namespace='chunk')),
            url(r'^files/', include(files_patterns, namespace='media')),
        ]
        return urlpatterns

    def dropdowns(self):
        dl = (
            (_("Sitemap"), self.reverse('index'), 'sitemap'),
            (_("Media"), self.reverse('media:list'), 'folder-open'),
            (_("Chunks"), self.reverse('chunk:list'), 'pencil-square-o'),
            (_("Redirects"), self.reverse('redirect:list'), 'reply'),
            (_("Applications"),
             self.reverse('real_placeholder:list'), 'puzzle-piece'),
        )
        return dl

    # Generic views - trial before moving onto Application

    def generic_parent_edit_related(self, request, pk,
                                    parent_model,
                                    child_model,
                                    child_related_model,
                                    parent_form_class=None,
                                    child_form_class=None,
                                    child_formset_class=None,
                                    post_save_redirect=None,
                                    *args, **kwargs):
        if pk is not None:
            instance = get_object_or_404(parent_model, pk=pk)
        else:
            instance = None
        if post_save_redirect is None:
            post_save_redirect = self.redirect(self.reverse('index'))
        return self.generic_edit_related(
            request, child_model, child_related_model,
            instance=instance,
            form_class=child_form_class,
            form_kwargs={
                'parent_form_class': parent_form_class,
                'parent_form_kwargs': {'user': request.user},
            },
            formset_class=child_formset_class,
            post_save_redirect=post_save_redirect,
            extra_context=kwargs)

    # Shared perms view

    def perms_sitemapnode(self, request, pk, **extra_context):
        return self.generic_permissions(request, SitemapNode, pk=pk,
                                        **extra_context)

    # Folder views -- folders are just SitemapNode's with no links

    @staff_login_required_m
    def edit_folder(self, request, pk=None, *args, **kwargs):
        form_class = NewSitemapNodeForm if pk is None else SitemapNodeForm
        return self.generic_edit(request, SitemapNode, pk=pk,
                                 form_class=form_class,
                                 related=(),
                                 permission_required=True,
                                 extra_context=kwargs)

    @staff_login_required_m
    def delete_folder(self, request, pk, *args, **kwargs):
        node = get_object_or_404(SitemapNode, pk=pk)
        if not node.is_leaf_node():
            raise Http404
        return self.generic_delete(
            request, SitemapNode, pk=pk, extra_context=kwargs)

    # Page views

    @staff_login_required_m
    def list_pages(self, request, **extra_context):
        try:
            root = SitemapNode.objects.get(pk=request.GET['r'])
            descendants = root.get_descendants()
            nodes = descendants.filter(self.filter) \
                               .filter(level=root.level + 1)
        except KeyError:
            nodes = SitemapNode.objects.filter(self.filter) \
                                       .filter(level=0)
        return self.generic_list(request, nodes,
                                 paginate_by=0,
                                 permission_required=True,
                                 extra_context=extra_context)

    @staff_login_required_m
    def edit_page(self, request, pk=None, **extra_context):
        post_save_redirect = None
        if pk is not None:
            instance = get_object_or_404(SitemapNode, pk=pk)
            if instance.parent_id:
                post_save_redirect = self.redirect(
                    self.reverse('index') + "?r=%d" % instance.parent_id)
        return self.generic_parent_edit_related(
            request, pk, parent_model=SitemapNode, child_model=Page,
            child_related_model=PageContent,
            parent_form_class=SitemapNodeForm, child_form_class=PageForm,
            child_formset_class=PageContentFormset,
            post_save_redirect=post_save_redirect,
            **extra_context)

    @staff_login_required_m
    def delete_page(self, request, pk, *args, **kwargs):
        node = get_object_or_404(SitemapNode, pk=pk)

        if request.method == 'POST':
            # verify that we are only operating on a leaf node
            if not node.is_leaf_node():
                raise Http404
            # if this was not a POST request, we don't make changes to the
            # database, we just display the warning page.
            page = node.object
            page.content.all().delete()
            page.delete()
            node.delete()
            redirect_to = self.reverse('index')
            if node.parent:
                redirect_to += '?r=%d' % node.parent_id
            return HttpResponseRedirect(redirect_to)

        context = {
            'object': node,
        }
        context.update(kwargs)

        templates = self.template_path('delete_confirm.html')
        return self.render(request, templates, context)

    # Redirect views

    @staff_login_required_m
    def list_redirects(self, request, *args, **kwargs):
        return self.generic_list(request, Redirect,
                                 permission_required=True,
                                 extra_context=kwargs)

    @staff_login_required_m
    def edit_redirect(self, request, pk=None, *args, **kwargs):
        return self.generic_edit(request, Redirect, pk=pk,
                                 form_class=RedirectEditForm,
                                 permission_required=True,
                                 extra_context=kwargs)

    @staff_login_required_m
    def delete_redirect(self, request, pk, *args, **kwargs):
        return self.generic_delete(request, Redirect, pk=pk,
                                   permission_required=True,
                                   extra_context=kwargs)

    @staff_login_required_m
    def perms_redirect(self, request, pk, *args, **kwargs):
        return self.generic_permissions(request, Redirect, pk=pk,
                                        **kwargs)

    # Application views

    @staff_login_required_m
    def list_placeholders(self, request, *args, **kwargs):
        return self.generic_list(request, Placeholder,
                                 paginate_by=0,
                                 permission_required=True,
                                 extra_context=kwargs)

    @staff_login_required_m
    def edit_placeholder_x(self, request, pk=None, *args, **kwargs):
        return self.generic_edit(request, Placeholder, pk=pk,
                                 permission_required=True,
                                 extra_context=kwargs)

    @staff_login_required_m
    def delete_placeholder_x(self, request, pk, *args, **kwargs):
        return self.generic_delete(request, Placeholder, pk=pk,
                                   permission_required=True,
                                   extra_context=kwargs)

    @staff_login_required_m
    def edit_application_node(self, request, pk=None, **extra_context):
        kwargs_form_class = PlaceholderConfigurationBase

        if pk is None:
            instance = SitemapNode()
            form_class = NewPlaceholderSitemapNodeForm
        else:
            instance = get_object_or_404(SitemapNode, pk=pk)
            form_class = PlaceholderSitemapNodeForm
            try:
                site = instance.object.site(instance)
            except ValueError:
                logger.exception("Application is unavailable, "
                                 "no custom form class.")
            else:
                kwargs_form_class = site.kwargs_form_class or kwargs_form_class

        if request.method == 'POST':
            form = form_class(data=request.POST, instance=instance,
                              user=request.user)
            kw = kwargs_form_class(data=request.POST, instance=instance,
                                   prefix='kw', user=request.user)
            copy = PlaceholderContentFormset(
                data=request.POST, instance=instance, prefix='copy')
            if form.is_valid() and kw.is_valid() and copy.is_valid():
                form.save()
                kw.save()
                copy.save()

                redirect_to = self.reverse('index')
                if instance.parent:
                    redirect_to += '?r=%d' % instance.parent_id

                # post_save_callback if we ever move this to a generic_edit
                # pattern in future.
                instance.object.invalidate_cache()

                return HttpResponseRedirect(redirect_to)
        else:
            form = form_class(instance=instance, user=request.user)
            kw = kwargs_form_class(
                instance=instance, prefix='kw', user=request.user)
            copy = PlaceholderContentFormset(instance=instance, prefix='copy')

        context = {
            'form': form,
            'formsets': {
                'kwargs': kw,
                'copy': copy,
            },
            'formset_media': kw.media + copy.media,
            'node': instance,
            'model': Placeholder.objects.none(),
        }
        context.update(extra_context)

        templates = self.template_path('edit_application.html')
        return self.render(request, templates, context)

    @require_POST_m
    @staff_login_required_m
    def delete_application_node(self, request, pk, *args, **kwargs):
        node = get_object_or_404(SitemapNode, pk=pk)

        if request.method == 'POST':
            # verify that we are only operating on a leaf node
            if not node.is_leaf_node():
                raise Http404
            redirect_to = reverse('admin:content:index', current_app=self.app)
            if node.parent:
                redirect_to += '?r=%d' % node.parent_id
            # if this was not a POST request, we don't make changes to the
            # database, we just display the warning page.
            node.delete()

            return HttpResponseRedirect(redirect_to)

        context = {
            'object': node,
        }
        context.update(kwargs)

        templates = self.template_path('delete_confirm.html')
        return self.render(request, templates, context)

    # Chunk views

    @staff_login_required_m
    def list_chunks(self, request, *args, **kwargs):
        return self.generic_list(request, Chunk,
                                 extra_context=kwargs)

    @staff_login_required_m
    def edit_chunk(self, request, pk=None, *args, **kwargs):
        return self.generic_edit(request, Chunk, pk=pk,
                                 extra_context=kwargs)

    @staff_login_required_m
    def delete_chunk(self, request, pk, **extra_context):
        return self.generic_delete(request, Chunk, pk=pk,
                                   extra_context=extra_context)

    @staff_login_required_m
    def perms_chunk(self, request, pk, *args, **kwargs):
        return self.generic_permissions(request, Chunk, pk=pk,
                                        **kwargs)

    @staff_login_required_m
    def ls(self, request, path=None, *args, **kwargs):
        try:
            directories, files = default_storage.listdir(path or ".")
        except OSError as e:
            raise Http404(e)

        directories.sort()
        files.sort()

        parentdir = os.path.dirname(path[:-1]) or 'ROOT' if path else ''

        writable = True  # presume it is, and fail gracefully if it isn't
        if path and hasattr(default_storage, 'writable'):
            writable = default_storage.writable(path)

        # Both types for Form expect a path to be passed in, set up the base.
        form_kwargs = dict(path=path)

        if request.method == 'POST':
            # Give the Forms the POST and FILES from the request
            form_kwargs.update(dict(data=request.POST, files=request.FILES))

            folder_form = NewFolderForm(prefix='folder', **form_kwargs)
            file_form = FileUploadForm(prefix='file', **form_kwargs)

            if folder_form.is_valid() and file_form.is_valid():
                redirect_to = '.'
                saved = None

                if folder_form.has_changed():
                    saved = folder_form.save()
                    logger.debug('created folder %s', saved)
                    folder_name = os.path.basename(saved)
                    messages.success(
                        request, _('Opened new folder "%s".') % folder_name)
                    redirect_to = folder_name + '/'  # FIXME reverse this

                if file_form.has_changed():
                    saved = file_form.save()
                    file_name = os.path.basename(saved)
                    messages.success(
                        request, _('Uploaded new file "%s"') % file_name)

                if saved is None:
                    messages.warning(
                        request, _('There were no changes to be saved.'))

                return self.redirect(redirect_to)

        else:
            folder_form = NewFolderForm(prefix='folder', **form_kwargs)
            file_form = FileUploadForm(prefix='file', **form_kwargs)

        def _directories(d):
            fullpath = os.path.join(path or '', d, '')
            return fullpath, d, default_storage.listdir(fullpath)

        directories = [_directories(d) for d in directories]
        match = resolve(request.path)

        @python_2_unicode_compatible
        class FileOrFolder(AdminUrlMixin, object):
            def __init__(s, pk, name):
                s.pk = pk
                s.name = name

            def __str__(s):
                return s.name

            def __repr__(s):
                return '<%s: %s, %r>' % (s.__class__.__name__, s.name, s.urls)

            def _get_admin_namespace(s):
                return match.namespace

            def _get_url_args(s):
                return (s.pk,)

        class File(FileOrFolder):
            icon = 'file'

            @cached_property
            def urls(s):
                namespace = s._get_admin_namespace()
                args = s._get_url_args()
                crud = {
                    'detail': os.path.join(settings.MEDIA_URL, *args),
                    'delete': reverse(
                        '%s:delete' % namespace, args=args),
                }
                return crud

            def _get_url_args(s):
                return filter(None, (path, s.pk))

        class Folder(FileOrFolder):
            icon = 'folder'

        object_list = (
            [Folder(d[0], d[1]) for d in directories] +
            [File(f, f) for f in files]
        )
        from collections import namedtuple
        context = {
            'base_url': default_storage.base_url,
            'path': path,
            'parent': parentdir,
            'directories': directories,
            'files': files,
            'folder_form': folder_form,
            'file_form': file_form,
            'writable': writable,
            'object_list': object_list,
            'queryset': Page.objects.all(),
            'list_template': first(
                self.template_path('list.inc.html', 'file')),
            'model': namedtuple('file', ()),
        }
        context.update(kwargs)

        templates = self.template_path('list.html', 'file')
        return self.render(request, templates, context)

    @staff_login_required_m
    def rm(self, request, path=None, filename=None, *args, **kwargs):
        try:
            directories, files = default_storage.listdir(path or '.')
        except OSError as e:
            logger.exception('path: %r', path)
            raise Http404(e[1])

        @python_2_unicode_compatible
        class File(object):
            def __init__(self, path):
                self.path = path

            def __str__(self):
                return self.path

            def is_leaf_node(self):
                return True

        class Folder(File):
            def __init__(self, path):
                self.path = os.path.dirname(path)

            def is_leaf_node(self):
                listdir = default_storage.listdir(self.path)
                logger.debug(listdir)
                return not any(filter(None, listdir))

        fullpath = os.path.join(path or '', filename or '')

        if filename:
            cls, action, tpl = File, default_storage.delete, 'file'
            item = os.path.basename(fullpath)
        elif hasattr(default_storage, 'rmdir'):
            cls, action, tpl = Folder, default_storage.rmdir, 'folder'
            item = os.path.basename(os.path.dirname(fullpath))
        else:
            raise Http404("We can't handle this, as the "
                          "backend storage system does not "
                          "support removing directories.")

        if path is not None:
            parentdir = os.path.normpath(os.path.join(path, '..')) + '/'
        else:
            parentdir = ''

        obj = cls(fullpath)
        if not obj.is_leaf_node():
            messages.error(request, _('The folder "%s" is not empty.') % item)
            return self.redirect(
                self.reverse('media:edit', kwargs={'path': parentdir}))

        if request.method == 'POST':
            action(fullpath)
            messages.success(
                request,
                'Successfully deleted %s "%s"' % (tpl, item))
            if path:
                if cls is Folder:
                    return self.redirect(
                        self.reverse('media:edit', kwargs={'path': parentdir}))
                return self.redirect(
                    self.reverse('media:edit', kwargs={'path': path}))
            return self.redirect(self.reverse('media:list'))

        context = {
            'path': path,
            'parentdir': parentdir,
            'filename': filename,
            'object': obj,
        }
        context.update(kwargs)

        templates = self.template_path('delete_confirm.html', tpl)
        return self.render(request, templates, context)


site.register(ContentAdminComponent)
