# -*- coding: utf-8 -*-
from __future__ import absolute_import, print_function, unicode_literals

from collections import OrderedDict

from django.db import models
from django.core.urlresolvers import reverse
from cms.models import Page, Placeholder, CMSPlugin
from cms.utils.plugins import build_plugin_tree, downcast_plugins
from rest_framework import serializers
from rest_framework.serializers import ListSerializer

from djangocms_rest_api.serializers.mapping import plugin_serializer_mapping
from djangocms_rest_api.serializers.utils import RequestSerializer

# for aldryn_forms SelectField
from aldryn_forms.models import Option

serializer_cache = {}

# code for one request with response with full data from CMS about page and her placeholders with plugins from this fork - https://github.com/DanielKirov/djangocms-rest-api/commit/c3ef49781fd01f86dbc4677fbd81ed4c3741ae8d
class NestedPlaceHolderPluginsSerializer(RequestSerializer, serializers.ModelSerializer):

    plugins = serializers.SerializerMethodField()

    class Meta:
        model = Placeholder
        fields = ['id', 'slot', 'plugins']
        depth = 2
    
    def get_plugins(self, obj):
         # Get the page language (PlaceHolderSerializer > PageSerializer > language
        try:
            language = self.parent.parent.language
        except Exception as e:
            print(e)
            print(repr(e))
            langeuage = 'en-gb'

        plugins = []
        for plugin in obj.get_plugins(language):
            instance, this_plugin = plugin.get_plugin_instance()
            model = getattr(this_plugin, 'model', None)
            if model:
                serializer = get_serializer(
                    instance, model=getattr(plugin, 'model', None), plugin=this_plugin, context=self.context
                )
                plugins.append(serializer.data)
        return plugins


# code for one request with response with full data from CMS about page and her placeholders with plugins
# from this fork - https://github.com/DanielKirov/djangocms-rest-api/commit/c3ef49781fd01f86dbc4677fbd81ed4c3741ae8d
class NestedPlaceHolderPluginsSerializer(RequestSerializer, serializers.ModelSerializer):

    plugins = serializers.SerializerMethodField()

    class Meta:
        model = Placeholder
        fields = ['id', 'slot', 'plugins']
        depth = 2
    
    def get_plugins(self, obj):
         # Get the page language (PlaceHolderSerializer > PageSerializer > language
        try:
            language = self.parent.parent.language
        except Exception as e:
            print(e)
            print(repr(e))
            langeuage = 'en-gb'

        plugins = []
        for plugin in obj.get_plugins(language):
            instance, this_plugin = plugin.get_plugin_instance()
            model = getattr(this_plugin, 'model', None)
            if model:
                serializer = get_serializer(
                    instance, model=getattr(plugin, 'model', None), plugin=this_plugin, context=self.context
                )
                plugins.append(serializer.data)
        return plugins


class PageSerializer(RequestSerializer, serializers.ModelSerializer):
    title = serializers.SerializerMethodField()
    page_title = serializers.SerializerMethodField()
    menu_title = serializers.SerializerMethodField()
    meta_description = serializers.SerializerMethodField()
    slug = serializers.SerializerMethodField()
    path = serializers.SerializerMethodField()
    template = serializers.SerializerMethodField()
    absolute_url = serializers.SerializerMethodField()
    languages = serializers.ListField(source='get_languages')
    url = serializers.SerializerMethodField()
    redirect = serializers.SerializerMethodField()
    parent_page = serializers.SerializerMethodField()
    placeholders = NestedPlaceHolderPluginsSerializer(read_only=True, many=True)
    

    class Meta:
        model = Page
        fields = [
            'id', 'title', 'placeholders', 'creation_date', 'changed_date', 'publication_date',
            'publication_end_date', 'in_navigation', 'template', 'is_home', 'languages', 'parent_page',
            'node', 'page_title', 'menu_title', 'meta_description', 'slug', 'url', 'path',
            'absolute_url', 'redirect'
        ]

    def get_title(self, obj):
        return obj.get_title(self.language)

    def get_page_title(self, obj):
        return obj.get_page_title(self.language)

    def get_menu_title(self, obj):
        return obj.get_menu_title(self.language)

    def get_meta_description(self, obj):
        return obj.get_meta_description(self.language)

    def get_slug(self, obj):
        return obj.get_slug(self.language)

    def get_path(self, obj):
        return obj.get_path(self.language)

    def get_template(self, obj):
        return obj.get_template()

    def get_absolute_url(self, obj):
        return obj.get_absolute_url(self.language)

    def get_url(self, obj):
        return reverse('api:page-detail', args=(obj.pk,))

    def get_redirect(self, obj):
        return obj.get_redirect(self.language)
    
    def get_parent_page(self, obj):
        parent_page = obj.get_parent_page()
        if parent_page:
            return parent_page.id

    @classmethod
    def many_init(cls, *args, **kwargs):
        kwargs['child'] = PageSerializer(*args, **kwargs)
        return ListSerializer(*args, **kwargs)


# TODO: decide if we need this
class PluginListSerializer(ListSerializer):
    """
    Custom ListSerializer supposed to make possible to use custom serializer for plugins list
    """

    def update(self, instance, validated_data):
        raise NotImplementedError('Readonly for now')

    def to_representation(self, data):

        iterable = data.all() if isinstance(data, models.Manager) else data
        resp = []
        for item in iterable:
            instance, plugin = item.get_plugin_instance()
            serializer = get_serializer(instance, plugin=plugin)
            resp.append(serializer.data)

        return resp


# TODO: Check image plugin data serializer
# TODO: decide if we need to return url for images with domain name or not, cdn?
class BasePluginSerializer(serializers.ModelSerializer):

    plugin_data = serializers.SerializerMethodField()
    inlines = serializers.SerializerMethodField()
    children = serializers.SerializerMethodField()

    class Meta:
        model = CMSPlugin
        list_serializer_class = PluginListSerializer
        fields = [
            'id', 'placeholder', 'parent', 'position', 'language', 'plugin_type', 'creation_date', 'changed_date',
            'plugin_data', 'inlines', 'children']

    def get_plugin_data(self, obj):

        plugin = obj.get_plugin_class()
        serializer = get_serializer(
            obj, model=plugin.model, plugin=plugin, context=self.context)
        return serializer.data

    def get_inlines(self, obj):
        """
        Some plugin can store data in related models
        This method supposed to fetch data from database for all inline models listed in inlines parameter of plugin
        prepare and return together with parent object
        :param obj:
        :return:
        """
        plugin = obj.get_plugin_class()
        inlines = getattr(plugin, 'inlines', [])
        data = {}
        for inline in inlines:
            for related_object in obj._meta.related_objects:
                if getattr(related_object, 'related_model', None) == inline.model:
                    name = related_object.name
                    # for SelectField from aldryn_forms support
                    if name == 'option':
                        options = Option.objects.filter(field=obj).all()
                        serializer = get_serializer(
                        options, model=inline.model, many=True, context=self.context)
                    else:
                        serializer = get_serializer(
                            getattr(obj, name).all(), model=inline.model, many=True, context=self.context)
                    data[name] = serializer.data
                    break
        return data

    def get_children(self, obj):
        """
        Some plugins can contain children
        This method supposed to get children and
        prepare and return together with parent object
        :param obj:
        :return:
        """
        data = []
        plugin = obj.get_plugin_class()
        if not(getattr(plugin, 'allow_children', False) and getattr(plugin, 'child_classes', None)):
            return data
        children = obj.get_descendants().order_by('placeholder', 'path')
        children = [obj] + list(children)
        children = list(downcast_plugins(children))
        children[0].parent_id = None
        children = list(build_plugin_tree(children))

        def get_plugin_data(child_plugin):
            serializer = get_serializer(child_plugin, model=child_plugin._meta.model, context=self.context)
            plugin_data = serializer.data
            plugin_data['inlines'] = self.get_inlines(child_plugin)
            if child_plugin.child_plugin_instances:
                plugin_data['children'] = []
                for plug in child_plugin.child_plugin_instances:
                    plugin_data['children'].append(get_plugin_data(plug))
            return plugin_data

        for child in children[0].child_plugin_instances or []:
            data.append(get_plugin_data(child))
        return data


class SimplePageSerializer(serializers.ModelSerializer):

    class Meta:
        model = Page
        fields = ['id', ]


class PlaceHolderSerializer(RequestSerializer, serializers.ModelSerializer):
    plugins = serializers.SerializerMethodField()
    page = SimplePageSerializer()

    class Meta:
        model = Placeholder
        fields = ['id', 'slot', 'plugins', 'page']
        depth = 2

    def get_plugins(self, obj):
        return [plugin.id for plugin in obj.get_plugins(self.language).filter(parent__isnull=True)]


def modelserializer_factory(model, serializer=serializers.ModelSerializer, fields=None, exclude=None, **kwargs):
    """
    Generate serializer basing on django's modelform_factory
    :param model: model we create serializer for
    :param serializer: base serializer class
    :param fields: list of fields to include in serializer
    :param exclude: list of fields to exclude from serializer
    :param kwargs: fields mapping
    :return:
    """

    # TODO: decide if we need cache and what to do with parameters tha can be different
    serializer_class = serializer_cache.get(model, None)

    if serializer_class:
        return serializer_class

    def _get_declared_fields(attrs):
        fields = [(field_name, attrs.pop(field_name))
                  for field_name, obj in list(attrs.items())
                  if isinstance(obj, serializers.Field)]
        fields.sort(key=lambda x: x[1]._creation_counter)
        return OrderedDict(fields)

    meta_attrs = {'model': model}
    if fields is not None:
        meta_attrs['fields'] = fields
    if exclude is not None:
        meta_attrs['exclude'] = exclude
    if fields is None and exclude is None:
        meta_attrs['fields'] = '__all__'

    parent = (object, )
    Meta = type(str('Meta'), parent, meta_attrs)
    class_name = model.__name__ + str('Serializer')

    serializer_class_attrs = {
        'Meta': Meta,
        '_get_declared_fields': _get_declared_fields(kwargs),
    }
    serializer_class = type(serializer)(class_name, (serializer,), serializer_class_attrs)
    serializer_cache[model] = serializer_class
    return serializer_class


def get_serializer_class(plugin=None, model=None):
    serializer_class = None
    if plugin:
        serializer_class = getattr(plugin, 'serializer_class', None)
        if not serializer_class:
            # some plguins such TextPlugin can be proxy object and don't have __name__ attr
            try:
                serializer_class = plugin_serializer_mapping.get(plugin.__name__)
            except AttributeError:
                serializer_class = plugin_serializer_mapping.get(plugin.name)


    if not serializer_class:
        if not model:
            serializer_class = BasePluginSerializer
        else:
            serializer_class = modelserializer_factory(model)
    return serializer_class


def get_serializer(instance, plugin=None, model=None, *args, **kwargs):
    """
    :param instance: model instance or queryset
    :param plugin: plugin instance that is used to get serializer for
    :param model: plugin model we build serializer for
    :param kwargs: kwargs like many and other
    :return:
    """
    serializer_class = get_serializer_class(plugin=plugin, model=model)
    if 'read_only' not in kwargs:
        kwargs['read_only'] = True
    return serializer_class(instance, *args, **kwargs)
