from django.utils.translation import ugettext_lazy as _

from rest_framework import serializers
from rest_framework.utils.field_mapping import get_field_kwargs

from .fields import EnumChoiceField as ModelEnumChoiceField
from .choice_builders import value_value
from .utils import as_choice_builder, value_from_built_choice

NO_KEY_MSG = _('Key {failing_key} is not a valid {enum_class_name}')
NOT_A_LIST_MSG = _('Expected a list of items but got type "{input_type}".')
EMPTY_MSG = _('This selection may not be empty.')


class EnumChoiceField(serializers.Field):
    default_error_messages = {
        'non_existent_key': NO_KEY_MSG
    }

    def __init__(self, enum_class, choice_builder=value_value, **kwargs):
        super().__init__(**kwargs)
        self.enum_class = enum_class
        self.choice_builder = as_choice_builder(choice_builder)

    def to_representation(self, value):
        return value_from_built_choice(
            self.choice_builder(value)
        )

    def to_internal_value(self, value):
        for choice in self.enum_class:
            if value_from_built_choice(self.choice_builder(choice)) == value:
                return choice

        self.fail(
            'non_existent_key',
            failing_key=value,
            enum_class_name=self.enum_class.__name__
        )


class MultipleEnumChoiceField(EnumChoiceField):
    default_error_messages = {
        'non_existent_key': NO_KEY_MSG,
        'not_a_list': NOT_A_LIST_MSG,
        'empty': EMPTY_MSG
    }

    def __init__(self, *args, **kwargs):
        self.allow_empty = kwargs.pop('allow_empty', False)

        super().__init__(*args, **kwargs)

        self.default_error_messages = {
            **self.default_error_messages,

        }

    def to_internal_value(self, data):
        if not isinstance(data, list):
            self.fail('not_a_list', input_type=type(data).__name__)

        if not self.allow_empty and not data:
            self.fail('empty')

        return [
            super(MultipleEnumChoiceField, self).to_internal_value(value)
            for value in data
        ]

    def to_representation(self, data):
        return [
            super(MultipleEnumChoiceField, self).to_representation(value)
            for value in data
        ]


class EnumChoiceModelSerializerMixin:
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.serializer_field_mapping[ModelEnumChoiceField] = EnumChoiceField

    def build_standard_field(self, field_name, model_field):
        """
        By default `ModelSerializer.build_standard_field` coerces any field
        that has a model field with choices to `ChoiceField` wich returns the
        value directly.

        Since enum values resemble `EnumClass.ENUM_INSTANCE`
        they won't be able to be encoded by the JSONEncoder when being passed
        to a `Response`.
        """

        if isinstance(model_field, ModelEnumChoiceField):
            # These are kwargs, generated by `get_field_kwargs`
            # but are not needed for our field.
            # `model_field` is used only in children of DRF's `ModelField`
            # `choices` is not used because we use `field.enum_class` to validate the choice
            # `max_length` is generated from the model field's max_length and we don't use it
            dump_kwargs = ('model_field', 'choices', 'max_length', 'allow_blank')

            initial_kwargs = {
                'enum_class': model_field.enum_class,
                'choice_builder': model_field.choice_builder,
                **get_field_kwargs(field_name, model_field)
            }
            finalized_kwargs = {
                key: value for key, value in initial_kwargs.items()
                if key not in dump_kwargs
            }

            return EnumChoiceField, finalized_kwargs

        return super().build_standard_field(field_name, model_field)
