from __future__ import annotations

import inspect
import sys

from importlib import reload as import_reload
from importlib.util import spec_from_loader, module_from_spec
from importlib.machinery import SourceFileLoader

from pathlib import Path


def import_(path: str | Path, reload: bool = False):
    """
    Import file from random destination as module
    """
    path = Path(path)
    name = path.stem
    if not path.exists():
        raise FileNotFoundError('File %s not found' % str(path))

    loader = SourceFileLoader(name, str(path))

    spec = spec_from_loader(loader.name, loader)
    if spec is not None:
        mod = module_from_spec(spec)

        if name in sys.modules and reload:
            if reload:
                import_reload(mod)
            return sys.modules[name]
        else:
            loader.exec_module(mod)

        sys.modules[name] = mod

        return mod

    else:
        raise ImportError(f'%s not loaded from %s' % (str(name), str(path)))


def register_mixin(obj, cls):
    _obj_cls = obj.__class__
    _obj_cls_name = obj.__class__.__name__
    obj.__class__ = type(_obj_cls_name, (_obj_cls, cls), {})


def reload_(filename: str | Path):
    return import_(filename, reload=True)


def find_models(modules):
    if isinstance(modules, (str, Path)):
        modules = import_(modules)

    _models = []
    if modules is None:
        return _models

    try:
        _classes = sys.modules[modules.__name__]
        n = modules.__name__
    except AttributeError:
        return _models

    for name, obj in inspect.getmembers(_classes, inspect.isclass):
        # only import classes defined in file
        if obj.__module__.startswith(n) and name[0] != '_':
            _models.append(obj)

    return _models
