"""
The configuration wrappers that are used for :ref:`PARLER_LANGUAGES`.
"""

import copy
import sys

from django.conf import settings
from django.core.exceptions import ImproperlyConfigured

from parler.utils.i18n import get_language, get_null_language_error, is_supported_django_language


def add_default_language_settings(languages_list, var_name="PARLER_LANGUAGES", **extra_defaults):
    """
    Apply extra defaults to the language settings.
    This function can also be used by other packages to
    create their own variation of ``PARLER_LANGUAGES`` with extra fields.
    For example::

        from django.conf import settings
        from parler import appsettings as parler_appsettings

        # Create local names, which are based on the global parler settings
        MYAPP_DEFAULT_LANGUAGE_CODE = getattr(settings, 'MYAPP_DEFAULT_LANGUAGE_CODE', parler_appsettings.PARLER_DEFAULT_LANGUAGE_CODE)
        MYAPP_LANGUAGES = getattr(settings, 'MYAPP_LANGUAGES', parler_appsettings.PARLER_LANGUAGES)

        # Apply the defaults to the languages
        MYAPP_LANGUAGES = parler_appsettings.add_default_language_settings(MYAPP_LANGUAGES, 'MYAPP_LANGUAGES',
            code=MYAPP_DEFAULT_LANGUAGE_CODE,
            fallback=MYAPP_DEFAULT_LANGUAGE_CODE,
            hide_untranslated=False
        )

    The returned object will be an :class:`~parler.utils.conf.LanguagesSetting` object,
    which adds additional methods to the :class:`dict` object.

    :param languages_list: The settings, in :ref:`PARLER_LANGUAGES` format.
    :param var_name: The name of your variable, for debugging output.
    :param extra_defaults: Any defaults to override in the ``languages_list['default']`` section, e.g. ``code``, ``fallback``, ``hide_untranslated``.
    :return: The updated ``languages_list`` with all defaults applied to all sections.
    :rtype: LanguagesSetting
    """
    languages_list = LanguagesSetting(languages_list)

    languages_list.setdefault("default", {})
    defaults = languages_list["default"]
    defaults.setdefault(
        "hide_untranslated", False
    )  # Whether queries with .active_translations() may or may not return the fallback language.

    if "fallback" in defaults:
        # warnings.warn("Please use 'fallbacks' instead of 'fallback' in the 'defaults' section of {0}".format(var_name), DeprecationWarning)
        defaults["fallbacks"] = [defaults.pop("fallback")]
    if "fallback" in extra_defaults:
        # warnings.warn("Please use 'fallbacks' instead of 'fallback' in parameters for {0} = add_default_language_settings(..)".format(var_name), DeprecationWarning)
        extra_defaults["fallbacks"] = [extra_defaults.pop("fallback")]

    defaults.update(extra_defaults)  # Also allow to override code and fallback this way.

    # This function previously existed in appsettings, where it could reference the defaults directly.
    # However, this module is a more logical place for this function. To avoid circular import problems,
    # the 'code' and 'fallback' parameters are always passed by the appsettings module.
    # In case these are missing, default to the original behavior for backwards compatibility.
    if "code" not in defaults:
        from parler import appsettings

        defaults["code"] = appsettings.PARLER_DEFAULT_LANGUAGE_CODE
    if "fallbacks" not in defaults:
        from parler import appsettings

        defaults["fallbacks"] = [appsettings.PARLER_DEFAULT_LANGUAGE_CODE]

    if not is_supported_django_language(defaults["code"]):
        raise ImproperlyConfigured(
            "The value for {}['defaults']['code'] ('{}') does not exist in LANGUAGES".format(
                var_name, defaults["code"]
            )
        )

    for site_id, lang_choices in languages_list.items():
        if site_id == "default":
            continue

        if not isinstance(lang_choices, (list, tuple)):
            raise ImproperlyConfigured(
                f"{var_name}[{site_id}] should be a tuple of language choices!"
            )
        for i, choice in enumerate(lang_choices):
            if not is_supported_django_language(choice["code"]):
                raise ImproperlyConfigured(
                    f"{var_name}[{site_id}][{i}]['code'] does not exist in LANGUAGES"
                )

            # Copy all items from the defaults, so you can provide new fields too.
            for key, value in defaults.items():
                choice.setdefault(key, value)

    return languages_list


class LanguagesSetting(dict):
    """
    This is the actual object type of the :ref:`PARLER_LANGUAGES` setting.
    Besides the regular :class:`dict` behavior, it also adds some additional methods.
    """

    def get_language(self, language_code, site_id=None):
        """
        Return the language settings for the current site

        This function can be used with other settings variables
        to support modules which create their own variation of the ``PARLER_LANGUAGES`` setting.
        For an example, see :func:`~parler.appsettings.add_default_language_settings`.
        """
        if language_code is None:
            raise ValueError(get_null_language_error())

        if site_id is None:
            site_id = getattr(settings, "SITE_ID", None)

        for lang_dict in self.get(site_id, ()):
            if lang_dict["code"] == language_code:
                return lang_dict

        # no language match, search for variant: fr-ca falls back to fr
        for lang_dict in self.get(site_id, ()):
            if lang_dict["code"].split("-")[0] == language_code.split("-")[0]:
                return lang_dict

        return self["default"]

    def get_active_choices(self, language_code=None, site_id=None):
        """
        Find out which translations should be visible in the site.
        It returns a list with either a single choice (the current language),
        or a list with the current language + fallback language.
        """
        if language_code is None:
            language_code = get_language()

        lang_dict = self.get_language(language_code, site_id=site_id)
        if not lang_dict["hide_untranslated"]:
            return [language_code] + [
                lang for lang in lang_dict["fallbacks"] if lang != language_code
            ]
        else:
            return [language_code]

    def get_fallback_languages(self, language_code=None, site_id=None):
        """
        Find out what the fallback language is for a given language choice.

        .. versionadded 1.5
        """
        choices = self.get_active_choices(language_code, site_id=site_id)
        return choices[1:]

    def get_fallback_language(self, language_code=None, site_id=None):
        """
        Find out what the fallback language is for a given language choice.

        .. deprecated:: 1.5
           Use :func:`get_fallback_languages` instead.
        """
        choices = self.get_active_choices(language_code, site_id=site_id)
        if choices and len(choices) > 1:
            # Still take the last, like previous code.
            # With multiple fallback languages that means taking the base language.
            # Hence, upgrade the code to use get_fallback_languages() instead.
            return choices[-1]
        else:
            return None

    def get_default_language(self):
        """
        Return the default language.
        """
        return self["default"]["code"]

    def get_first_language(self, site_id=None):
        """
        Return the first language for the current site.
        This can be used for user interfaces, where the languages are displayed in tabs.
        """
        if site_id is None:
            site_id = getattr(settings, "SITE_ID", None)

        try:
            return self[site_id][0]["code"]
        except (KeyError, IndexError):
            # No configuration, always fallback to default language.
            # This is essentially a non-multilingual configuration.
            return self["default"]["code"]


def get_parler_languages_from_django_cms(cms_languages=None):
    """
    Converts django CMS' setting CMS_LANGUAGES into PARLER_LANGUAGES. Since
    CMS_LANGUAGES is a strict superset of PARLER_LANGUAGES, we do a bit of
    cleansing to remove irrelevant items.
    """
    valid_keys = ["code", "fallbacks", "hide_untranslated", "redirect_on_fallback"]
    if cms_languages:
        parler_languages = copy.deepcopy(cms_languages)
        for site_id, site_config in cms_languages.items():
            if site_id and (not isinstance(site_id, int) and site_id != "default"):
                del parler_languages[site_id]
                continue

            if site_id == "default":
                for key, value in site_config.items():
                    if key not in valid_keys:
                        del parler_languages["default"][key]
            else:
                for i, lang_config in enumerate(site_config):
                    for key, value in lang_config.items():
                        if key not in valid_keys:
                            del parler_languages[site_id][i][key]
        return parler_languages
    return None
