import json
from typing import (
    Dict,
    List,
    Optional,
)

from messagebus import (
    DataType,
    DefaultToken,
    Message,
    MessageBus,
    TokenCreator,
    TokenPriority,
)
from model.exceptions import ErrorLoadingPosTranslations
from ._I18NRepository import I18NRepository
from ._L10NRepository import L10NRepository
from ._POSControllerRepository import POSControllerRepository

ignore_languages = ["pt", "en_UK", "en_US"]
TK_I18N_GETMSG = TokenCreator.create_token(
    priority=TokenPriority.low,
    message_group="7",
    number="2",
)
TK_I18N_GETLANGS = TokenCreator.create_token(
    priority=TokenPriority.low,
    message_group="7",
    number="4"
)
TK_I18N_GETTABLEJSON = TokenCreator.create_token(
    priority=TokenPriority.low,
    message_group="7",
    number="5",
)


class I18N(object):

    def __init__(
        self,
        message_bus: MessageBus,
        pos_controller_repository: POSControllerRepository,
    ) -> None:

        self.message_bus = message_bus
        self.pos_controller_repository = pos_controller_repository

        self._l10n_params: Dict[int, Dict[str, str]] = {}
        self._i18n_labels: Dict[str, Dict[str, str]] = {}

        self._set_i18n_labels_for_each_language()

    def translate_labels(
        self,
        pos_id: int,
        labels: List[str],
    ) -> Dict[str, Optional[str]]:

        if pos_id not in self._l10n_params:
            self._set_l10n_params(pos_id=pos_id)

        dict_words_translated: Dict[str, Optional[str]] = {}
        language = self._get_language(pos_id=pos_id)
        for label in labels:
            translated_label = self._i18n_labels[language].get(
                label.replace("$", "")
            )
            dict_words_translated[label] = translated_label

        return dict_words_translated

    def build_l10n_repository(
        self,
        pos_id: int,
    ) -> L10NRepository:

        if pos_id not in self._l10n_params:
            self._set_l10n_params(pos_id=pos_id)

        return L10NRepository(l10n_params=self._l10n_params[pos_id])

    def build_i18n_repository(
        self,
        pos_id: int,
    ) -> I18NRepository:

        try:
            language = self._get_language(pos_id=pos_id)
            labels = self._get_i18n_labels(language=language)
            return I18NRepository(i18n_labels=labels)

        except (Exception,):
            raise ErrorLoadingPosTranslations()

    def _set_i18n_labels_for_each_language(
        self,
    ) -> None:

        languages = self._get_i18n_languages()
        for language in languages:
            self._set_i18n_table_labels(language=language)

    def _get_i18n_languages(
        self,
    ) -> List[str]:

        message = Message(
            data_type=DataType.param,
            token=TK_I18N_GETLANGS,
        )
        response = self.message_bus.send_message(
            component="I18N",
            message=message,
        )
        if response.token != DefaultToken.TK_SYS_ACK:
            raise RuntimeError("Error trying to get all languages from I18N")

        all_languages = response.data.decode().split("\0")
        languages = []
        for language in all_languages:
            if language and language not in ignore_languages:
                languages.append(language)

        return languages

    def _get_i18n_labels(
        self,
        language: str,
    ) -> Dict[str, str]:

        message = Message(
            data=language.encode(),
            data_type=DataType.param,
            token=TK_I18N_GETTABLEJSON,
        )
        response = self.message_bus.send_message(
            component="I18N",
            message=message,
        )
        if response.token != DefaultToken.TK_SYS_ACK:
            raise RuntimeError(f"Error trying to get table labels {language} from I18N")

        return json.loads(response.data.decode())

    def _set_i18n_table_labels(
        self,
        language: str,
    ) -> None:

        message = Message(
            data=language.encode(),
            data_type=DataType.param,
            token=TK_I18N_GETTABLEJSON,
        )
        response = self.message_bus.send_message(
            component="I18N",
            message=message,
        )
        if response.token != DefaultToken.TK_SYS_ACK:
            raise RuntimeError(f"Error trying to get table labels {language} from I18N")

        all_labels = json.loads(response.data.decode())
        self._i18n_labels[language] = all_labels

    def _get_language(
        self,
        pos_id: int,
    ) -> str:

        pos_model = self.pos_controller_repository.get_model(pos_id=pos_id)
        if pos_model:
            language = pos_model.find("Language")
            if language is not None:
                return language.get("name", "")

        raise RuntimeError(f"Error trying to get Language, pos id {pos_id} does not exist in PosController cache")

    def _set_l10n_params(
        self,
        pos_id: int,
    ) -> None:

        l10n_params = [
            "L10_PYTHON_DATE_FORMAT",
            "L10_PYTHON_DATETIME_FORMAT",
            "L10N_DECIMAL_PLACES",
            "L10N_CURRENCY_DECIMALS",
            "L10N_CURRENCY_SYMBOL",
            "L10N_DECIMALS_SEPARATOR",
            "L10N_THOUSANDS_SEPARATOR",
            "L10N_CURRENCY_SYMBOL_POSITION",
        ]

        language = self._get_language(pos_id=pos_id)
        l10n_param = language + "\0"
        l10n_param += "\0".join(l10n_params)

        message = Message(
            token=TK_I18N_GETMSG,
            data_type=DataType.param,
            data=l10n_param.encode(),
        )
        response = self.message_bus.send_message(
            component="I18N",
            message=message,
        )
        if response.token != DefaultToken.TK_SYS_ACK:
            raise RuntimeError(f"Invalid token from I18n: TK_I18N_GETMSG - {response.data.decode()}")

        l10n_data = self._remove_language(data=response.data.decode())
        self._l10n_params[pos_id] = {}
        count = 0
        for _ in range(len(l10n_params)):
            self._l10n_params[pos_id][l10n_params[count]] = l10n_data[count]
            count += 1

    @staticmethod
    def _remove_language(
        data: str,
    ) -> List[str]:

        return data.split("\0")[1:]
