from collections import defaultdict

from django import forms
from django.template.defaultfilters import urlize
from django.utils.safestring import mark_safe
from django.utils.translation import activate, get_language
from django.utils.translation import gettext_lazy as _

from mezzanine.conf import registry, settings
from mezzanine.conf.models import Setting

if settings.USE_MODELTRANSLATION:
    from collections import OrderedDict

    from modeltranslation.utils import build_localized_fieldname


FIELD_TYPES = {
    bool: forms.BooleanField,
    int: forms.IntegerField,
    float: forms.FloatField,
}


class SettingsForm(forms.Form):
    """
    Form for settings - creates a field for each setting in
    ``mezzanine.conf`` that is marked as editable.
    """

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # Create a form field for each editable setting's from its type.
        active_language = get_language()
        for name in sorted(registry.keys()):
            setting = registry[name]
            if setting["editable"]:
                field_class = FIELD_TYPES.get(setting["type"], forms.CharField)
                if settings.USE_MODELTRANSLATION and setting["translatable"]:
                    for code in OrderedDict(settings.LANGUAGES):
                        try:
                            activate(code)
                        except:  # noqa
                            pass
                        else:
                            self._init_field(
                                setting,
                                field_class,
                                name,
                                code,
                                active_language,
                            )
                else:
                    self._init_field(setting, field_class, name)
            activate(active_language)

    def _init_field(
        self,
        setting,
        field_class,
        name,
        code=None,
        active_language=None,
    ):
        """
        Initialize a field whether it is built with a custom name for a
        specific translation language or not.
        """

        initial = getattr(settings, name)
        if (
            code
            and code != active_language
            and settings.USE_MODELTRANSLATION
            and setting["translatable"]
        ):
            try:
                initial = Setting.objects.get(name=name).value
            except Setting.DoesNotExist:
                pass

        kwargs = {
            "label": setting["label"] + ":",
            "required": setting["type"] in (int, float),
            "initial": initial,
            "help_text": self.format_help(setting["description"]),
        }
        if setting["choices"]:
            field_class = forms.ChoiceField
            kwargs["choices"] = setting["choices"]
        field_instance = field_class(**kwargs)
        code_name = "_modeltranslation_" + code if code else ""
        self.fields[name + code_name] = field_instance
        css_class = field_class.__name__.lower()
        field_instance.widget.attrs["class"] = css_class
        if code:
            field_instance.widget.attrs["class"] += " modeltranslation"

    def __iter__(self):
        """
        Calculate and apply a group heading to each field and order by
        the heading.
        """
        fields = list(super().__iter__())
        group = lambda field: field.name.split("_", 1)[0].title()
        misc = _("Miscellaneous")
        groups = defaultdict(int)
        for field in fields:
            groups[group(field)] += 1
        for (i, field) in enumerate(fields):
            setattr(fields[i], "group", group(field))
            if groups[fields[i].group] == 1:
                fields[i].group = misc
        return iter(sorted(fields, key=lambda x: (x.group == misc, x.group)))

    def save(self):
        """
        Save each of the settings to the DB.
        """
        active_language = get_language()
        for (name, value) in self.cleaned_data.items():
            if name not in registry:
                name, code = name.rsplit("_modeltranslation_", 1)
            else:
                code = None
            setting_obj, created = Setting.objects.get_or_create(name=name)
            if settings.USE_MODELTRANSLATION:
                if registry[name]["translatable"]:
                    try:
                        activate(code)
                    except:  # noqa
                        pass
                    finally:
                        setting_obj.value = value
                        activate(active_language)
                else:
                    # Duplicate the value of the setting for every language
                    for code in OrderedDict(settings.LANGUAGES):
                        setattr(
                            setting_obj, build_localized_fieldname("value", code), value
                        )
            else:
                setting_obj.value = value
            setting_obj.save()

    def format_help(self, description):
        """
        Format the setting's description into HTML.
        """
        for bold in ("``", "*"):
            parts = []
            if description is None:
                description = ""
            for i, s in enumerate(description.split(bold)):
                parts.append(s if i % 2 == 0 else "<b>%s</b>" % s)
            description = "".join(parts)
        description = urlize(description, autoescape=False)
        return mark_safe(description.replace("\n", "<br>"))
