import logging
import os
import sys
from configparser import ConfigParser
from importlib import import_module
from inspect import isabstract, isclass
from pathlib import Path
from pkgutil import iter_modules

from .services import ServiceBase


class SparcClient(object):
    """
    The main class of the sparc.client library.

    This class is used to connect existing modules located in <projectbase>/services folder


    Attributes
    ----------
    config_file : str
        The location of the file in INI format that is used to extract configuration variables.
        The config file needs to define a [global] section with the name of the default profile
        (in square brackets as well) which holds environmental variables used by the modules.
        Refer to configparser for further details.
    connect : bool (True)
        Calls connect() method of each of the modules.
        By default during initialization all modules are initialized and ready to be used,
        unless connect is set to False.


    Attributes
    ----------
    module_names : list
        Stores the list of modules that are automatically loaded from the <projectbase>/services directory.

    Methods
    -------
    add_module(path, config, connect):
        Adds and optionally connects to a module in a given path with configuration variables defined in config.
    connect()
        Connects all the modules by calling their connect() functions.
    """

    module_names = []

    def __init__(self, config_file="config/config.ini", connect=True) -> None:
        # Read config file
        if not config_file:
            raise RuntimeError("Configuration file not given")

        config = ConfigParser()

        if os.path.isfile(config_file):
            config.read(config_file)
            logging.debug(str(config))
        current_config = config["global"]["default_profile"]

        logging.debug("Using the following config:")
        logging.debug(str(config[current_config]))

        # iterate through the modules in the current package
        package_dir = os.path.join(Path(__file__).resolve().parent, "services")

        for _, module_name, _ in iter_modules([package_dir]):
            # import the module and iterate through its attributes
            self.add_module(
                f"{__package__}.services.{module_name}", config[current_config], connect
            )

    def add_module(self, path, config, connect=True):
        """Adds and optionally connects to a module in a given path with configuration variables defined in config.

        Parameters
        ----------
        path : str
            a path to the module
        config : dict or configparser.SectionProxy
            a dictionary (or Section of the config file parsed by ConfigParser) with the configuration variables
        connect : bool
            determines if the module should auto-connect
        """
        module_name = path.split(".")[-1]
        try:
            module = import_module(path)
        except ImportError:
            logging.debug("Skipping module. Failed to import from %s", f"{path=}", exc_info=True)
        else:
            for attribute_name in dir(module):
                attribute = getattr(module, attribute_name)
                if (
                    isclass(attribute)
                    and issubclass(attribute, ServiceBase)
                    and not isabstract(attribute)
                ):
                    # Add the class to this package's variables
                    self.module_names.append(module_name)
                    c = attribute(connect=False, config=config)
                    setattr(self, module_name, c)
                    if connect:
                        c.connect()

    def connect(self):
        """Connects each of the modules loaded into self.module_names"""
        for module_name in self.module_names:
            getattr(self, module_name).connect()
