"""
Helper functions for mapping model fields to a dictionary of default
keyword arguments that should be used for their equivelent serializer fields.
"""
from django.core import validators
from django.db import models
from django.utils.text import capfirst
from rest_framework.compat import clean_manytomany_helptext
from rest_framework.validators import UniqueValidator
import inspect


NUMERIC_FIELD_TYPES = (
    models.IntegerField, models.FloatField, models.DecimalField
)


class ClassLookupDict(object):
    """
    Takes a dictionary with classes as keys.
    Lookups against this object will traverses the object's inheritance
    hierarchy in method resolution order, and returns the first matching value
    from the dictionary or raises a KeyError if nothing matches.
    """
    def __init__(self, mapping):
        self.mapping = mapping

    def __getitem__(self, key):
        if hasattr(key, '_proxy_class'):
            # Deal with proxy classes. Ie. BoundField behaves as if it
            # is a Field instance when using ClassLookupDict.
            base_class = key._proxy_class
        else:
            base_class = key.__class__

        for cls in inspect.getmro(base_class):
            if cls in self.mapping:
                return self.mapping[cls]
        raise KeyError('Class %s not found in lookup.', cls.__name__)

    def __setitem__(self, key, value):
        self.mapping[key] = value


def needs_label(model_field, field_name):
    """
    Returns `True` if the label based on the model's verbose name
    is not equal to the default label it would have based on it's field name.
    """
    default_label = field_name.replace('_', ' ').capitalize()
    return capfirst(model_field.verbose_name) != default_label


def get_detail_view_name(model):
    """
    Given a model class, return the view name to use for URL relationships
    that refer to instances of the model.
    """
    return '%(model_name)s-detail' % {
        'app_label': model._meta.app_label,
        'model_name': model._meta.object_name.lower()
    }


def get_field_kwargs(field_name, model_field):
    """
    Creates a default instance of a basic non-relational field.
    """
    kwargs = {}
    validator_kwarg = list(model_field.validators)

    # The following will only be used by ModelField classes.
    # Gets removed for everything else.
    kwargs['model_field'] = model_field

    if model_field.verbose_name and needs_label(model_field, field_name):
        kwargs['label'] = capfirst(model_field.verbose_name)

    if model_field.help_text:
        kwargs['help_text'] = model_field.help_text

    max_digits = getattr(model_field, 'max_digits', None)
    if max_digits is not None:
        kwargs['max_digits'] = max_digits

    decimal_places = getattr(model_field, 'decimal_places', None)
    if decimal_places is not None:
        kwargs['decimal_places'] = decimal_places

    if isinstance(model_field, models.TextField):
        kwargs['style'] = {'base_template': 'textarea.html'}

    if isinstance(model_field, models.AutoField) or not model_field.editable:
        # If this field is read-only, then return early.
        # Further keyword arguments are not valid.
        kwargs['read_only'] = True
        return kwargs

    if model_field.has_default() or model_field.blank or model_field.null:
        kwargs['required'] = False

    if model_field.null and not isinstance(model_field, models.NullBooleanField):
        kwargs['allow_null'] = True

    if model_field.blank:
        kwargs['allow_blank'] = True

    if model_field.flatchoices:
        # If this model field contains choices, then return early.
        # Further keyword arguments are not valid.
        kwargs['choices'] = model_field.flatchoices
        return kwargs

    # Ensure that max_length is passed explicitly as a keyword arg,
    # rather than as a validator.
    max_length = getattr(model_field, 'max_length', None)
    if max_length is not None and isinstance(model_field, models.CharField):
        kwargs['max_length'] = max_length
        validator_kwarg = [
            validator for validator in validator_kwarg
            if not isinstance(validator, validators.MaxLengthValidator)
        ]

    # Ensure that min_length is passed explicitly as a keyword arg,
    # rather than as a validator.
    min_length = next((
        validator.limit_value for validator in validator_kwarg
        if isinstance(validator, validators.MinLengthValidator)
    ), None)
    if min_length is not None and isinstance(model_field, models.CharField):
        kwargs['min_length'] = min_length
        validator_kwarg = [
            validator for validator in validator_kwarg
            if not isinstance(validator, validators.MinLengthValidator)
        ]

    # Ensure that max_value is passed explicitly as a keyword arg,
    # rather than as a validator.
    max_value = next((
        validator.limit_value for validator in validator_kwarg
        if isinstance(validator, validators.MaxValueValidator)
    ), None)
    if max_value is not None and isinstance(model_field, NUMERIC_FIELD_TYPES):
        kwargs['max_value'] = max_value
        validator_kwarg = [
            validator for validator in validator_kwarg
            if not isinstance(validator, validators.MaxValueValidator)
        ]

    # Ensure that max_value is passed explicitly as a keyword arg,
    # rather than as a validator.
    min_value = next((
        validator.limit_value for validator in validator_kwarg
        if isinstance(validator, validators.MinValueValidator)
    ), None)
    if min_value is not None and isinstance(model_field, NUMERIC_FIELD_TYPES):
        kwargs['min_value'] = min_value
        validator_kwarg = [
            validator for validator in validator_kwarg
            if not isinstance(validator, validators.MinValueValidator)
        ]

    # URLField does not need to include the URLValidator argument,
    # as it is explicitly added in.
    if isinstance(model_field, models.URLField):
        validator_kwarg = [
            validator for validator in validator_kwarg
            if not isinstance(validator, validators.URLValidator)
        ]

    # EmailField does not need to include the validate_email argument,
    # as it is explicitly added in.
    if isinstance(model_field, models.EmailField):
        validator_kwarg = [
            validator for validator in validator_kwarg
            if validator is not validators.validate_email
        ]

    # SlugField do not need to include the 'validate_slug' argument,
    if isinstance(model_field, models.SlugField):
        validator_kwarg = [
            validator for validator in validator_kwarg
            if validator is not validators.validate_slug
        ]

    if getattr(model_field, 'unique', False):
        validator = UniqueValidator(queryset=model_field.model._default_manager)
        validator_kwarg.append(validator)

    if validator_kwarg:
        kwargs['validators'] = validator_kwarg

    return kwargs


def get_relation_kwargs(field_name, relation_info):
    """
    Creates a default instance of a flat relational field.
    """
    model_field, related_model, to_many, has_through_model = relation_info
    kwargs = {
        'queryset': related_model._default_manager,
        'view_name': get_detail_view_name(related_model)
    }

    if to_many:
        kwargs['many'] = True

    if has_through_model:
        kwargs['read_only'] = True
        kwargs.pop('queryset', None)

    if model_field:
        if model_field.verbose_name and needs_label(model_field, field_name):
            kwargs['label'] = capfirst(model_field.verbose_name)
        help_text = clean_manytomany_helptext(model_field.help_text)
        if help_text:
            kwargs['help_text'] = help_text
        if not model_field.editable:
            kwargs['read_only'] = True
            kwargs.pop('queryset', None)
        if kwargs.get('read_only', False):
            # If this field is read-only, then return early.
            # No further keyword arguments are valid.
            return kwargs
        if model_field.has_default() or model_field.null:
            kwargs['required'] = False
        if model_field.null:
            kwargs['allow_null'] = True
        if model_field.validators:
            kwargs['validators'] = model_field.validators
        if getattr(model_field, 'unique', False):
            validator = UniqueValidator(queryset=model_field.model._default_manager)
            kwargs['validators'] = kwargs.get('validators', []) + [validator]

    return kwargs


def get_nested_relation_kwargs(relation_info):
    kwargs = {'read_only': True}
    if relation_info.to_many:
        kwargs['many'] = True
    return kwargs


def get_url_kwargs(model_field):
    return {
        'view_name': get_detail_view_name(model_field)
    }
