"""Implementation of packaging-related magic functions.
"""
#-----------------------------------------------------------------------------
#  Copyright (c) 2018 The IPython Development Team.
#
#  Distributed under the terms of the Modified BSD License.
#
#  The full license is in the file COPYING.txt, distributed with this software.
#-----------------------------------------------------------------------------

import functools
import os
import re
import shlex
import sys
from pathlib import Path

from IPython.core.magic import Magics, magics_class, line_magic


def is_conda_environment(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        """Return True if the current Python executable is in a conda env"""
        # TODO: does this need to change on windows?
        if not Path(sys.prefix, "conda-meta", "history").exists():
            raise ValueError(
                "The python kernel does not appear to be a conda environment.  "
                "Please use ``%pip install`` instead."
            )
        return func(*args, **kwargs)

    return wrapper


def _get_conda_like_executable(command):
    """Find the path to the given executable

    Parameters
    ----------

    executable: string
        Value should be: conda, mamba or micromamba
    """
    # Check for a environment variable bound to the base executable, both conda and mamba
    # set these when activating an environment.
    base_executable = "CONDA_EXE"
    if "mamba" in command.lower():
        base_executable = "MAMBA_EXE"
    if base_executable in os.environ:
        executable = Path(os.environ[base_executable])
        if executable.is_file():
            return str(executable.resolve())

    # Check if there is a conda executable in the same directory as the Python executable.
    # This is the case within conda's root environment.
    executable = Path(sys.executable).parent / command
    if executable.is_file():
        return str(executable)

    # Otherwise, attempt to extract the executable from conda history.
    # This applies in any conda environment. Parsing this way is error prone because
    # different versions of conda and mamba include differing cmd values such as
    # `conda`, `conda-script.py`, or `path/to/conda`, here use the raw command provided.
    history = Path(sys.prefix, "conda-meta", "history").read_text(encoding="utf-8")
    match = re.search(
        rf"^#\s*cmd:\s*(?P<command>.*{command})\s[create|install]",
        history,
        flags=re.MULTILINE,
    )
    if match:
        return match.groupdict()["command"]

    # Fallback: assume the executable is available on the system path.
    return command


CONDA_COMMANDS_REQUIRING_PREFIX = {
    'install', 'list', 'remove', 'uninstall', 'update', 'upgrade',
}
CONDA_COMMANDS_REQUIRING_YES = {
    'install', 'remove', 'uninstall', 'update', 'upgrade',
}
CONDA_ENV_FLAGS = {'-p', '--prefix', '-n', '--name'}
CONDA_YES_FLAGS = {'-y', '--y'}


@magics_class
class PackagingMagics(Magics):
    """Magics related to packaging & installation"""

    @line_magic
    def pip(self, line):
        """Run the pip package manager within the current kernel.

        Usage:
          %pip install [pkgs]
        """
        python = sys.executable
        if sys.platform == "win32":
            python = '"' + python + '"'
        else:
            python = shlex.quote(python)

        self.shell.system(" ".join([python, "-m", "pip", line]))

        print("Note: you may need to restart the kernel to use updated packages.")

    def _run_command(self, cmd, line):
        args = shlex.split(line)
        command = args[0] if len(args) > 0 else ""
        args = args[1:] if len(args) > 1 else [""]

        extra_args = []

        # When the subprocess does not allow us to respond "yes" during the installation,
        # we need to insert --yes in the argument list for some commands
        stdin_disabled = getattr(self.shell, 'kernel', None) is not None
        needs_yes = command in CONDA_COMMANDS_REQUIRING_YES
        has_yes = set(args).intersection(CONDA_YES_FLAGS)
        if stdin_disabled and needs_yes and not has_yes:
            extra_args.append("--yes")

        # Add --prefix to point conda installation to the current environment
        needs_prefix = command in CONDA_COMMANDS_REQUIRING_PREFIX
        has_prefix = set(args).intersection(CONDA_ENV_FLAGS)
        if needs_prefix and not has_prefix:
            extra_args.extend(["--prefix", sys.prefix])

        self.shell.system(" ".join([cmd, command] + extra_args + args))
        print("\nNote: you may need to restart the kernel to use updated packages.")

    @line_magic
    @is_conda_environment
    def conda(self, line):
        """Run the conda package manager within the current kernel.

        Usage:
          %conda install [pkgs]
        """
        conda = _get_conda_like_executable("conda")
        self._run_command(conda, line)

    @line_magic
    @is_conda_environment
    def mamba(self, line):
        """Run the mamba package manager within the current kernel.

        Usage:
          %mamba install [pkgs]
        """
        mamba = _get_conda_like_executable("mamba")
        self._run_command(mamba, line)

    @line_magic
    @is_conda_environment
    def micromamba(self, line):
        """Run the conda package manager within the current kernel.

        Usage:
          %micromamba install [pkgs]
        """
        micromamba = _get_conda_like_executable("micromamba")
        self._run_command(micromamba, line)

    @line_magic
    def uv(self, line):
        """Run the uv package manager within the current kernel.

        Usage:
          %uv pip install [pkgs]
        """
        python = sys.executable
        if sys.platform == "win32":
            python = '"' + python + '"'
        else:
            python = shlex.quote(python)

        self.shell.system(" ".join([python, "-m", "uv", line]))

        print("Note: you may need to restart the kernel to use updated packages.")
