"""
locations.py tests

"""

import getpass
import os
import shutil
import sys
import sysconfig
import tempfile
from pathlib import Path
from typing import Any
from unittest.mock import Mock

import pytest

from pip._internal.locations import SCHEME_KEYS, _should_use_sysconfig, get_scheme

if sys.platform == "win32":
    pwd = Mock()
else:
    import pwd


def _get_scheme_dict(*args: Any, **kwargs: Any) -> dict[str, str]:
    scheme = get_scheme(*args, **kwargs)
    return {k: getattr(scheme, k) for k in SCHEME_KEYS}


class TestLocations:
    def setup_method(self) -> None:
        self.tempdir = tempfile.mkdtemp()
        self.st_uid = 9999
        self.username = "example"
        self.patch()

    def teardown_method(self) -> None:
        self.revert_patch()
        shutil.rmtree(self.tempdir, ignore_errors=True)

    def patch(self) -> None:
        """first store and then patch python methods pythons"""
        self.tempfile_gettempdir = tempfile.gettempdir
        self.old_os_fstat = os.fstat
        if sys.platform != "win32":
            # os.geteuid and pwd.getpwuid are not implemented on windows
            self.old_os_geteuid = os.geteuid
            self.old_pwd_getpwuid = pwd.getpwuid
        self.old_getpass_getuser = getpass.getuser

        # now patch
        tempfile.gettempdir = lambda: self.tempdir
        getpass.getuser = lambda: self.username
        os.fstat = lambda fd: self.get_mock_fstat(fd)
        if sys.platform != "win32":
            os.geteuid = lambda: self.st_uid
            pwd.getpwuid = self.get_mock_getpwuid

    def revert_patch(self) -> None:
        """revert the patches to python methods"""
        tempfile.gettempdir = self.tempfile_gettempdir
        getpass.getuser = self.old_getpass_getuser
        if sys.platform != "win32":
            # os.geteuid and pwd.getpwuid are not implemented on windows
            os.geteuid = self.old_os_geteuid
            pwd.getpwuid = self.old_pwd_getpwuid
        os.fstat = self.old_os_fstat

    def get_mock_fstat(self, fd: int) -> os.stat_result:
        """returns a basic mock fstat call result.
        Currently only the st_uid attribute has been set.
        """
        result = Mock()
        result.st_uid = self.st_uid
        return result

    def get_mock_getpwuid(self, uid: int) -> Any:
        """returns a basic mock pwd.getpwuid call result.
        Currently only the pw_name attribute has been set.
        """
        result = Mock()
        result.pw_name = self.username
        return result

    def test_default_should_use_sysconfig(
        self, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        monkeypatch.delattr(sysconfig, "_PIP_USE_SYSCONFIG", raising=False)
        assert _should_use_sysconfig() is True

    @pytest.mark.parametrize("vendor_value", [True, False, None, "", 0, 1])
    def test_vendor_overridden_should_use_sysconfig(
        self, monkeypatch: pytest.MonkeyPatch, vendor_value: Any
    ) -> None:
        monkeypatch.setattr(
            sysconfig, "_PIP_USE_SYSCONFIG", vendor_value, raising=False
        )
        assert _should_use_sysconfig() is bool(vendor_value)


class TestDistutilsScheme:
    def test_root_modifies_appropriately(self) -> None:
        # This deals with nt/posix path differences
        # root is c:\somewhere\else or /somewhere/else
        root = os.path.normcase(
            os.path.abspath(os.path.join(os.path.sep, "somewhere", "else"))
        )
        norm_scheme = _get_scheme_dict("example")
        root_scheme = _get_scheme_dict("example", root=root)

        for key, value in norm_scheme.items():
            drive, path = os.path.splitdrive(os.path.abspath(value))
            expected = os.path.join(root, path[1:])
            assert os.path.abspath(root_scheme[key]) == expected

    @pytest.mark.incompatible_with_sysconfig
    @pytest.mark.incompatible_with_venv
    def test_distutils_config_file_read(
        self, tmpdir: Path, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        # This deals with nt/posix path differences
        install_scripts = os.path.normcase(
            os.path.abspath(os.path.join(os.path.sep, "somewhere", "else"))
        )
        f = tmpdir / "config" / "setup.cfg"
        f.parent.mkdir()
        f.write_text("[install]\ninstall-scripts=" + install_scripts)
        from distutils.dist import Distribution

        # patch the function that returns what config files are present
        monkeypatch.setattr(
            Distribution,
            "find_config_files",
            lambda self: [f],
        )
        scheme = _get_scheme_dict("example")
        assert scheme["scripts"] == install_scripts

    @pytest.mark.incompatible_with_sysconfig
    @pytest.mark.incompatible_with_venv
    # when we request install-lib, we should install everything (.py &
    # .so) into that path; i.e. ensure platlib & purelib are set to
    # this path. sysconfig does not support this.
    def test_install_lib_takes_precedence(
        self, tmpdir: Path, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        # This deals with nt/posix path differences
        install_lib = os.path.normcase(
            os.path.abspath(os.path.join(os.path.sep, "somewhere", "else"))
        )
        f = tmpdir / "config" / "setup.cfg"
        f.parent.mkdir()
        f.write_text("[install]\ninstall-lib=" + install_lib)
        from distutils.dist import Distribution

        # patch the function that returns what config files are present
        monkeypatch.setattr(
            Distribution,
            "find_config_files",
            lambda self: [f],
        )
        scheme = _get_scheme_dict("example")
        assert scheme["platlib"] == install_lib + os.path.sep
        assert scheme["purelib"] == install_lib + os.path.sep

    def test_prefix_modifies_appropriately(self) -> None:
        prefix = os.path.abspath(os.path.join("somewhere", "else"))

        normal_scheme = _get_scheme_dict("example")
        prefix_scheme = _get_scheme_dict("example", prefix=prefix)

        def _calculate_expected(value: str) -> str:
            path = os.path.join(prefix, os.path.relpath(value, sys.prefix))
            return os.path.normpath(path)

        expected = {k: _calculate_expected(v) for k, v in normal_scheme.items()}
        assert prefix_scheme == expected
