import os
import re
import sys
import traceback
from collections.abc import Callable
from pathlib import Path
from types import ModuleType

from ._imports import dotenv


def patch_pypath(wd: Path | None = None):
    sys.path.insert(0, str(wd.resolve()) if wd else '')


def get_import_components(path: str) -> list[str | None]:
    return (re.split(r':(?![\\/])', path, maxsplit=1) + [None])[:2]


def prepare_import(path: str) -> str:
    path = os.path.realpath(path)

    fname, ext = os.path.splitext(path)
    if ext == '.py':
        path = fname
    if os.path.basename(path) == '__init__':
        path = os.path.dirname(path)

    module_name = []

    #: move up untile outside package
    while True:
        path, name = os.path.split(path)
        module_name.append(name)

        if not os.path.exists(os.path.join(path, '__init__.py')):
            break

    if sys.path[0] != path:
        sys.path.insert(0, path)

    return '.'.join(module_name[::-1])


def load_module(module_name: str, raise_on_failure: bool = True) -> ModuleType | None:
    try:
        __import__(module_name)
    except ImportError:
        if sys.exc_info()[-1].tb_next:
            raise RuntimeError(
                f"While importing '{module_name}', an ImportError was raised:\n\n{traceback.format_exc()}"
            )
        elif raise_on_failure:
            raise RuntimeError(f"Could not import '{module_name}'.")
        else:
            return
    return sys.modules[module_name]


def load_target(target: str, wd: Path | None = None, factory: bool = False) -> Callable[..., None]:
    patch_pypath(wd)
    path, name = get_import_components(target)
    path = prepare_import(path) if path else None
    name = name or 'app'
    module = load_module(path)
    rv = module
    for element in name.split('.'):
        rv = getattr(rv, element)
    if factory:
        rv = rv()
    return rv


def load_env(files):
    for env_file in files:
        dotenv.load_dotenv(dotenv_path=env_file, override=False)


def build_env_loader():
    env = set(os.environ.keys())

    def loader(files):
        for env_file in files:
            values = dotenv.dotenv_values(dotenv_path=env_file)
            for key in set(values.keys()) - env:
                val = values[key]
                if val is not None:
                    os.environ[key] = val

    return loader
