import abc


class VarGroup:
    """Construct a variable group"""

    def __init__(self, values: list[tuple[str, str]]):
        self._vars = sorted(values, key=lambda val: val[0])

    def as_dict(self) -> dict[str, dict | str]:
        content = {}
        key = None
        vars = []
        for val in self._vars:
            items = val[0].split(".")
            if len(items) == 1:
                content[items[0]] = val[1]
                continue

            if items[0] != key:
                if vars:
                    content[key] = VarGroup(vars).as_dict()
                key = items[0]
                vars = []

            vars.append((".".join(items[1:]), val[1]))

        if vars:
            content[key] = VarGroup(vars).as_dict()

        return content

    def merge(self, other: "VarGroup") -> "VarGroup":
        vars = {val[0]: val[1] for val in self._vars}
        for var in other._vars:
            vars[var[0]] = var[1]

        return VarGroup([(key, val) for key, val in vars.items()])

    def __bool__(self) -> bool:
        return bool(self._vars)


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 | None:
        """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(self, key: str, reader: VarsReader) -> str | None:
        app_var = None
        if self._app and not key.startswith(self._app):
            app_var = self._get(f"{self._app}.{key}", reader)
            if app_var:
                return app_var

        return reader.read(key)

    def _get_group(self, key: str, reader: VarsReader) -> VarGroup:
        """Return value of a key, None it there is not content"""

        app_var = VarGroup([])
        if self._app and not key.startswith(self._app):
            app_var = self._get_group(f"{self._app}.{key}", reader)

        family = self._list_family(key, reader)
        if not family:
            return app_var

        names = [var[len(key) + 1 :] for var in family]
        values = [reader.read(var) for var in family]
        group = VarGroup(
            [(name, value) for name, value in zip(names, values) if value is not None]
        )
        return group.merge(app_var)

    def get_group(self, key: str) -> dict[str, dict | str]:
        """Return a group of variables as a dict"""
        group = VarGroup([])
        for reader in reversed(self._readers):
            group = group.merge(self._get_group(key, reader))

        return group.as_dict()

    def get(self, key: str, default: str | None = None) -> str:
        """Return value of a key, None it there is not content"""
        for reader in self._readers:
            var = self._get(f"{self._app}.{key}", reader)
            if var:
                return var
            var = self._get(key, reader)
            if var:
                return var
        if default is not None:
            return default

        raise VariableNotFound(key)
