import abc
import json

from .core import Group, Primitive


class VarsReader(abc.ABC):
    """Interface to access to variables in a store"""

    @abc.abstractmethod
    def list_vars(self) -> list[str]:
        """List all available keys in the store"""

    @abc.abstractmethod
    def read(self, key: str) -> str:
        """Return the value of a key, it doesn't exist return None"""


class VariableNotFound(Exception):
    def __init__(self, key: str):
        self.key = key
        super().__init__(f'Variable with name "{key}" not found in environment')


class ConfigService:
    def __init__(self, app_name: str, *readers: VarsReader):
        self._readers = readers
        self._env = ""
        self._app = app_name

    def _list_family(self, key: str, reader: VarsReader) -> list[str]:
        vars = reader.list_vars()
        if key in vars:
            return [key]

        return [var for var in vars if var.startswith(key + ".")]

    def get_primitive(self, key: str) -> Primitive:
        """Return value of a key, None it there is not content"""
        var = None
        for reader in self._readers:
            var = reader.read(f"{self._app}.{key}")
            if var:
                break
            var = reader.read(key)
            if var:
                break

        if var is None or var == "":
            raise VariableNotFound(key)

        return json.loads(var)

    def get_group(self, key: str) -> Group:
        """Return a group of variables merged by all readers"""
        group = Group()
        for reader in reversed(self._readers):
            group.update(self._load_group(key, reader))
            group.update(self._load_group(f"{self._app}.{key}", reader))
        return group

    def _load_group(self, key: str, reader: VarsReader) -> Group:
        var = reader.read(key)
        if var is None or var == "":
            return Group()

        g = json.loads(var)
        if type(g) is not dict:
            raise TypeError(f"Content {g} from {key} should be a dictionary")
        return Group(g)
