"""Tests for all things related to the configuration
"""

import re
from unittest.mock import MagicMock

import pytest

from pip._internal.configuration import get_configuration_files, kinds
from pip._internal.exceptions import ConfigurationError
from tests.lib.configuration_helpers import ConfigurationMixin


class TestConfigurationLoading(ConfigurationMixin):
    def test_global_loading(self) -> None:
        self.patch_configuration(kinds.GLOBAL, {"test.hello": "1"})

        self.configuration.load()
        assert self.configuration.get_value("test.hello") == "1"

    def test_user_loading(self) -> None:
        self.patch_configuration(kinds.USER, {"test.hello": "2"})

        self.configuration.load()
        assert self.configuration.get_value("test.hello") == "2"

    def test_site_loading(self) -> None:
        self.patch_configuration(kinds.SITE, {"test.hello": "3"})

        self.configuration.load()
        assert self.configuration.get_value("test.hello") == "3"

    def test_environment_config_loading(self, monkeypatch: pytest.MonkeyPatch) -> None:
        contents = """
            [test]
            hello = 4
        """

        with self.tmpfile(contents) as config_file:
            monkeypatch.setenv("PIP_CONFIG_FILE", config_file)

            self.configuration.load()
            assert (
                self.configuration.get_value("test.hello") == "4"
            ), self.configuration._config

    def test_environment_var_loading(self, monkeypatch: pytest.MonkeyPatch) -> None:
        monkeypatch.setenv("PIP_HELLO", "5")

        self.configuration.load()
        assert self.configuration.get_value(":env:.hello") == "5"

    @pytest.mark.skipif("sys.platform == 'win32'")
    def test_environment_var_does_not_load_lowercase(
        self, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        monkeypatch.setenv("pip_hello", "5")

        self.configuration.load()
        with pytest.raises(ConfigurationError):
            self.configuration.get_value(":env:.hello")

    def test_environment_var_does_not_load_version(
        self, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        monkeypatch.setenv("PIP_VERSION", "True")

        self.configuration.load()

        with pytest.raises(ConfigurationError):
            self.configuration.get_value(":env:.version")

    def test_environment_config_errors_if_malformed(
        self, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        contents = """
            test]
            hello = 4
        """
        with self.tmpfile(contents) as config_file:
            monkeypatch.setenv("PIP_CONFIG_FILE", config_file)
            with pytest.raises(ConfigurationError) as err:
                self.configuration.load()

        assert "section header" in str(err.value)  # error kind
        assert "1" in str(err.value)  # line number
        assert config_file in str(err.value) or repr(config_file) in str(  # file name
            err.value
        )

    def test_no_such_key_error_message_no_command(self) -> None:
        self.configuration.load_only = kinds.GLOBAL
        self.configuration.load()
        expected_msg = (
            "Key does not contain dot separated section and key. "
            "Perhaps you wanted to use 'global.index-url' instead?"
        )
        pat = f"^{re.escape(expected_msg)}$"
        with pytest.raises(ConfigurationError, match=pat):
            self.configuration.get_value("index-url")

    def test_no_such_key_error_message_missing_option(self) -> None:
        self.configuration.load_only = kinds.GLOBAL
        self.configuration.load()
        expected_msg = "No such key - global.index-url"
        pat = f"^{re.escape(expected_msg)}$"
        with pytest.raises(ConfigurationError, match=pat):
            self.configuration.get_value("global.index-url")


class TestConfigurationPrecedence(ConfigurationMixin):
    # Tests for methods to that determine the order of precedence of
    # configuration options

    def test_env_overides_site(self) -> None:
        self.patch_configuration(kinds.SITE, {"test.hello": "1"})
        self.patch_configuration(kinds.ENV, {"test.hello": "0"})
        self.configuration.load()

        assert self.configuration.get_value("test.hello") == "0"

    def test_env_overides_user(self) -> None:
        self.patch_configuration(kinds.USER, {"test.hello": "2"})
        self.patch_configuration(kinds.ENV, {"test.hello": "0"})
        self.configuration.load()

        assert self.configuration.get_value("test.hello") == "0"

    def test_env_overides_global(self) -> None:
        self.patch_configuration(kinds.GLOBAL, {"test.hello": "3"})
        self.patch_configuration(kinds.ENV, {"test.hello": "0"})
        self.configuration.load()

        assert self.configuration.get_value("test.hello") == "0"

    def test_site_overides_user(self) -> None:
        self.patch_configuration(kinds.USER, {"test.hello": "2"})
        self.patch_configuration(kinds.SITE, {"test.hello": "1"})
        self.configuration.load()

        assert self.configuration.get_value("test.hello") == "1"

    def test_site_overides_global(self) -> None:
        self.patch_configuration(kinds.GLOBAL, {"test.hello": "3"})
        self.patch_configuration(kinds.SITE, {"test.hello": "1"})
        self.configuration.load()

        assert self.configuration.get_value("test.hello") == "1"

    def test_user_overides_global(self) -> None:
        self.patch_configuration(kinds.GLOBAL, {"test.hello": "3"})
        self.patch_configuration(kinds.USER, {"test.hello": "2"})
        self.configuration.load()

        assert self.configuration.get_value("test.hello") == "2"

    def test_env_not_overriden_by_environment_var(
        self, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        self.patch_configuration(kinds.ENV, {"test.hello": "1"})
        monkeypatch.setenv("PIP_HELLO", "5")

        self.configuration.load()

        assert self.configuration.get_value("test.hello") == "1"
        assert self.configuration.get_value(":env:.hello") == "5"

    def test_site_not_overriden_by_environment_var(
        self, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        self.patch_configuration(kinds.SITE, {"test.hello": "2"})
        monkeypatch.setenv("PIP_HELLO", "5")

        self.configuration.load()

        assert self.configuration.get_value("test.hello") == "2"
        assert self.configuration.get_value(":env:.hello") == "5"

    def test_user_not_overriden_by_environment_var(
        self, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        self.patch_configuration(kinds.USER, {"test.hello": "3"})
        monkeypatch.setenv("PIP_HELLO", "5")

        self.configuration.load()

        assert self.configuration.get_value("test.hello") == "3"
        assert self.configuration.get_value(":env:.hello") == "5"

    def test_global_not_overriden_by_environment_var(
        self, monkeypatch: pytest.MonkeyPatch
    ) -> None:
        self.patch_configuration(kinds.GLOBAL, {"test.hello": "4"})
        monkeypatch.setenv("PIP_HELLO", "5")

        self.configuration.load()

        assert self.configuration.get_value("test.hello") == "4"
        assert self.configuration.get_value(":env:.hello") == "5"


class TestConfigurationModification(ConfigurationMixin):
    # Tests for methods to that modify the state of a Configuration

    def test_no_specific_given_modification(self) -> None:
        self.configuration.load()

        with pytest.raises(ConfigurationError):
            self.configuration.set_value("test.hello", "10")

    def test_site_modification(self) -> None:
        self.configuration.load_only = kinds.SITE
        self.configuration.load()

        # Mock out the method
        mymock = MagicMock(spec=self.configuration._mark_as_modified)
        # https://github.com/python/mypy/issues/2427
        self.configuration._mark_as_modified = mymock  # type: ignore[assignment]

        self.configuration.set_value("test.hello", "10")

        # get the path to site config file
        assert mymock.call_count == 1
        assert mymock.call_args[0][0] == (get_configuration_files()[kinds.SITE][0])

    def test_user_modification(self) -> None:
        # get the path to local config file
        self.configuration.load_only = kinds.USER
        self.configuration.load()

        # Mock out the method
        mymock = MagicMock(spec=self.configuration._mark_as_modified)
        # https://github.com/python/mypy/issues/2427
        self.configuration._mark_as_modified = mymock  # type: ignore[assignment]

        self.configuration.set_value("test.hello", "10")

        # get the path to user config file
        assert mymock.call_count == 1
        assert mymock.call_args[0][0] == (
            # Use new config file
            get_configuration_files()[kinds.USER][1]
        )

    def test_global_modification(self) -> None:
        # get the path to local config file
        self.configuration.load_only = kinds.GLOBAL
        self.configuration.load()

        # Mock out the method
        mymock = MagicMock(spec=self.configuration._mark_as_modified)
        # https://github.com/python/mypy/issues/2427
        self.configuration._mark_as_modified = mymock  # type: ignore[assignment]

        self.configuration.set_value("test.hello", "10")

        # get the path to user config file
        assert mymock.call_count == 1
        assert mymock.call_args[0][0] == (get_configuration_files()[kinds.GLOBAL][-1])

    def test_normalization(self) -> None:
        # underscores and dashes can be used interchangeably.
        # internally, underscores get converted into dashes before reading/writing file
        self.configuration.load_only = kinds.GLOBAL
        self.configuration.load()
        self.configuration.set_value("global.index_url", "example.org")
        assert self.configuration.get_value("global.index_url") == "example.org"
        assert self.configuration.get_value("global.index-url") == "example.org"
        self.configuration.unset_value("global.index-url")
        pat = r"^No such key - global\.index-url$"
        with pytest.raises(ConfigurationError, match=pat):
            self.configuration.get_value("global.index-url")
