import re
from abc import (
    ABC,
    abstractmethod,
)
from typing import (
    Dict,
    List,
    Optional,
    Union,
)

from model.exceptions import (
    ConditionMustBeAVariable,
    TextListAttributeIsNotAList,
    VariableNotGeneratedByBuilder,
)
from nreports.repository import (
    I18NRepository,
    L10NRepository,
)
from nreports.utils import NON_BREAKING_SPACE
from nreports_api.shared import JSON


class Line(ABC):

    def __init__(
        self,
        line: JSON,
    ) -> None:

        self.line = line
        self._variables_to_distribute: Dict[str, List[str]] = {}

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

        raise NotImplementedError()

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

        raise NotImplementedError()

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

        data_to_build: Dict[str, Optional[Dict[str, None]]] = {}
        condition_variable = self._get_condition_variables(line=self.line)
        for variable in condition_variable:
            data_to_build[variable] = None

        data_to_build.update(self.get_inner_data_to_build())

        return data_to_build

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

        if not self._is_condition_valid(variables=data):
            return []

        ret = []
        ret.extend(
            self.inner_format(
                data=data,
                i18n_repository=i18n_repository,
                l10n_repository=l10n_repository,
            )
        )

        return ret

    def format_variables(
        self,
        variables: Dict[str, JSON],
        text: str,
    ) -> str:

        ret_text = text.replace("<str>", NON_BREAKING_SPACE)
        condition_variables = self._get_condition_variables(line=self.line)
        for full_variable_name in self.get_text_variables(text=text):
            variable_name = full_variable_name[1:-1]
            try:
                variable_value = variables[variable_name]
                if variable_value is None and variable_name not in condition_variables:
                    raise VariableNotGeneratedByBuilder(variable_name=variable_name)

            except KeyError:
                raise VariableNotGeneratedByBuilder(variable_name=variable_name)

            if isinstance(variable_value, list):
                variable_value = self._distribute_variables_values(
                    variable_name=variable_name,
                    variable_value=variable_value,
                )

            ret_text = ret_text.replace(
                full_variable_name,
                NON_BREAKING_SPACE + str(variable_value) + NON_BREAKING_SPACE,
            )

        return ret_text

    def _distribute_variables_values(
        self,
        variable_name: str,
        variable_value: List[str],
    ) -> str:

        if variable_name not in self._variables_to_distribute:
            self._variables_to_distribute[variable_name] = variable_value

        return self._variables_to_distribute[variable_name].pop(0)

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

        return re.findall(r"{[^}]*}", text)

    @staticmethod
    def build(
        line: JSON,
    ) -> Union["Line", List["Line"]]:

        from ._TextLine import TextLine
        from ._CommandLine import CommandLine
        from ._ImageLine import ImageLine
        from ._IgnoredLine import IgnoredLine
        from ._InnerListLine import InnerListLine

        if "text" in line:
            return TextLine(
                line=line,
                text_attr="text",
            )

        elif "innerList" in line:
            return InnerListLine(line=line)

        elif "command" in line:
            return CommandLine(line=line)

        elif "qrCode" in line:
            text_line = TextLine(
                line=line,
                text_attr="qrCode",
            )
            return Line._fill_text_list(
                text_line=text_line,
                line=line,
            )

        elif "image" in line:
            text_line = ImageLine(
                line=line,
                text_attr="image",
            )
            return Line._fill_text_list(
                text_line=text_line,
                line=line,
            )

        elif "barCode" in line:
            return TextLine(
                line=line,
                text_attr="barCode",
            )

        elif "header" in line:
            return IgnoredLine(
                line=line,
                text_attr="",
            )

        raise RuntimeError("Unknown line type")

    @staticmethod
    def _fill_text_list(
        text_line,
        line: JSON,
    ) -> "Line":

        if "textList" in line:
            text_list = Line._get_text_list(line=line)
            text_line.set_text_list(text_list=text_list)

        return text_line

    @staticmethod
    def _get_text_list(
        line: JSON,
    ) -> List["Line"]:

        from ._TextLine import TextLine

        if not isinstance(line["textList"], list):
            raise TextListAttributeIsNotAList()

        text_list: List["Line"] = []
        for text in line["textList"]:
            text_list.append(
                TextLine(
                    line=text,
                    text_attr="text",
                )
            )

        return text_list

    def _get_condition_variables(
        self,
        line: Dict[str, JSON],
    ) -> List[str]:

        condition_variables: List[str] = []
        if "condition" in line:
            conditions = line["condition"]
            if isinstance(conditions, list):
                for condition in conditions:
                    condition_variables.append(condition[1:-1])
            else:
                condition_variables.append(conditions[1:-1])

        if "textList" in line:
            for text_line in line["textList"]:
                variable = self._get_condition_variables(line=text_line)
                if variable:
                    condition_variables.extend(variable)

        return condition_variables

    def _is_condition_valid(
        self,
        variables: Dict[str, JSON],
        line: Optional[JSON] = None,
    ) -> bool:

        line_to_check = line or self.line
        condition_line = line_to_check.get("condition")

        if isinstance(condition_line, list):
            for condition in condition_line:
                valid_condition = self._check_condition(
                    condition=condition,
                    variables=variables,
                )
                if valid_condition is False:
                    return False

            return True

        if condition_line:
            return self._check_condition(
                condition=condition_line,
                variables=variables,
            )

        return True

    @staticmethod
    def _check_condition(
        condition: str,
        variables: Dict[str, JSON],
    ) -> bool:

        if condition[0] != "{" and condition[-1] != "}":
            raise ConditionMustBeAVariable(condition_text=condition)

        condition_variable = condition[1:-1]
        return bool(variables[condition_variable])
