# Copyright 2022-2026 Broadcom.
# SPDX-License-Identifier: Apache-2.0
"""
This code is run when initializing the python interperter in a Relenv environment.

- Point Relenv's Openssl to the system installed Openssl certificate path
- Make sure pip creates scripts with a shebang that points to the correct
  python using a relative path.
- On linux, provide pip with the proper location of the Relenv toolchain
  gcc. This ensures when using pip any c dependencies are compiled against the
  proper glibc version.
"""

from __future__ import annotations

import contextlib
import ctypes as _ctypes
import functools
import importlib as _importlib
import inspect
import json as _json
import os
import pathlib
import shutil as _shutil
import site as _site
import subprocess as _subprocess
import sys as _sys
import textwrap
import warnings as _warnings
from typing import TYPE_CHECKING, Any, cast

if TYPE_CHECKING:
    from collections.abc import Callable, Iterable, Iterator, Sequence
    from importlib.machinery import ModuleSpec
    from types import ModuleType

# The tests monkeypatch these module-level imports (e.g., json.loads) inside
# relenv.runtime itself; keeping them as Any both preserves test isolation—no
# need to patch the global stdlib modules—and avoids mypy attr-defined noise
# while still exercising the real runtime wiring.
json = cast("Any", _json)
importlib = cast("Any", _importlib)
site = cast("Any", _site)
subprocess = cast("Any", _subprocess)
sys = cast("Any", _sys)
ctypes = cast("Any", _ctypes)
shutil = cast("Any", _shutil)
warnings = cast("Any", _warnings)

__all__ = [
    "sys",
    "shutil",
    "subprocess",
    "json",
    "importlib",
    "site",
    "ctypes",
    "warnings",
]

PathType = str | os.PathLike[str]
ConfigVars = dict[str, str]

# relenv.pth has a __file__ which is set to the path to site.py of the python
# interpreter being used. We're using that to determine the proper
# relenv.runtime to import. Working around the rest of the import mechanisims.
# Import any other needed modules from this same relenv. This prevents pulling
# in a relenv from some other location in the path and is needed because these
# imports happen before our path munghing in site in wrapsitecustomize.


def path_import(name: str, path: PathType) -> ModuleType:
    """
    Import module from a path.

    This causes hashlib to be imported because of importing importlib.util so
    it can not be used until after openssl has been configured.
    """
    import importlib.util

    spec = importlib.util.spec_from_file_location(name, path)
    if spec is None or spec.loader is None:
        raise ImportError(f"Unable to load module {name} from {path}")
    module = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(module)
    sys.modules[name] = module
    return module


_COMMON: ModuleType | None = None
_RELOCATE: ModuleType | None = None
_BUILDENV: ModuleType | None = None


def common() -> ModuleType:
    """Return the cached ``relenv.common`` module."""
    global _COMMON
    if _COMMON is None:
        _COMMON = path_import("relenv.common", str(pathlib.Path(__file__).parent / "common.py"))
    return _COMMON


def relocate() -> ModuleType:
    """Return the cached ``relenv.relocate`` module."""
    global _RELOCATE
    if _RELOCATE is None:
        _RELOCATE = path_import("relenv.relocate", str(pathlib.Path(__file__).parent / "relocate.py"))
    return _RELOCATE


def buildenv() -> ModuleType:
    """Return the cached ``relenv.buildenv`` module."""
    global _BUILDENV
    if _BUILDENV is None:
        _BUILDENV = path_import("relenv.buildenv", str(pathlib.Path(__file__).parent / "buildenv.py"))
    return _BUILDENV


def get_major_version() -> str:
    """
    Current python major version.
    """
    return "{}.{}".format(*sys.version_info)


@contextlib.contextmanager
def pushd(new_dir: PathType) -> Iterator[None]:
    """
    Changedir context.
    """
    old_dir = os.getcwd()
    os.chdir(new_dir)
    try:
        yield
    finally:
        os.chdir(old_dir)


def debug(string: str) -> None:
    """
    Prints the provided message if RELENV_DEBUG is truthy in the environment.

    Writes to stderr so this output does not contaminate stdout when relenv
    Python is invoked as a helper that emits structured data (JSON/TOML)
    on stdout — tools like ``maturin pep517 write-dist-info`` and several
    pyo3 / rust-openssl-sys build scripts parse the first line of stdout
    and fail with "expected value at line 1 column 1" otherwise.

    :param string: The message to print
    :type string: str
    """
    if os.environ.get("RELENV_DEBUG"):
        print(string, file=sys.stderr)
        sys.stderr.flush()


def relenv_root() -> pathlib.Path:
    """
    Return the relenv module root.
    """
    MODULE_DIR = pathlib.Path(__file__).resolve().parent
    # XXX Look for rootdir / ".relenv"
    if sys.platform == "win32":
        # /Lib/site-packages/relenv/
        return MODULE_DIR.parent.parent.parent
    # /lib/pythonX.X/site-packages/relenv/
    return MODULE_DIR.parent.parent.parent.parent


def _build_shebang(func: Callable[..., bytes], *args: Any, **kwargs: Any) -> Callable[..., bytes]:
    """
    Build a shebang to point to the proper location.

    :return: The shebang
    :rtype: bytes
    """

    @functools.wraps(func)
    def wrapped(self: Any, *args: Any, **kwargs: Any) -> bytes:
        scripts = pathlib.Path(self.target_dir)
        if TARGET.TARGET:
            scripts = pathlib.Path(_ensure_target_path()).absolute() / "bin"
        try:
            interpreter = common().relative_interpreter(sys.RELENV, scripts, pathlib.Path(sys.executable).resolve())
        except ValueError:
            debug(f"Relenv Value Error - _build_shebang {self.target_dir}")
            original_result: bytes = func(self, *args, **kwargs)
            return original_result
        debug(f"Relenv - _build_shebang {scripts} {interpreter}")
        if sys.platform == "win32":
            return str(pathlib.Path("#!<launcher_dir>") / interpreter).encode() + b"\r\n"
        rel_path = str(pathlib.PurePosixPath("/") / interpreter)
        formatted = cast("str", common().format_shebang(rel_path))
        return formatted.encode()

    return wrapped


def get_config_var_wrapper(func: Callable[[str], Any]) -> Callable[[str], Any]:
    """
    Return a wrapper to resolve paths relative to the relenv root.
    """

    @functools.wraps(func)
    def wrapped(name: str) -> Any:
        if name == "BINDIR":
            orig = func(name)
            if os.environ.get("RELENV_PIP_DIR"):
                val = relenv_root()
            else:
                val = relenv_root() / "Scripts"
            debug(f"get_config_var call {name} old: {orig} new: {val}")
            return val
        else:
            val = func(name)
            debug(f"get_config_var call {name} {val}")
            return val

    return wrapped


CONFIG_VARS_DEFAULTS: ConfigVars = {
    "AR": "ar",
    "CC": "gcc",
    "CFLAGS": "-Wno-unused-result -Wsign-compare -DNDEBUG -g -fwrapv -O3 -Wall",
    "CPPFLAGS": "-I. -I./Include",
    "CXX": "g++",
    "LIBDEST": "/usr/local/lib/python3.10",
    "SCRIPTDIR": "/usr/local/lib",
    "BLDSHARED": "gcc -shared",
    "LDFLAGS": "",
    "LDCXXSHARED": "g++ -shared",
    "LDSHARED": "gcc -shared",
}

_SYSTEM_CONFIG_VARS: ConfigVars | None = None


def system_sysconfig() -> ConfigVars:
    """
    Read the system python's sysconfig values.

    Th system python isthe one installed by your package manager. Memoize them
    to avoid the overhead of shelling out.
    """
    global _SYSTEM_CONFIG_VARS
    if _SYSTEM_CONFIG_VARS is not None:
        return _SYSTEM_CONFIG_VARS
    pyexec = pathlib.Path("/usr/bin/python3")
    if pyexec.exists():
        p = subprocess.run(
            [
                str(pyexec),
                "-c",
                "import json, sysconfig; print(json.dumps(sysconfig.get_config_vars()))",
            ],
            capture_output=True,
        )
        try:
            _SYSTEM_CONFIG_VARS = json.loads(p.stdout.strip())
        except json.JSONDecodeError:
            debug(f"Failed to load JSON from: {p.stdout.strip()}")
            _SYSTEM_CONFIG_VARS = CONFIG_VARS_DEFAULTS
    else:
        debug("System python not found")
        _SYSTEM_CONFIG_VARS = CONFIG_VARS_DEFAULTS
    return _SYSTEM_CONFIG_VARS


def get_config_vars_wrapper(func: Callable[..., ConfigVars], mod: ModuleType) -> Callable[..., ConfigVars]:
    """
    Return a wrapper to resolve paths relative to the relenv root.
    """

    @functools.wraps(func)
    def wrapped(*args: Any) -> ConfigVars:
        if sys.platform == "win32" or "RELENV_BUILDENV" in os.environ:
            return func(*args)

        config_vars = func()
        system_config_vars = system_sysconfig()
        for name in [
            "AR",
            "CC",
            "CFLAGS",
            "CPPFLAGS",
            "CXX",
            "LIBDEST",
            "SCRIPTDIR",
            "BLDSHARED",
            "LDFLAGS",
            "LDCXXSHARED",
            "LDSHARED",
        ]:
            config_vars[name] = system_config_vars[name]
        setattr(mod, "_CONFIG_VARS", config_vars)
        return func(*args)

    return wrapped


def get_paths_wrapper(func: Callable[..., dict[str, str]], default_scheme: str) -> Callable[..., dict[str, str]]:
    """
    Return a wrapper to resolve paths relative to the relenv root.
    """

    @functools.wraps(func)
    def wrapped(
        scheme: str | None = default_scheme,
        vars: dict[str, str] | None = None,
        expand: bool = True,
    ) -> dict[str, str]:
        paths = func(scheme=scheme, vars=vars, expand=expand)
        if "RELENV_PIP_DIR" in os.environ:
            paths["scripts"] = str(relenv_root())
            sys.exec_prefix = paths["scripts"]
        return paths

    return wrapped


def finalize_options_wrapper(func: Callable[..., Any]) -> Callable[..., Any]:
    """
    Wrapper around build_ext.finalize_options.

    Used to add the relenv environment's include path.
    """

    @functools.wraps(func)
    def wrapper(self: Any, *args: Any, **kwargs: Any) -> None:
        func(self, *args, **kwargs)
        if "RELENV_BUILDENV" in os.environ:
            self.include_dirs.append(str(relenv_root() / "include"))

    return wrapper


def install_wheel_wrapper(func: Callable[..., Any]) -> Callable[..., Any]:
    """
    Wrap pip's wheel install function.

    This method determines any newly installed files and checks their RPATHs.
    """

    @functools.wraps(func)
    def wrapper(
        name: str,
        wheel_path: PathType,
        scheme: Any,
        req_description: str,
        *args: Any,
        **kwargs: Any,
    ) -> Any:
        from zipfile import ZipFile

        from pip._internal.utils.wheel import parse_wheel

        with ZipFile(wheel_path) as zf:
            info_dir, metadata = parse_wheel(zf, name)
        func(name, wheel_path, scheme, req_description, *args, **kwargs)
        if "RELENV_BUILDENV" in os.environ:
            plat = pathlib.Path(scheme.platlib)
            rootdir = relenv_root()
            with open(plat / info_dir / "RECORD") as fp:
                for line in fp.readlines():
                    file = plat / line.split(",", 1)[0]
                    if not file.exists():
                        debug(f"Relenv - File not found {file}")
                        continue
                    if relocate().is_elf(file):
                        debug(f"Relenv - Found elf {file}")
                        relocate().handle_elf(plat / file, rootdir / "lib", True, rootdir)
                    elif relocate().is_macho(file):
                        otool_bin = shutil.which("otool")
                        if otool_bin:
                            relocate().handle_macho(str(plat / file), str(rootdir), True)
                        else:
                            debug("The otool command is not available, please run `xcode-select --install`")

    return wrapper


def install_legacy_wrapper(func: Callable[..., Any]) -> Callable[..., Any]:
    """
    Wrap pip's legacy install function.

    This method determines any newly installed files and checks their RPATHs.
    """
    # XXX It might be better to handle legacy installs by overriding things in
    # setuptools, would we get more bang for our buck or increase complexity?

    @functools.wraps(func)
    def wrapper(
        install_options: Any,
        global_options: Any,
        root: Any,
        home: Any,
        prefix: Any,
        use_user_site: Any,
        pycompile: Any,
        scheme: Any,
        setup_py_path: Any,
        isolated: Any,
        req_name: Any,
        build_env: Any,
        unpacked_source_directory: Any,
        req_description: Any,
    ) -> Any:
        pkginfo = pathlib.Path(setup_py_path).parent / "PKG-INFO"
        with open(pkginfo) as fp:
            pkg_info = fp.read()
        version = None
        name = None
        for line in pkg_info.splitlines():
            if line.startswith("Version:"):
                version = line.split("Version: ")[1].strip()
                if name:
                    break
            if line.startswith("Name:"):
                name = line.split("Name: ")[1].strip()
                if version:
                    break
        func(
            install_options,
            global_options,
            root,
            home,
            prefix,
            use_user_site,
            pycompile,
            scheme,
            setup_py_path,
            isolated,
            req_name,
            build_env,
            unpacked_source_directory,
            req_description,
        )
        egginfo = None
        if prefix:
            sitepack = pathlib.Path(prefix) / "lib" / f"python{get_major_version()}" / "site-packages"
            for path in sorted(sitepack.glob("*.egg-info")):
                if path.name.startswith(f"{name}-{version}"):
                    egginfo = path
                    break
        for path in sorted(pathlib.Path(scheme.purelib).glob("*.egg-info")):
            if path.name.startswith(f"{name}-{version}"):
                egginfo = path
                break
        if egginfo is None:
            debug(f"Relenv was not able to find egg info for: {req_description}")
            return
        plat = pathlib.Path(scheme.platlib)
        rootdir = relenv_root()
        with pushd(egginfo):
            with open("installed-files.txt") as fp:
                for line in fp.readlines():
                    file = pathlib.Path(line.strip()).resolve()
                    if not file.exists():
                        debug(f"Relenv - File not found {file}")
                        continue
                    if relocate().is_elf(file):
                        debug(f"Relenv - Found elf {file}")
                        relocate().handle_elf(plat / file, rootdir / "lib", True, rootdir)

    return wrapper


class Wrapper:
    """
    Wrap methods of an imported module.
    """

    def __init__(
        self,
        module: str,
        wrapper: Callable[[str], ModuleType],
        matcher: str = "equals",
        _loading: bool = False,
    ) -> None:
        self.module = module
        self.wrapper = wrapper
        self.matcher = matcher
        self.loading = _loading

    def matches(self: Wrapper, module: str) -> bool:
        """
        Check if wrapper metches module being imported.
        """
        if self.matcher == "startswith":
            return module.startswith(self.module)
        return self.module == module

    def __call__(self: Wrapper, module_name: str) -> ModuleType:
        """
        Preform the wrapper operation.
        """
        return self.wrapper(module_name)


class RelenvImporter:
    """
    Handle runtime wrapping of module methods.
    """

    def __init__(
        self,
        wrappers: Iterable[Wrapper] | None = None,
        _loads: dict[str, ModuleType] | None = None,
    ) -> None:
        if wrappers is None:
            wrappers = []
        self.wrappers: set[Wrapper] = set(wrappers)
        if _loads is None:
            _loads = {}
        self._loads: dict[str, ModuleType] = _loads

    def find_spec(
        self: RelenvImporter,
        module_name: str,
        package_path: Sequence[str] | None = None,
        target: Any = None,
    ) -> ModuleSpec | None:
        """
        Find modules being imported.
        """
        for wrapper in self.wrappers:
            if wrapper.matches(module_name) and not wrapper.loading:
                debug(f"RelenvImporter - match {module_name} {package_path} {target}")
                wrapper.loading = True
                spec = importlib.util.spec_from_loader(module_name, self)
                return cast("ModuleSpec | None", spec)
        return None

    def find_module(
        self: RelenvImporter,
        module_name: str,
        package_path: Sequence[str] | None = None,
    ) -> RelenvImporter | None:
        """
        Find modules being imported.
        """
        for wrapper in self.wrappers:
            if wrapper.matches(module_name) and not wrapper.loading:
                debug(f"RelenvImporter - match {module_name}")
                wrapper.loading = True
                return self
        return None

    def load_module(self: RelenvImporter, name: str) -> ModuleType:
        """
        Load an imported module.
        """
        mod: ModuleType | None = None
        for wrapper in self.wrappers:
            if wrapper.matches(name):
                debug(f"RelenvImporter - load_module {name}")
                mod = wrapper(name)
                wrapper.loading = False
                break
        if mod is None:
            mod = importlib.import_module(name)
        sys.modules[name] = mod
        return mod

    def create_module(self: RelenvImporter, spec: ModuleSpec) -> ModuleType | None:
        """
        Create the module via a spec.
        """
        return self.load_module(spec.name)

    def exec_module(self: RelenvImporter, module: ModuleType) -> None:
        """
        Exec module noop.
        """
        return None


def wrap_sysconfig(name: str) -> ModuleType:
    """
    Sysconfig wrapper.
    """
    module: ModuleType = importlib.import_module("sysconfig")
    mod = cast("Any", module)
    mod.get_config_var = get_config_var_wrapper(mod.get_config_var)
    mod.get_config_vars = get_config_vars_wrapper(mod.get_config_vars, mod)
    mod._PIP_USE_SYSCONFIG = True
    try:
        # Python >= 3.10
        scheme = mod.get_default_scheme()
    except AttributeError:
        # Python < 3.10
        scheme = mod._get_default_scheme()
    mod.get_paths = get_paths_wrapper(mod.get_paths, scheme)
    return module


def wrap_pip_distlib_scripts(name: str) -> ModuleType:
    """
    pip.distlib.scripts wrapper.
    """
    module: ModuleType = importlib.import_module(name)
    mod = cast("Any", module)
    mod.ScriptMaker._build_shebang = _build_shebang(mod.ScriptMaker._build_shebang)
    return module


def wrap_distutils_command(name: str) -> ModuleType:
    """
    distutils.command wrapper.
    """
    module: ModuleType = importlib.import_module(name)
    mod = cast("Any", module)
    mod.build_ext.finalize_options = finalize_options_wrapper(mod.build_ext.finalize_options)
    return module


def wrap_pip_install_wheel(name: str) -> ModuleType:
    """
    pip._internal.operations.install.wheel wrapper.
    """
    module: ModuleType = importlib.import_module(name)
    mod = cast("Any", module)
    mod.install_wheel = install_wheel_wrapper(mod.install_wheel)
    return module


def wrap_pip_install_legacy(name: str) -> ModuleType:
    """
    pip._internal.operations.install.legacy wrapper.
    """
    module: ModuleType = importlib.import_module(name)
    mod = cast("Any", module)
    mod.install = install_legacy_wrapper(mod.install)
    return module


def set_env_if_not_set(name: str, value: str) -> None:
    """
    Set an environment variable if not already set.

    If the environment variable is already set and not equal to value, warn the
    user.
    """
    if name in os.environ and os.environ[name] != value:
        # Stderr keeps this warning out of stdout, which several build
        # tools parse as structured data — see debug() above.
        print(
            f"Warning: {name} environment not set to relenv's root!\nexpected: {value}\ncurrent: {os.environ[name]}",
            file=sys.stderr,
        )
    else:
        debug(f"Relenv set {name}")
        os.environ[name] = value


def wrap_pip_build_wheel(name: str) -> ModuleType:
    """
    pip._internal.operations.build wrapper.
    """
    module: ModuleType = importlib.import_module(name)
    mod = cast("Any", module)

    def wrap(func: Callable[..., Any]) -> Callable[..., Any]:
        @functools.wraps(func)
        def wrapper(*args: Any, **kwargs: Any) -> Any:
            if sys.platform != "linux":
                return func(*args, **kwargs)
            toolchain = common().get_toolchain()
            cargo_home = str(common().DATA_DIR / "cargo")
            if toolchain is None or not toolchain.exists():
                debug("Unable to set CARGO_HOME no toolchain exists")
            else:
                relenvroot = str(sys.RELENV)
                rustflags = (
                    f"-C link-arg=-Wl,-rpath,{relenvroot}/lib "
                    f"-C link-arg=-L{relenvroot}/lib "
                    f"-C link-arg=-L{toolchain}/sysroot/lib"
                )
                set_env_if_not_set("CARGO_HOME", cargo_home)
                set_env_if_not_set("OPENSSL_DIR", relenvroot)
                set_env_if_not_set("RUSTFLAGS", rustflags)
            return func(*args, **kwargs)

        return wrapper

    mod.build_wheel_pep517 = wrap(mod.build_wheel_pep517)
    return module


class TARGET:
    """
    Container for global pip target state.
    """

    TARGET: bool = False
    PATH: str | None = None
    IGNORE: bool = False
    INSTALL: bool = False


def _ensure_target_path() -> str:
    """
    Return the stored target path, raising if it is unavailable.
    """
    if TARGET.PATH is None:
        raise RuntimeError("TARGET path requested but not initialized")
    return TARGET.PATH


def wrap_cmd_install(name: str) -> ModuleType:
    """
    Wrap pip install command to store target argument state.
    """
    module: ModuleType = importlib.import_module(name)
    mod = cast("Any", module)

    def wrap_run(func: Callable[..., Any]) -> Callable[..., Any]:
        @functools.wraps(func)
        def wrapper(self: Any, options: Any, args: Sequence[str]) -> Any:
            if not options.use_user_site:
                if options.target_dir:
                    TARGET.TARGET = True
                    TARGET.PATH = options.target_dir
                    TARGET.IGNORE = options.ignore_installed
            return func(self, options, args)

        return wrapper

    mod.InstallCommand.run = wrap_run(mod.InstallCommand.run)

    def wrap_handle_target(func: Callable[..., Any]) -> Callable[..., Any]:
        @functools.wraps(func)
        def wrapper(self: Any, target_dir: str, target_temp_dir: str, upgrade: bool) -> int:
            from pip._internal.cli.status_codes import SUCCESS

            return SUCCESS

        return wrapper

    if hasattr(mod.InstallCommand, "_handle_target_dir"):
        mod.InstallCommand._handle_target_dir = wrap_handle_target(mod.InstallCommand._handle_target_dir)
    return module


def wrap_locations(name: str) -> ModuleType:
    """
    Wrap pip locations to fix locations when installing with target.
    """
    module: ModuleType = importlib.import_module(name)
    mod = cast("Any", module)

    def make_scheme_wrapper(func: Callable[..., Any]) -> Callable[..., Any]:
        @functools.wraps(func)
        def wrapper(
            dist_name: str,
            user: bool = False,
            home: PathType | None = None,
            root: PathType | None = None,
            isolated: bool = False,
            prefix: PathType | None = None,
        ) -> Any:
            scheme = func(dist_name, user, home, root, isolated, prefix)
            if TARGET.TARGET and TARGET.INSTALL:
                from pip._internal.models.scheme import Scheme

                target_path = _ensure_target_path()
                scheme = Scheme(
                    platlib=target_path,
                    purelib=target_path,
                    headers=scheme.headers,
                    scripts=scheme.scripts,
                    data=scheme.data,
                )
            return scheme

        return wrapper

    # get_scheme is not available on pip-19.2.3
    # try:
    mod.get_scheme = make_scheme_wrapper(mod.get_scheme)
    # except AttributeError:
    #    debug(f"Module {mod} does not have attribute get_scheme")

    return module


def wrap_req_command(name: str) -> ModuleType:
    """
    Honor ignore installed option from pip cli.
    """
    module: ModuleType = importlib.import_module(name)
    mod = cast("Any", module)

    def make_package_finder_wrapper(func: Callable[..., Any]) -> Callable[..., Any]:
        @functools.wraps(func)
        def wrapper(
            self: Any,
            options: Any,
            session: Any,
            target_python: Any = None,
            ignore_requires_python: Any = None,
        ) -> Any:
            if TARGET.TARGET:
                options.ignore_installed = TARGET.IGNORE
            return func(self, options, session, target_python, ignore_requires_python)

        return wrapper

    mod.RequirementCommand._build_package_finder = make_package_finder_wrapper(
        mod.RequirementCommand._build_package_finder
    )
    return module


def wrap_req_install(name: str) -> ModuleType:
    """
    Ensure installs honor the TARGET home directory, staying compatible with
    whatever shape pip's InstallRequirement.install currently has (pip has
    changed this signature several times across releases, most recently
    adding `script_executable` in pip 26.2).
    """
    module: ModuleType = importlib.import_module(name)
    mod = cast("Any", module)

    original = mod.InstallRequirement.install
    sig = inspect.signature(original)
    if "home" not in sig.parameters:
        raise RuntimeError(
            "pip's InstallRequirement.install no longer has a 'home' "
            f"parameter (signature is now {sig}); relenv's TARGET-install "
            "support needs to be updated for this pip version."
        )

    @functools.wraps(original)
    def install_wrapper(self: Any, *args: Any, **kwargs: Any) -> Any:
        try:
            if TARGET.TARGET:
                TARGET.INSTALL = True
                bound = sig.bind_partial(self, *args, **kwargs)
                bound.apply_defaults()
                bound.arguments["home"] = _ensure_target_path()
                return original(*bound.args, **bound.kwargs)
            return original(self, *args, **kwargs)
        finally:
            TARGET.INSTALL = False

    mod.InstallRequirement.install = install_wrapper
    return module


importer = RelenvImporter(
    wrappers=[
        Wrapper("sysconfig", wrap_sysconfig),
        Wrapper("pip._vendor.distlib.scripts", wrap_pip_distlib_scripts),
        Wrapper("distutils.command.build_ext", wrap_distutils_command),
        Wrapper("pip._internal.operations.install.wheel", wrap_pip_install_wheel),
        Wrapper("pip._internal.operations.install.legacy", wrap_pip_install_legacy),
        Wrapper("pip._internal.operations.build.wheel", wrap_pip_build_wheel),
        Wrapper("pip._internal.commands.install", wrap_cmd_install),
        Wrapper("pip._internal.locations", wrap_locations),
        Wrapper("pip._internal.cli.req_command", wrap_req_command),
        Wrapper("pip._internal.req.req_install", wrap_req_install),
    ],
)


def install_cargo_config() -> None:
    """
    Setup cargo config.
    """
    if sys.platform != "linux":
        return

    # We need this as a late import for python < 3.12 becuase importing it will
    # load the ssl module. Causing out setup_openssl method to fail to load
    # fips module.
    dirs = common().work_dirs()
    cargo_home = dirs.data / "cargo"
    triplet = common().get_triplet()
    toolchain = None

    try:
        toolchain = common().get_toolchain()
    except PermissionError:
        pass

    if not toolchain:
        debug("Unable to set CARGO_HOME ppbt package not installed")
        return

    if not toolchain.exists():
        debug("Unable to set CARGO_HOME no toolchain exists")
        return

    # cargo_home = dirs.data / "cargo"
    cargo_home.mkdir(parents=True, exist_ok=True)
    cargo_config = cargo_home / "config.toml"
    if triplet == "x86_64-linux-gnu":
        cargo_triplet = "x86_64-unknown-linux-gnu"
    else:
        cargo_triplet = "aarch64-unknown-linux-gnu"
    gcc = toolchain / "bin" / f"{triplet}-gcc"

    def existing_linker() -> str | None:
        if not cargo_config.exists():
            return None
        try:
            contents = cargo_config.read_text()
        except OSError:
            return None
        marker = f"[target.{cargo_triplet}]"
        if marker not in contents:
            return None
        for line in contents.splitlines():
            stripped = line.strip()
            if stripped.startswith("linker"):
                value_part: str
                _, _, value_part = stripped.partition("=")
                value_str: str = value_part.strip().strip('"')
                if value_str:
                    return value_str
        return None

    if existing_linker() != str(gcc):
        cargo_config.write_text(
            textwrap.dedent(
                """\
            [target.{triplet}]
            linker = "{linker}"
            """
            ).format(triplet=cargo_triplet, linker=gcc)
        )


def setup_openssl() -> None:
    """
    Configure openssl certificate locations.
    """
    if sys.platform == "win32":
        return

    openssl_bin = shutil.which("openssl")
    if not openssl_bin:
        debug("Could not find the 'openssl' binary in the path")
        set_openssl_modules_dir(str(sys.RELENV / "lib" / "ossl-modules"))

        if load_openssl_provider("default") == 0:
            debug("Unable to load the default openssl provider")
        if load_openssl_provider("legacy") == 0:
            debug("Unable to load the legacy openssl provider")

        return

    if "OPENSSL_MODULES" not in os.environ:
        # First try and load the system's fips provider. Then load relenv's
        # legacy and default providers. The fips provider must be loaded first
        # in order OpenSSl to work properly..

        # Try and determine the system's openssl modules directory. This is so
        # we can use the system installed fips provider if it configured.
        proc = subprocess.run(
            [openssl_bin, "version", "-m"],
            universal_newlines=True,
            shell=False,
            check=False,
            capture_output=True,
        )
        if proc.returncode != 0:
            msg = "Unable to get the modules directory from openssl"
            if proc.stderr:
                msg += f": {proc.stderr}"
            debug(msg)
        else:
            try:
                _, directory = proc.stdout.split(":", 1)
            except ValueError:
                debug("Unable to parse modules dir")
                return
            path = directory.strip().strip('"')
            set_openssl_modules_dir(path)
            if load_openssl_provider("fips") == 0:
                debug("Unable to load the fips openssl provider")

        set_openssl_modules_dir(str(sys.RELENV / "lib" / "ossl-modules"))

        if load_openssl_provider("default") == 0:
            debug("Unable to load the default openssl provider")
        if load_openssl_provider("legacy") == 0:
            debug("Unable to load the legacy openssl provider")

    # Use system openssl dirs
    # XXX Should we also setup SSL_CERT_FILE, OPENSSL_CONF &
    # OPENSSL_CONF_INCLUDE?
    if "SSL_CERT_DIR" not in os.environ:
        proc = subprocess.run(
            [openssl_bin, "version", "-d"],
            universal_newlines=True,
            shell=False,
            check=False,
            capture_output=True,
        )
        if proc.returncode != 0:
            msg = "Unable to get the certificates directory from openssl"
            if proc.stderr:
                msg += f": {proc.stderr}"
            debug(msg)
        else:
            try:
                _, directory = proc.stdout.split(":", 1)
            except ValueError:
                debug("Unable to parse openssldir")
                return
            path = pathlib.Path(directory.strip().strip('"'))
            if not os.environ.get("SSL_CERT_DIR"):
                os.environ["SSL_CERT_DIR"] = str(path / "certs")
            cert_file = path / "cert.pem"
            if cert_file.exists() and not os.environ.get("SSL_CERT_FILE"):
                os.environ["SSL_CERT_FILE"] = str(cert_file)


def set_openssl_modules_dir(path: str) -> None:
    """
    Set the default search location for openssl modules.
    """
    if sys.platform == "darwin":
        cryptopath = str(sys.RELENV / "lib" / "libcrypto.dylib")
    else:
        cryptopath = str(sys.RELENV / "lib" / "libcrypto.so")
    libcrypto = ctypes.CDLL(cryptopath)
    POSSL_LIB_CTX = ctypes.c_void_p
    OSSL_PROVIDER_set_default_search_path = libcrypto.OSSL_PROVIDER_set_default_search_path
    OSSL_PROVIDER_set_default_search_path.argtypes = (POSSL_LIB_CTX, ctypes.c_char_p)
    OSSL_PROVIDER_set_default_search_path.restype = ctypes.c_int
    OSSL_PROVIDER_set_default_search_path(None, path.encode())


def load_openssl_provider(name: str) -> int:
    """
    Load an openssl module.
    """
    if sys.platform == "darwin":
        cryptopath = str(sys.RELENV / "lib" / "libcrypto.dylib")
    else:
        cryptopath = str(sys.RELENV / "lib" / "libcrypto.so")
    libcrypto = ctypes.CDLL(cryptopath)
    POSSL_LIB_CTX = ctypes.c_void_p
    OSSL_PROVIDER_load = libcrypto.OSSL_PROVIDER_load
    OSSL_PROVIDER_load.argtypes = (POSSL_LIB_CTX, ctypes.c_char_p)
    OSSL_PROVIDER_load.restype = ctypes.c_int
    result = OSSL_PROVIDER_load(None, name.encode())
    return int(result)


def setup_crossroot() -> None:
    """
    Setup cross root if needed.
    """
    cross = os.environ.get("RELENV_CROSS", "")
    if cross:
        crossroot = pathlib.Path(cross).resolve()
        sys.prefix = str(crossroot)
        sys.exec_prefix = str(crossroot)
        # XXX What about dist-packages
        pyver = f"python{sys.version_info.major}.{sys.version_info.minor}"
        sys.path = [
            str(crossroot / "lib" / pyver),
            str(crossroot / "lib" / pyver / "lib-dynload"),
            str(crossroot / "lib" / pyver / "site-packages"),
        ] + [_ for _ in sys.path if "site-packages" not in _]


def wrapsitecustomize(func: Callable[[], Any]) -> Callable[[], None]:
    """
    Wrap site.execsitecustomize.

    This allows relenv a hook to be the last thing that runs when pythong is
    setting itself up.
    """

    @functools.wraps(func)
    def wrapper() -> None:
        func()

        sitecustomize_module = None
        try:
            import sitecustomize as _sitecustomize

            sitecustomize_module = _sitecustomize
        except ImportError as exc:
            if exc.name != "sitecustomize":
                raise

        # Attempt to make sure we're not pulling in packages outside of the
        # relenv environment. This can't be done when pip is using build_env to
        # install packages. This code seems potentially brittle and there may
        # be reasonable arguments against doing it at all.
        if sitecustomize_module is None or "pip-build-env" not in sitecustomize_module.__file__:
            _orig = sys.path[:]
            # Replace sys.path
            sys.path[:] = common().sanitize_sys_path(sys.path)
            debug(f"original sys.path was {_orig}")
            debug(f"new sys.path is {sys.path}")
        else:
            debug("Skip path munging")

        site.ENABLE_USER_SITE = False
        debug("After site customize wrapper")

    return wrapper


def bootstrap() -> None:
    """
    Bootstrap the relenv environment.
    """
    warnings.filterwarnings(
        "ignore",
        message=".*falling back to find_module.*",
        category=ImportWarning,
        module="importlib._bootstrap",
        lineno=914,
    )
    sys.RELENV = relenv_root()
    setup_openssl()
    site.execsitecustomize = wrapsitecustomize(site.execsitecustomize)
    setup_crossroot()
    install_cargo_config()
    sys.meta_path = [importer] + sys.meta_path

    # For Python 3.13+, sysconfig became a package so the importer doesn't
    # intercept it. Manually wrap it here.
    if sys.version_info >= (3, 13):
        wrap_sysconfig("sysconfig")
