from __future__ import annotations

import os
import platform
import subprocess  # noqa: S404
import sys
from pathlib import Path
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from collections.abc import Callable


def detect_active_interpreter() -> str:
    """
    Attempt to detect a venv, virtualenv, poetry, or conda environment by looking for certain markers.

    If it fails to find any, it will fail with a message.
    """
    detection_funcs: list[Callable[[], Path | None]] = [
        detect_venv_or_virtualenv_interpreter,
        detect_conda_env_interpreter,
        detect_poetry_env_interpreter,
    ]
    for detect in detection_funcs:
        path = detect()
        if not path:
            continue
        if not path.exists():
            break
        return str(path)

    print("Unable to detect virtual environment.", file=sys.stderr)  # noqa: T201
    raise SystemExit(1)


def detect_venv_or_virtualenv_interpreter() -> Path | None:
    # Both virtualenv and venv set this environment variable.
    env_var = os.environ.get("VIRTUAL_ENV")
    if not env_var:
        return None

    path = Path(env_var)
    path /= determine_bin_dir()

    file_name = determine_interpreter_file_name()
    return path / file_name if file_name else None


def determine_bin_dir() -> str:
    return "Scripts" if os.name == "nt" else "bin"


def detect_conda_env_interpreter() -> Path | None:
    # Env var mentioned in https://docs.conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html#saving-environment-variables.
    env_var = os.environ.get("CONDA_PREFIX")
    if not env_var:
        return None

    path = Path(env_var)

    # On POSIX systems, conda adds the python executable to the /bin directory. On Windows, it resides in the parent
    # directory of /bin (i.e. the root directory).
    # See https://docs.anaconda.com/free/working-with-conda/configurations/python-path/#examples.
    if os.name == "posix":  # pragma: posix cover
        path /= "bin"

    file_name = determine_interpreter_file_name()

    return path / file_name if file_name else None


def detect_poetry_env_interpreter() -> Path | None:
    # poetry doesn't expose an environment variable like other implementations, so we instead use its CLI to snatch the
    # active interpreter.
    # See https://python-poetry.org/docs/managing-environments/#displaying-the-environment-information.
    try:
        result = subprocess.run(
            ("poetry", "env", "info", "--executable"),
            check=True,
            text=True,
            stdout=subprocess.PIPE,
            stderr=subprocess.DEVNULL,
        )
    except Exception:  # noqa: BLE001
        return None

    return Path(result.stdout.strip())


def determine_interpreter_file_name() -> str | None:
    impl_name_to_file_name_dict = {"CPython": "python", "PyPy": "pypy"}
    name = impl_name_to_file_name_dict.get(platform.python_implementation())
    if not name:
        return None
    if os.name == "nt":  # pragma: nt cover
        return name + ".exe"
    return name


__all__ = ["detect_active_interpreter"]
