from __future__ import annotations

import importlib
import inspect
from typing import Any, Callable


def load_callable(spec: str) -> Callable[[], Any]:
    """Load a zero-arg callable from 'module.sub:callable_name'.

    Using a string spec keeps multiprocessing(spawn) reliable: workers import the
    factory instead of trying to pickle a function.
    """

    if ":" not in spec:
        raise ValueError("session factory must be in the form 'module.sub:callable'")

    module_name, attr_name = spec.split(":", 1)
    if not module_name or not attr_name:
        raise ValueError("invalid session factory spec")

    module = importlib.import_module(module_name)
    factory = getattr(module, attr_name)
    if not callable(factory):
        raise TypeError(f"{spec} is not callable")

    return factory


def call_factory(factory: Callable[..., Any], kwargs: dict[str, Any]) -> Any:
    """Call a session factory with optional keyword arguments.

    This keeps compatibility with user factories that are defined as:
        def make_session(): ...
    while allowing richer factories:
        def make_session(*, pooling_on: bool = False, max_pool_size: int | None = None): ...
    """

    if not kwargs:
        return factory()

    try:
        sig = inspect.signature(factory)
        params = sig.parameters.values()
        accepts_var_kw = any(p.kind == inspect.Parameter.VAR_KEYWORD for p in params)
        kw_names = {
            p.name
            for p in params
            if p.kind in (inspect.Parameter.POSITIONAL_OR_KEYWORD, inspect.Parameter.KEYWORD_ONLY)
        }
        if accepts_var_kw:
            return factory(**kwargs)

        filtered = {k: v for k, v in kwargs.items() if k in kw_names}
        if not filtered:
            return factory()

        return factory(**filtered)

    except Exception:
        # Signature introspection can fail for some callables.
        try:
            return factory(**kwargs)
        except TypeError:
            return factory()
