import re
import typing as t
from collections import OrderedDict
from rest_framework.fields import ChoiceField as _ChoiceField
from rest_framework.serializers import ModelSerializer as _ModelSerializer


class ChoiceField(_ChoiceField):
    """Rewrite this fields to support (int, str) choices."""

    def __init__(self, choices, **kwargs):
        super().__init__(choices, **kwargs)

        self.int_str_choices: t.Dict[int, str] = {}
        self.str_int_choices: t.Dict[str, int] = {}
        if choices:
            self._set_int_str_choices(choices)

    def _set_int_str_choices(self, choices: t.List[t.Tuple[int, str]]):
        int_str_choices: t.Dict[int, str] = {}
        str_int_choices: t.Dict[str, int] = {}
        for choice in choices:
            if len(choice) == 2 and isinstance(choice[0], int) and _is_lower_string(choice[1]):
                int_str_choices[choice[0]] = choice[1]
                str_int_choices[choice[1]] = choice[0]
            else:
                return
        self.int_str_choices = int_str_choices
        self.str_int_choices = str_int_choices

    def to_internal_value(self, data):
        if data == '' and self.allow_blank:
            return ''

        if self.str_int_choices:
            try:
                return self.str_int_choices[str(data)]
            except KeyError:
                self.fail('invalid_choice', input=data)

        try:
            return self.choice_strings_to_values[str(data)]
        except KeyError:
            self.fail('invalid_choice', input=data)

    def to_representation(self, value):
        if value in ('', None):
            return value

        if self.int_str_choices:
            return self.int_str_choices[value]

        return self.choice_strings_to_values.get(str(value), value)


class ModelSerializer(_ModelSerializer):
    serializer_choice_field = ChoiceField

    @property
    def _readable_fields(self):
        include_only_fields = getattr(self.Meta, 'include_only_fields', [])
        if not include_only_fields:
            for field in self.fields.values():
                if not field.write_only:
                    yield field
        else:
            include_terms = self.context.get('include_fields', [])
            if not include_terms:
                request = self.context.get('request')
                if request:
                    include_terms = getattr(request, 'include_terms', [])

            for field in self.fields.values():
                if field.field_name in include_only_fields and field.field_name not in include_terms:
                    continue

                if not field.write_only:
                    yield field

    def get_fields(self):
        fields = super().get_fields()
        flatten_fields = getattr(self.Meta, 'flatten_fields', None)
        if flatten_fields:
            return _make_flatten_fields(fields, flatten_fields)
        return fields


def _make_flatten_fields(form: OrderedDict, flatten: t.List[str]):
    for source in flatten:
        serializer: t.Any = form.pop(source, None)
        if serializer:
            fields = serializer.get_fields()
            for name in fields:
                field = fields[name]
                field.source = f'{source}.{name}'
                if name in form:
                    form[f'{source}_{name}'] = field
                else:
                    form[name] = field
    return form


def _is_lower_string(s: str):
    return isinstance(s, str) and bool(re.match(r'^[0-9a-z-_]+$', s))
