import enum

from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.utils.translation import gettext_lazy as _

from utilities.data import get_config_value_ci
from utilities.string import enum_key

__all__ = (
    'ChoiceSet',
    'unpack_grouped_choices',
)


class ChoiceSetMeta(type):
    """
    Metaclass for ChoiceSet
    """
    def __new__(mcs, name, bases, attrs):

        # Extend static choices with any configured choices
        if key := attrs.get('key'):
            if type(attrs['CHOICES']) is not list:
                raise ImproperlyConfigured(
                    _("{name} has a key defined but CHOICES is not a list").format(name=name)
                )
            app = attrs['__module__'].split('.', 1)[0]
            replace_key = f'{app}.{key}'
            replace_choices = get_config_value_ci(settings.FIELD_CHOICES, replace_key)
            if replace_choices is not None:
                attrs['CHOICES'] = replace_choices
            else:
                extend_key = f'{replace_key}+'
                extend_choices = get_config_value_ci(settings.FIELD_CHOICES, extend_key)
                if extend_choices is not None:
                    attrs['CHOICES'].extend(extend_choices)

        # Define choice tuples and color maps
        attrs['_choices'] = []
        attrs['colors'] = {}
        for choice in attrs['CHOICES']:
            if isinstance(choice[1], (list, tuple)):
                grouped_choices = []
                for c in choice[1]:
                    grouped_choices.append((c[0], c[1]))
                    if len(c) == 3:
                        attrs['colors'][c[0]] = c[2]
                attrs['_choices'].append((choice[0], grouped_choices))
            else:
                attrs['_choices'].append((choice[0], choice[1]))
                if len(choice) == 3:
                    attrs['colors'][choice[0]] = choice[2]

        return super().__new__(mcs, name, bases, attrs)

    def __call__(cls, *args, **kwargs):
        # django-filters will check if a 'choices' value is callable, and if so assume that it returns an iterable
        return getattr(cls, '_choices', ())

    def __iter__(cls):
        return iter(getattr(cls, '_choices', ()))


class ChoiceSet(metaclass=ChoiceSetMeta):
    """
    Holds an iterable of choice tuples suitable for passing to a Django model or form field. Choices can be defined
    statically within the class as CHOICES and/or gleaned from the FIELD_CHOICES configuration parameter.
    """
    CHOICES = list()

    @classmethod
    def values(cls):
        return [c[0] for c in unpack_grouped_choices(cls._choices)]

    @classmethod
    def as_enum(cls, name=None, prefix=''):
        """
        Return the ChoiceSet as an Enum. If no name is provided, "Choices" will be stripped from the class name (if
        present) and "Enum" will be appended. For example, "CircuitStatusChoices" will become "CircuitStatusEnum".
        """
        name = name or f"{cls.__name__.split('Choices')[0]}Enum"
        prefix = f'{prefix}_' if prefix else ''
        data = {f'{prefix}{enum_key(v)}'.upper(): v for v in cls.values()}
        return enum.Enum(name, data)


def unpack_grouped_choices(choices):
    """
    Unpack a grouped choices hierarchy into a flat list of two-tuples. For example:

    choices = (
        ('Foo', (
            (1, 'A'),
            (2, 'B')
        )),
        ('Bar', (
            (3, 'C'),
            (4, 'D')
        ))
    )

    becomes:

    choices = (
        (1, 'A'),
        (2, 'B'),
        (3, 'C'),
        (4, 'D')
    )
    """
    unpacked_choices = []
    for key, value in choices:
        if isinstance(value, (list, tuple)):
            # Entered an optgroup
            for optgroup_key, optgroup_value in value:
                unpacked_choices.append((optgroup_key, optgroup_value))
        else:
            unpacked_choices.append((key, value))
    return unpacked_choices
