import typing
from dataclasses import Field, fields, is_dataclass
from inspect import isclass
from typing import Any, Callable, Dict, List, Optional

from risc_lasagna.helper import is_list, is_optional, print_dict_like_list
from risc_lasagna.layer.dict_layer import DictLayer
from risc_lasagna.layer.typedef import Layer
from risc_lasagna.typedef import ConfigurationError


class DataClassDefaultLayer(Layer):
    """
    This class maps a dataclass instance to an environment. Variables are expanded in a dict and can
    be accessed by a key.
    Examples:
    """

    def __init__(
        self,
        dataclass_instance,
        variable_prefix: str = None,
        capitalize_keys: bool = True,
        dict_layer_generator: Callable[
            [Dict[str, Any]], DictLayer
        ] = lambda map_: DictLayer(map_),
    ):
        if not is_dataclass(dataclass_instance) or isclass(dataclass_instance):
            raise ConfigurationError from ValueError(
                "Variable dataclass must be a valid instance of a dataclass!"
            )

        # translate db result to dataclass names. The resulting dict is then used in the constructor
        _map = DataClassDefaultLayer.__build_map_from(
            dataclass_instance, variable_prefix, capitalize_keys
        )
        self._dict_layer = dict_layer_generator(_map)

    def get(self, key):
        return self._dict_layer.get(key)

    def keys(self) -> List[str]:
        return self._dict_layer.keys()

    def contains(self, key: str) -> bool:
        return self._dict_layer.contains(key)

    def __repr__(self):
        return print_dict_like_list(self._dict_layer.map, title=self.__class__.__name__)

    @staticmethod
    def __build_with_prefix(
        key: str, variable_prefix: Optional[str], capitalize_keys: bool
    ):
        if variable_prefix is not None:
            key = f"{DataClassDefaultLayer.__normalize_prefix(variable_prefix)}{key}"

        return key.upper() if capitalize_keys else key

    @staticmethod
    def __normalize_prefix(variable_prefix: Optional[str]) -> str:
        if variable_prefix is None:
            return ""
        return (
            variable_prefix if variable_prefix.endswith("_") else f"{variable_prefix}_"
        )

    @staticmethod
    def __build_map_from(
        dataclass_instance, variable_prefix: Optional[str], capitalize_keys: bool
    ) -> Dict[str, Any]:
        field: Field
        mapped_and_flattened_dict = {}
        for field in fields(dataclass_instance):
            type_ = field.type
            if is_optional(type_) or is_list(type_):
                type_ = typing.get_args(type_)[0]

            if is_dataclass(type_):
                # recursively parse the inner dataclass, flattens it out
                mapped_and_flattened_dict = {
                    **mapped_and_flattened_dict,
                    **DataClassDefaultLayer.__build_map_from(
                        getattr(dataclass_instance, field.name),
                        f"{DataClassDefaultLayer.__normalize_prefix(variable_prefix)}{field.name}",
                        capitalize_keys,
                    ),
                }
                continue
            mapped_and_flattened_dict[
                DataClassDefaultLayer.__build_with_prefix(
                    field.name, variable_prefix, capitalize_keys
                )
            ] = getattr(dataclass_instance, field.name)
        return mapped_and_flattened_dict
