"""Handle application configuration."""

from __future__ import annotations

import atexit
import collections
import importlib.resources
import logging
import pathlib

import ruamel.yaml

from mariner.utils import path

Value = str | int | dict | list


class Config(collections.abc.MutableMapping):
    """Class to hold config settings using yaml. Behaves like a dictionary."""

    default_directory = "~/.config/mariner/"
    default_config = importlib.resources.files("mariner") / "config/config.yaml"
    log = logging.getLogger(__name__)

    def __init__(
        self,
        configpath: str | None = None,
        _parent: Config | None = None,
        _config: dict | None = None,
    ) -> None:
        self._configpath = configpath
        self._yaml = ruamel.yaml.YAML()
        self._parent = _parent

        if not self._parent:
            self._config: dict = self.load()
            atexit.register(self.save)
        else:
            self._config = _config or {}

    @property
    def configpath(self) -> pathlib.Path:
        """Create configuration file if necessary and return the path.

        Returns:
            Path to configuration file.

        """
        if self._configpath:
            configpath = pathlib.Path(self._configpath)
        else:
            directory = path.config()
            directory = path.exists_or_create(directory)
            configpath = pathlib.Path(directory, "config.yaml")
        self.log.debug("path=%s", path)
        return configpath

    def load(self) -> dict:
        """Load configuration saved in given path.

        Returns:
            Dictionary of configuration values.

        """
        try:
            raw_config = self.configpath.read_text()
        except FileNotFoundError:
            raw_config = self.default_config.read_text(encoding="utf-8")
        return self._yaml.load(raw_config)

    def save(self) -> None:
        """Save the configuration."""
        if self._parent:
            self._parent.save()
        else:
            with self.configpath.open("w", encoding="utf-8") as file_:
                self.log.debug("configpath=%s file=%s", self.configpath, file_)
                self._yaml.dump(self._config, file_)

    def _as_config(
        self,
        dict_: str | int | list | dict,
    ) -> str | int | list | Config:
        """Save config inside config.

        Args:
          dict_: Piece of configuration.

        Returns:
            Configuration as a dictionary.

        """
        if isinstance(dict_, dict):
            return Config(_parent=self, _config=dict_)
        return dict_

    def __getitem__(self, item: str) -> Value:
        if item not in self._config:
            raise KeyError(item)
        return self._config[item]

    def __setitem__(self, key: str, value: Value) -> None:
        self._config[key] = self._as_config(value)
        self.save()

    def __getattr__(self, attr):
        return self.__getitem__(attr)

    def __setattr__(self, attr: str, value: Value) -> None:
        if attr.startswith("_"):
            self.__dict__[attr] = value
        else:
            self.__setitem__(attr, value)

    def __delitem__(self, key: str) -> None:
        del self._config[key]

    def __iter__(self):
        yield from self._config

    def __len__(self) -> int:
        return len(self._config)

    def __repr__(self) -> str:
        return repr(self._config)
