from __future__ import annotations

import importlib
from typing import TYPE_CHECKING

import distlib.scripts
from pip._internal.utils import logging as pip_logging
from pip._vendor.pkg_resources import EggInfoDistribution
from pip_shims import shims

from pdm.iostream import stream
from pdm.models.builders import log_subprocessor
from pdm.models.requirements import parse_requirement

if TYPE_CHECKING:
    from distlib.wheel import Wheel
    from pip._vendor.pkg_resources import Distribution

    from pdm.models.candidates import Candidate
    from pdm.models.environment import Environment


def is_dist_editable(dist: Distribution) -> bool:
    return isinstance(dist, EggInfoDistribution)


def format_dist(dist: Distribution) -> str:
    formatter = "{version}{path}"
    path = ""
    if is_dist_editable(dist):
        path = f" (-e {dist.location})"
    return formatter.format(version=stream.yellow(dist.version), path=path)


EXE_INITIALIZE = """
import sys
with open({0!r}) as fp:
    exec(compile(fp.read(), __file__, "exec"))
""".strip()


class Installer:  # pragma: no cover
    """The installer that performs the installation and uninstallation actions."""

    def __init__(self, environment: Environment, auto_confirm: bool = True) -> None:
        self.environment = environment
        self.auto_confirm = auto_confirm
        # XXX: Patch pip to make it work under multi-thread mode
        pip_logging._log_state.indentation = 0

    def install(self, candidate: Candidate) -> None:
        candidate.get_metadata(allow_all_wheels=False)
        if candidate.req.editable:
            self.install_editable(candidate.ireq)
        else:
            self.install_wheel(candidate.wheel)

    def install_wheel(self, wheel: Wheel) -> None:
        paths = self.environment.get_paths()
        maker = distlib.scripts.ScriptMaker(None, None)
        maker.executable = self.environment.python_executable
        if not self.environment.is_global:
            site_custom_script = (
                self.environment.packages_path / "site/sitecustomize.py"
            ).as_posix()
            maker.script_template = maker.script_template.replace(
                "import sys",
                EXE_INITIALIZE.format(site_custom_script),
            )
        wheel.install(paths, maker)

    def install_editable(self, ireq: shims.InstallRequirement) -> None:
        setup_path = ireq.setup_py_path
        paths = self.environment.get_paths()
        install_script = importlib.import_module(
            "pdm.installers._editable_install"
        ).__file__.rstrip("co")
        install_args = [
            self.environment.python_executable,
            "-u",
            install_script,
            setup_path,
            paths["prefix"],
            paths["purelib"],
            paths["scripts"],
        ]
        with self.environment.activate(True):
            extra_env = {"INJECT_SITE": "1"} if not self.environment.is_global else None
            log_subprocessor(install_args, ireq.unpacked_source_directory, extra_env)

    def uninstall(self, dist: Distribution) -> None:
        req = parse_requirement(dist.project_name)
        if is_dist_editable(dist):
            ireq = shims.install_req_from_editable(dist.location)
        else:
            ireq = shims.install_req_from_line(dist.project_name)
        ireq.req = req

        with self.environment.activate():
            pathset = ireq.uninstall(auto_confirm=self.auto_confirm)
            if pathset:
                pathset.commit()
