import logging
import os
from collections.abc import MutableMapping
from pathlib import Path

import yaml

from risc_lasagna.layer.dict_layer import DictLayer
from risc_lasagna.typedef import ConfigurationError

logger = logging.getLogger(__name__)


class _YamlLayer(DictLayer):
    pass


def YamlLayer(
    file: Path,
    raise_exception_if_file_not_readable: bool = False,
    variable_prefix: str = "",
) -> _YamlLayer:
    """
    Creates the YAML Layer from a given file
    :param file: Path to file
    :param raise_exception_if_file_not_readable: If the layer should raise an exception if file can not be read
    or act as an 'empty' layer
    :param variable_prefix: Prefix which is prepended before every key
    """
    if raise_exception_if_file_not_readable and not os.access(file, os.R_OK):
        raise ConfigurationError from ValueError(
            f"File {file} is not readable or does not exist!"
        )

    map_ = {}
    try:
        with open(file, "r") as f:
            # open and parse the file, ignore comment lines (starting with comment char)
            parsed = yaml.safe_load(f)
            map_ = _flatten(parsed)
    except (IOError, yaml.YAMLError) as e:
        logger.error(f"Error reading yml file {file}", exc_info=e)
        if raise_exception_if_file_not_readable:
            raise ConfigurationError from e

    # yaml layer are never capitalized because yaml is case sensitive by default
    return _YamlLayer(map_, capitalize_keys=False, variable_prefix=variable_prefix)


def _flatten(d, parent_key="", sep="_"):
    items = []
    for key, value in d.items():
        # define new key
        new_key = f"{parent_key}{sep}{key}" if parent_key else key

        if isinstance(value, MutableMapping):
            # recursively parse sub dict
            items.extend(_flatten(value, new_key, sep=sep).items())
        elif isinstance(value, list):
            # parse lists and their sub elements
            values = []
            for element in value:
                # parse sub dicts and flatten them
                if isinstance(element, MutableMapping):
                    element = _flatten(element, "", sep=sep)

                # add normal values back to list
                values.append(element)

            # add simple lists (e.g. no objects) to key
            if len(values) > 0:
                items.append((new_key, values))
        else:
            # case for simple key
            items.append((new_key, value))
    return dict(items)
