import importlib
import inspect
import os
import pkgutil
import sys
from abc import ABC
from logging import Logger, getLogger
from types import ModuleType
from typing import Union, Type, Callable, Any, Optional


def get_method_by_name(element: Union[Type, object], name: str):
    for method_name, method_obj in inspect.getmembers(element):
        if (
                (inspect.isfunction(method_obj) or inspect.ismethod(method_obj))
                and method_name == name
        ):
            return method_obj
    return None


def inherit_from(element: Union[Type, object], parent_type: Type):
    return (
            isinstance(element, parent_type)
            or (isinstance(element, type) and parent_type in element.__bases__)
    )


def load_module_then_execute(
        load_module: ModuleType,
        callback: Callable[[ModuleType, ], Any],
        logger: Optional[Logger] = None,
        recursive: Optional[bool] = True
):
    if recursive:
        logger = logger or getLogger()
        package_dir = os.path.dirname(load_module.__file__)

        for (_, module_name, _) in pkgutil.iter_modules([package_dir]):
            if os.path.exists(os.path.join(package_dir, module_name, '__init__.py')):
                full_package_name = f"{load_module.__name__}.{module_name}"

                if full_package_name not in sys.modules:
                    logger.debug(f"Load extra module {full_package_name}")
                    module = importlib.import_module(full_package_name)
                else:
                    logger.debug(f"Using loaded module {full_package_name}")
                    module = sys.modules[full_package_name]

                load_module_then_execute(module, callback, logger)

    callback(load_module)


def get_class_parent_types(dep_class: Type):
    return [child for child in dep_class.__bases__ if child not in [ABC, object]]
