import abc
from typing import Literal


class Var(abc.ABC):
    @abc.abstractmethod
    def get(self) -> str | dict[str, dict] | None:
        ...

    @abc.abstractmethod
    def merge(self, other: "Var") -> "Var":
        ...


class SingleVar(Var):
    def __init__(self, value: str | None = None):
        self._value = value

    def get(self) -> str | None:
        return self._value

    def merge(self, other: Var) -> Var:
        if other.get() is None:
            return self

        return other


class VarGroup(Var):
    """Construct a variable group"""

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

    def get(self) -> dict[str, dict]:
        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).get()
                key = items[0]
                vars = []

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

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

        return content

    def merge(self, other: Var) -> Var:
        if other.get() is None:
            return self

        if not isinstance(other, VarGroup):
            return other

        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()])


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 ConfigService:
    def __init__(
            self,  prod_reader: VarsReader,  pre_reader: VarsReader,devel_reader: VarsReader | None = None
    ):
        self._devel = devel_reader
        self._pre = pre_reader
        self._prod = prod_reader
        self._env = ""
        self._app = ""

    def set_environment(self, value: Literal["devel", "pre", "prod"]):
        """Set environment"""
        self._env = value

    def set_application(self, name: str):
        """Set application name to merge app var with general"""
        self._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) -> Var:
        """Return value of a key, None it there is not content"""
        app_var = SingleVar()
        if self._app and not key.startswith(self._app):
            app_var = self._get(f"{self._app}.{key}", reader)

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

        if len(family) == 1 and family[0] == key:
            return SingleVar(reader.read(key)).merge(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(self, key: str) -> str | dict | None:
        """Return value of a key, None it there is not content"""
        prod_vars = self._get(key, self._prod)
        match self._env:
            case "devel":
                if not self._devel:
                    return
                devel_vars = self._get(key, self._devel)
                if prod_vars.get() is not None and devel_vars.get() is None:
                    raise ValueError(f'Variable "{key}" should be defined in devel')
                return devel_vars.get()
            case "pre":
                pre_vars = self._get(key, self._pre)
                if prod_vars and pre_vars:
                    return prod_vars.merge(pre_vars).get()

        return prod_vars.get()
