import inspect
from logging import Logger
from types import ModuleType
from typing import Callable, List, Optional, Type

from ._DependencyContext import DependencyContext
from ._DependencyContextImpl import DependencyContextImpl
from ._DependencyMap import DependencyMap
from ._DependencyMapAdapterBase import DependencyMapAdapterBase
from ._utils import DEFAULT_LOGGER, load_module_then_execute


class DependencyLoader(DependencyMapAdapterBase):
    """Discovers and loads dependency classes from Python modules.

    This class scans specified modules for classes and registers them in an internal
    dependency map. It can then be used to build a `DependencyContext` for resolving
    these dependencies at runtime.
    """

    def __init__(self, logger: Optional[Logger] = None):
        """Initializes the DependencyLoader.

        Args:
            logger (Optional[Logger]): A logger instance. If not provided, a default logger is used.
        """
        super().__init__(DependencyMap())
        self.logger = logger or DEFAULT_LOGGER

    def load_modules(
        self,
        modules: List[ModuleType],
        recursive: Optional[bool] = True,
        class_filter: Optional[Callable[[Type, ], bool]] = None,
    ):
        """Loads dependency classes from a list of modules.

        This method iterates through the provided modules and scans them for classes
        to register as dependencies.

        Args:
            modules (List[ModuleType]): A list of Python modules to scan.
            recursive (Optional[bool]): If True, scans sub-packages within the modules recursively. Defaults to True.
            class_filter (Optional[Callable[[Type, ], bool]]): A callable that receives a class type and returns True if it should be registered.
        """
        for module in modules:
            load_module_then_execute(
                load_module=module,
                callback=lambda m: self._load_dependencies_from_module(
                    load_module=m,
                    class_filter=class_filter
                ),
                recursive=recursive
            )

    def build_context(self) -> DependencyContext:
        """Builds a new DependencyContext from the loaded dependencies.

        Returns:
            DependencyContext: A new context instance with a clone of the current dependency map.
        """
        return DependencyContextImpl(
            dependency_map=self.dependency_map.clone(),
            logger=self.logger,
        )

    def _load_dependencies_from_module(
        self,
        load_module: ModuleType,
        class_filter: Optional[Callable[[Type, ], bool]] = None,
    ):
        """Scans a single module and registers its classes as dependencies.

        Args:
            load_module (ModuleType): The module to inspect.
            class_filter (Optional[Callable[[Type, ], bool]]): A filter to apply to discovered classes.
        """
        for _, member_obj in inspect.getmembers(load_module):
            if inspect.isclass(member_obj):
                if class_filter is None or class_filter(member_obj):
                    self.add_dependency_class(member_obj)
