import os
import traceback
from abc import abstractmethod, ABC
from pathlib import Path
from threading import Thread
from typing import Optional

import yaml

from bauh.api.paths import CONFIG_DIR
from bauh.commons import util


def read_config(file_path: str, template: dict, update_file: bool = False, update_async: bool = False) -> dict:
    if not os.path.exists(file_path):
        Path(CONFIG_DIR).mkdir(parents=True, exist_ok=True)
        save_config(template, file_path)
    else:
        with open(file_path) as f:
            local_config = yaml.safe_load(f.read())

        if local_config:
            util.deep_update(template, local_config)

        if update_file:
            if update_async:
                Thread(target=save_config, args=(template, file_path), daemon=True).start()
            else:
                save_config(template, file_path)

    return template


def save_config(config: dict, file_path: str):
    with open(file_path, 'w+') as f:
        f.write(yaml.dump(config))


class ConfigManager(ABC):

    @abstractmethod
    def read_config(self) -> Optional[dict]:
        pass

    @abstractmethod
    def get_default_config(self) -> dict:
        pass

    @abstractmethod
    def is_config_cached(self) -> bool:
        pass

    def get_config(self) -> dict:
        default_config = self.get_default_config()

        if default_config:
            cached_config = self.read_config()

            if cached_config:
                self.merge_config(default_config, cached_config)

        return default_config

    @staticmethod
    def merge_config(base_config: dict, current_config: dict):
        util.deep_update(base_config, current_config)

    @abstractmethod
    def save_config(self, config_obj: dict):
        pass


class YAMLConfigManager(ConfigManager, ABC):

    def __init__(self, config_file_path: str):
        self.file_path = config_file_path

    def is_config_cached(self) -> bool:
        return os.path.exists(self.file_path)

    def read_config(self) -> Optional[dict]:
        if self.is_config_cached():
            with open(self.file_path) as f:
                local_config = yaml.safe_load(f.read())

            if local_config is not None:
                return local_config

    def save_config(self, config_obj: dict):
        if config_obj:
            config_dir = os.path.dirname(self.file_path)
            try:
                Path(config_dir).mkdir(parents=True, exist_ok=True)
            except OSError:
                traceback.print_exc()
                return

            try:
                with open(self.file_path, 'w+') as f:
                    f.write(yaml.dump(config_obj))
            except:
                traceback.print_exc()
