from django.contrib.contenttypes.fields import GenericRel, GenericForeignKey, GenericRelation
from django.contrib.contenttypes.models import ContentType
from django.db import models

from jet_django.filters.model import model_filter_class_factory
from jet_django.serializers.model import model_serializer_factory
from jet_django.serializers.model_detail import model_detail_serializer_factory
from jet_django.views.model import model_viewset_factory


class JetAdminModelDescription(object):
    def __init__(self, Model, fields=None, hidden=False):
        self.model = Model
        self.fields = fields
        self.hidden = hidden

        self.content_type = ContentType.objects.get_for_model(Model)
        self.field_names = list(map(lambda x: x.name, self.get_display_model_fields()))
        self.serializer = model_serializer_factory(Model, self.field_names)
        self.detail_serializer = model_detail_serializer_factory(Model, self.field_names)
        self.filter_class = model_filter_class_factory(Model, self.get_display_model_fields(), self.get_model_relations())
        self.queryset = Model.objects.all()
        self.viewset = model_viewset_factory(
            Model,
            self.filter_class,
            self.serializer,
            self.detail_serializer,
            self.queryset
        )

    @property
    def viewset_url(self):
        return 'models/(?P<model>{})'.format(self.model._meta.db_table)

    def get_model_fields(self):
        fields = self.model._meta.get_fields()
        def filter_fields(x):
            if any(map(lambda rel: isinstance(x, rel), [
                models.ManyToOneRel,
                models.ManyToManyField,
                models.ManyToManyRel,
                GenericRel,
                GenericForeignKey,
                GenericRelation
            ])):
                return False
            return True
        return filter(filter_fields, fields)

    def get_model_relations(self):
        fields = self.model._meta.get_fields(include_hidden=True)
        def filter_fields(x):
            if any(map(lambda rel: isinstance(x, rel), [
                models.OneToOneRel,
                models.OneToOneField,
                models.ManyToOneRel,
                models.ManyToManyField,
                models.ManyToManyRel
            ])):
                return True
            return False
        return list(filter(filter_fields, fields))

    def get_model_relation_through(self, field):
        if isinstance(field, models.ManyToManyRel):
            return self.serialize_model(field.through)
        elif isinstance(field, models.ManyToManyField):
            return self.serialize_model(field.remote_field.through)

    def get_display_model_fields(self):
        fields = self.get_model_fields()
        def filter_fields(x):
            if self.fields:
                return x.name in self.fields
            return True
        return filter(filter_fields, fields)

    def serialize(self):
        return {
            'model': self.model._meta.db_table,
            'db_table': self.model._meta.db_table,
            'verbose_name': self.model._meta.verbose_name,
            'verbose_name_plural': self.model._meta.verbose_name_plural,
            'hidden': self.hidden,
            'fields': map(lambda field: {
                'name': field.name,
                'db_column': field.get_attname_column()[1],
                'verbose_name': field.verbose_name,
                'is_relation': field.is_relation,
                'field': field.__class__.__name__,
                'required': not field.blank,
                'null': field.null,
                'editable': field.editable,
                'filterable': field.name in self.filter_class.Meta.fields,
                'params': {
                    'related_model': self.serialize_model(field.related_model)
                }
            }, self.get_display_model_fields()),
            'flex_fields': [],
            'relations': map(lambda field: {
                'name': field.name,
                'verbose_name': field.related_model._meta.verbose_name_plural,
                'related_model': self.serialize_model(field.related_model),
                'field': field.__class__.__name__,
                'related_model_field': field.remote_field.name,
                'through': self.get_model_relation_through(field)
            }, self.get_model_relations())
        }

    def get_model(self):
        return {
            'model': self.model._meta.db_table
        }

    def get_related_models(self):
        return map(lambda field: {
            'model': field.related_model,
            'model_info': self.serialize_model(field.related_model)
        }, self.get_model_relations())

    def serialize_model(self, Model):
        if not Model:
            return
        return {
            'model': Model._meta.db_table,
        }
