import re
from typing import (
    Dict,
    List,
    Optional,
    Union,
)

from nreports.repository import (
    I18NRepository,
    L10NRepository,
)
from nreports_api.shared import JSON
from ._Line import Line
from ._ReportFunction import ReportFunction
from .exceptions import ObjectListValueIsNotAList


class TextLine(Line):

    def __init__(
        self,
        line: JSON,
        text_attr: str,
    ) -> None:
        super().__init__(line)

        self._text_attr = text_attr
        self._variables: List[str] = []
        self._object_list_variables: List[str] = []
        self._functions: List[ReportFunction] = []
        self._i18n_terms: List[str] = []
        self._text_list: List[Line] = []

    def inner_format(
        self,
        data: Dict[str, JSON],
        i18n_repository: I18NRepository,
        l10n_repository: L10NRepository,
    ) -> List[Dict[str, Union[str, Dict[str, str]]]]:

        if "objectList" in self.line:
            formatted = self._format_object_list(object_lists=data)
        else:
            formatted = [
                {
                    self._text_attr: self.format_variables(
                        variables=data,
                        text=self.line[self._text_attr],
                    )
                }
            ]

            if "textList" in self.line:
                formatted[0]["textList"] = self._format_text_list(
                    data=data,
                    i18n_repository=i18n_repository,
                    l10n_repository=l10n_repository,
                )

        for line in formatted:
            text = self._format_i18n_labels(
                text=line[self._text_attr],
                i18n_repository=i18n_repository,
            )
            line[self._text_attr] = self._format_functions(
                text=text,
                l10n_repository=l10n_repository,
            )

            if not line[self._text_attr]:
                line["command"] = "{CMD_BREAK_LINE}"
                line.pop(self._text_attr)
            elif "customization" in self.line:
                line["customization"] = self.line.get("customization")

        return formatted

    def _format_text_list(
        self,
        data: Dict[str, JSON],
        i18n_repository: I18NRepository,
        l10n_repository: L10NRepository,
    ) -> List[Dict[str, str]]:

        formatted_text_list: List[Dict[str, str]] = []
        for text_line in self._text_list:
            formatted_text_list.extend(
                text_line.format(
                    data=data,
                    i18n_repository=i18n_repository,
                    l10n_repository=l10n_repository,
                )
            )

        return formatted_text_list

    def set_text_list(
        self,
        text_list: List[Line],
    ) -> None:

        self._text_list = text_list

    def _format_object_list(
        self,
        object_lists: Dict[str, List[Dict[str, JSON]]],
    ) -> List[Dict[str, str]]:

        object_list_values = object_lists[self.line["objectList"]]
        if not isinstance(object_list_values, list):
            raise ObjectListValueIsNotAList(name=self.line["objectList"])

        formatted_lines = []
        for line_variables in object_list_values:
            formatted_line = self.format_variables(
                variables=line_variables,
                text=self.line[self._text_attr],
            )
            split_lines = formatted_line.split("\n")
            for line in split_lines:
                formatted_lines.append({self._text_attr: line})

        return formatted_lines

    def get_inner_data_to_build(
        self,
    ) -> Dict[str, Optional[Dict[str, None]]]:

        ret: Dict[str, Optional[Dict[str, None]]] = {}
        for variable in self.get_text_variables(text=self.line[self._text_attr]):
            ret[variable[1:-1]] = None

        if "objectList" in self.line:
            inner = ret
            ret = {
                self.line["objectList"]: inner,
            }

        if "textList" in self.line:
            for text_line in self._text_list:
                ret.update(text_line.get_inner_data_to_build())

        return ret

    @staticmethod
    def _format_functions(
        text: str,
        l10n_repository: L10NRepository,
    ) -> str:

        functions = TextLine._get_function_sentences(text=text)

        for function_sentence in functions:
            report_function = ReportFunction(
                function_sentence=function_sentence,
                l10n_repository=l10n_repository,
            )
            text = text.replace(
                function_sentence,
                report_function.execute(parsed_params={}),
            )

        return text

    @staticmethod
    def _get_function_sentences(
        text: str,
    ) -> List[str]:

        functions = []
        inner_funcs = 0
        inside_function = False
        function_sentence = ""
        for i in range(len(text)):
            c = text[i]
            if c == "#":
                inside_function = True

            if not inside_function:
                continue

            if c == "(":
                inner_funcs += 1
            elif c == ")":
                inner_funcs -= 1

                if inner_funcs == 0:
                    function_sentence += c
                    functions.append(function_sentence)
                    function_sentence = ""
                    inside_function = False

            if inside_function:
                function_sentence += c

        if function_sentence != "":
            functions.append(function_sentence)

        return functions

    @staticmethod
    def _format_i18n_labels(
        text: str,
        i18n_repository: I18NRepository,
    ) -> str:

        all_keys = re.findall(r"\$\w+", text)
        translations = i18n_repository.translate_labels(labels=all_keys)
        for i18n_key in all_keys:
            if i18n_key in translations:
                text = text.replace(
                    i18n_key,
                    translations[i18n_key],
                )

        return text
