import inspect
import os
from abc import ABC
from typing import Union, Any, Dict
from deep_utils.utils.lib_utils.main_utils import import_module
from deep_utils.utils.os_utils import split_all


class MainClass(ABC):
    def __init__(self, name, file_path, **kwargs):
        self.config = None
        self.model = None
        self.name = name
        self.download_variables: Union[tuple, None] = kwargs.get('download_variables', None)
        self.load_config(file_path, **kwargs)
        self.load_model()

    def __repr__(self):
        message = f"{self.name} config attributes:"
        for name, val in inspect.getmembers(self.config):

            if not name.startswith('_'):
                if not inspect.ismethod(val):
                    message += f"\n{name} = {val}"
        return message

    def load_model(self):
        raise NotImplementedError('load_model is not implemented')

    def load_config(self, file_path, **kwargs):
        separated_files = os.path.join('deep_utils', os.path.split(file_path)[0].split('deep_utils')[-1][1:])
        file_ = '.'.join(split_all(separated_files) + ["config"])
        config = import_module(file_, 'Config')
        self.config = config()
        self.update_config(**kwargs)

    def __dict_update(self, out_dict: Dict[str, Any], in_dict: Dict[str, Any]):
        for arg, val in in_dict.items():
            if arg in out_dict:
                source = out_dict[arg]
                if isinstance(val, dict) and isinstance(source, dict):
                    target = self.__dict_update(source, val)
                    out_dict[arg] = target
                else:
                    out_dict[arg] = val
            else:
                out_dict[arg] = val

        return out_dict

    def update_config(self, **kwargs):
        for arg, val in kwargs.items():
            if hasattr(self.config, arg):
                if val is None:
                    continue
                source = getattr(self.config, arg)
                if isinstance(val, dict) and isinstance(source, dict):
                    target = self.__dict_update(source, val)
                    setattr(self.config, arg, target)
                else:
                    setattr(self.config, arg, val)
            else:
                raise ValueError(f"[ERROR] the config file does not contain argument: {arg}")
