"""Bot support for multiple languages."""

# Standard library modules
import asyncio
from collections import OrderedDict
import logging

# Project modules
from .messages import default_language_messages
from .utilities import extract, make_button, make_inline_keyboard


class MultiLanguageObject(object):
    """Make bot inherit from this class to make it support multiple languages.

    Call MultiLanguageObject().get_message(
        field1, field2, ...,
        update, user_record, language,
        format_kwarg1, format_kwarg2, ...
    ) to get the corresponding message in the selected language.
    """

    def __init__(self, *args,
                 messages=None,
                 default_language='en',
                 missing_message="Invalid message!",
                 supported_languages=None,
                 **kwargs):
        """Instantiate MultiLanguageObject, setting its attributes."""
        if messages is None:
            messages = dict()
        self.messages = messages
        self._default_language = default_language
        self._missing_message = missing_message
        if supported_languages is None:
            supported_languages = OrderedDict(
                {
                    self.default_language: OrderedDict(
                        name=self.default_language,
                        flag=''
                    )
                }
            )
        self._supported_languages = supported_languages

    @property
    def default_language(self):
        """Return default language."""
        return self._default_language

    def set_default_language(self, language):
        """Set default language."""
        self._default_language = language

    @property
    def missing_message(self):
        """Return this message when a proper message can not be found."""
        return self._missing_message

    def set_missing_message(self, message):
        """Set message to be returned where a proper one can not be found."""
        self._missing_message = message

    @property
    def supported_languages(self):
        """Return dict of supported languages.

        If it is not set, return default language only without flag.
        """
        return self._supported_languages

    def add_supported_languages(self, languages):
        """Add some `languages` to supported languages.

        Example
        ```python
        languages = {
            'en': {
                'flag': '🇬🇧',
                'name': 'English'
            },
            'it': {
                'flag': '🇮🇹',
                'name': 'Italiano'
            }
        }
        ```
        """
        assert type(languages) is dict, "Supported languages must be in a dict"
        if len(languages) == 0:
            return
        if self._supported_languages is None:
            self._supported_languages = dict()
        self._supported_languages.update(languages)

    def get_language(self, update=None, user_record=None, language=None):
        """Get language.

        Language will be the first non-null value of this list:
        - `language` parameter
        - `user_record['selected_language_code']`: language selected by user
        - `update['language_code']`: language of incoming telegram update
        - Fallback to default language if none of the above fits
        """
        if user_record is None:
            user_record = dict()
        if update is None:
            update = dict()
        if (
            language is None
            and 'selected_language_code' in user_record
        ):
            language = user_record['selected_language_code']
        if (
            language is None
            and 'from' in update
            and 'language_code' in update['from']
        ):
            language = update['from']['language_code']
        return language or self.default_language

    def get_message(self, *fields, update=None, user_record=None,
                    default_message=None, language=None, **format_kwargs):
        """Given a list of strings (`fields`), return proper message.

        Language will be determined by `get_language` method.
        `format_kwargs` will be passed to format function on the result.
        """
        # Choose language
        if update is None:
            update = dict()
        if user_record is None:
            user_record = dict()
        language = self.get_language(
            update=update,
            user_record=user_record,
            language=language
        )
        # Find result for `language`
        result = self.messages
        for field in fields:
            if field not in result:
                logging.debug(
                    "Please define self.message{f}".format(
                        f=''.join(
                            '[\'{field}\']'.format(
                                field=field
                            )
                            for field in fields
                        )
                    )
                )
                return default_message or self.missing_message
            result = result[field]
        if language not in result:
            # For specific languages, try generic ones
            language = language.partition('-')[0]
            if language not in result:
                language = 'en'
                if language not in result:
                    logging.debug(
                        "Please define self.message{f}['en']".format(
                            f=''.join(
                                '[\'{field}\']'.format(
                                    field=field
                                )
                                for field in fields
                            )
                        )
                    )
                    return default_message or self.missing_message
        if type(result) is str:
            return result
        return result[language].format(
            **format_kwargs
        )


async def _language_command(bot, update, user_record):
    text, reply_markup = get_language_panel(
        bot=bot,
        update=update,
        user_record=user_record
    )
    return dict(
        text=text,
        reply_markup=reply_markup
    )


def get_language_panel(bot, update, user_record):
    """Get language panel for user.

    Return text and reply_markup of the message about user's language
        preferences.
    """
    text = bot.get_message(
        'language', 'language_panel', 'text',
        update=update, user_record=user_record,
    )
    text += "\n"
    if 'selected_language_code' in user_record:
        current_code = user_record['selected_language_code']
    else:
        current_code = None
    for code, language in bot.supported_languages.items():
        text += (f"\n{'✅' if code == current_code else '☑️'} "
                 f"{language['name']} {language['flag']}")
    reply_markup = make_inline_keyboard(
        [
            make_button(
                text=(
                    f"{'✅' if code == current_code else '☑️'} "
                    f"{language['name']} {language['flag']}"
                ),
                prefix='lang:///',
                delimiter='|',
                data=['set', code]
            )
            for code, language in bot.supported_languages.items()
        ],
        3
    )
    return text, reply_markup


async def _language_button(bot, update, user_record, data):
    result, text, reply_markup = '', '', None
    if len(data) > 1 and data[0] == 'set':
        # If message is already updated, do not update it
        if (
            'selected_language_code' in user_record
            and data[1] == user_record['selected_language_code']
            and data[1] in bot.supported_languages
            and bot.supported_languages[data[1]]['flag'] in extract(
                update['message']['text'],
                starter='✅',
                ender='\n'
            )
        ):
            return
        # If database-stored information is not updated, update it
        if (
            'selected_language_code' not in user_record
            or data[1] != user_record['selected_language_code']
        ):
            with bot.db as db:
                db['users'].update(
                    dict(
                        selected_language_code=data[1],
                        id=user_record['id']
                    ),
                    ['id'],
                    ensure=True
                )
                user_record['selected_language_code'] = data[1]
        if 'chat' in update['message'] and update['message']['chat']['id'] > 0:
            asyncio.ensure_future(
                bot.send_message(
                    text=bot.get_message(
                        'language', 'language_button', 'language_set',
                        update=update['message'], user_record=user_record
                    ),
                    chat_id=update['message']['chat']['id']
                )
            )
    if len(data) == 0 or data[0] in ('show', 'set'):
        text, reply_markup = get_language_panel(bot=bot, update=update, user_record=user_record)
    if text:
        return dict(
            text=result,
            edit=dict(
                text=text,
                reply_markup=reply_markup
            )
        )
    return result


def init(telegram_bot,
         language_messages=None,
         show_in_keyboard=True,
         supported_languages=None):
    """Set language support to `bot`."""
    if supported_languages is None:
        supported_languages = {}
    from .bot import Bot
    assert isinstance(telegram_bot, MultiLanguageObject), (
        "Bot must be a MultiLanguageObject subclass in order to support "
        "multiple languages."
    )
    assert isinstance(telegram_bot, Bot), (
        "Bot must be a davtelepot Bot subclass in order to support "
        "command and button decorators."
    )
    if language_messages is None:
        language_messages = default_language_messages
    telegram_bot.messages['language'] = language_messages
    telegram_bot.add_supported_languages(supported_languages)

    aliases = [
        alias
        for alias in language_messages[
            'language_command']['alias'].values()
    ]

    @telegram_bot.command(
        command='/language',
        aliases=aliases,
        reply_keyboard_button=language_messages['language_command'][
            'reply_keyboard_button'],
        show_in_keyboard=show_in_keyboard,
        description=language_messages['language_command']['description'],
        authorization_level='everybody'
    )
    async def language_command(bot, update, user_record):
        return await _language_command(bot, update, user_record)

    @telegram_bot.button(
        prefix='lang:///',
        separator='|',
        description=language_messages['language_button']['description'],
        authorization_level='everybody'
    )
    async def language_button(bot, update, user_record, data):
        return await _language_button(bot, update, user_record, data)
