# Copyright 2016 Alethea Katherine Flowers
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import os
import sys
from unittest import mock

import contexter
import pkg_resources
import pytest

import nox
import nox.__main__
import nox._options
import nox.registry
import nox.sessions


RESOURCES = os.path.join(os.path.dirname(__file__), "resources")
VERSION = pkg_resources.get_distribution("nox").version


# This is needed because CI systems will mess up these tests due to the
# way nox handles the --session parameter's default value. This avoids that
# mess.
os.environ.pop("NOXSESSION", None)


def test_main_no_args(monkeypatch):
    monkeypatch.setattr(sys, "argv", [sys.executable])
    with mock.patch("nox.workflow.execute") as execute:
        execute.return_value = 0

        # Call the function.
        with mock.patch.object(sys, "exit") as exit:
            nox.__main__.main()
            exit.assert_called_once_with(0)
        assert execute.called

        # Verify that the config looks correct.
        config = execute.call_args[1]["global_config"]
        assert config.noxfile == "noxfile.py"
        assert config.sessions is None
        assert not config.reuse_existing_virtualenvs
        assert not config.stop_on_first_error
        assert config.posargs == []


def test_main_long_form_args():
    sys.argv = [
        sys.executable,
        "--noxfile",
        "noxfile.py",
        "--envdir",
        ".other",
        "--sessions",
        "1",
        "2",
        "--reuse-existing-virtualenvs",
        "--stop-on-first-error",
    ]
    with mock.patch("nox.workflow.execute") as execute:
        execute.return_value = 0

        # Call the main function.
        with mock.patch.object(sys, "exit") as exit:
            nox.__main__.main()
            exit.assert_called_once_with(0)
        assert execute.called

        # Verify that the config looks correct.
        config = execute.call_args[1]["global_config"]
        assert config.noxfile == "noxfile.py"
        assert config.envdir.endswith(".other")
        assert config.sessions == ["1", "2"]
        assert config.reuse_existing_virtualenvs is True
        assert config.stop_on_first_error is True
        assert config.posargs == []


def test_main_short_form_args(monkeypatch):
    monkeypatch.setattr(
        sys, "argv", [sys.executable, "-f", "noxfile.py", "-s", "1", "2", "-r"]
    )
    with mock.patch("nox.workflow.execute") as execute:
        execute.return_value = 0

        # Call the main function.
        with mock.patch.object(sys, "exit") as exit:
            nox.__main__.main()
            exit.assert_called_once_with(0)
        assert execute.called

        # Verify that the config looks correct.
        config = execute.call_args[1]["global_config"]
        assert config.noxfile == "noxfile.py"
        assert config.sessions == ["1", "2"]
        assert config.reuse_existing_virtualenvs is True


def test_main_explicit_sessions(monkeypatch):
    monkeypatch.setattr(sys, "argv", [sys.executable, "-e", "1", "2"])
    with mock.patch("nox.workflow.execute") as execute:
        execute.return_value = 0

        # Call the main function.
        with mock.patch.object(sys, "exit") as exit:
            nox.__main__.main()
            exit.assert_called_once_with(0)
        assert execute.called

        # Verify that the explicit sessions are listed in the config.
        config = execute.call_args[1]["global_config"]
        assert config.sessions == ["1", "2"]


def test_main_explicit_sessions_with_spaces_in_names(monkeypatch):
    monkeypatch.setattr(
        sys, "argv", [sys.executable, "-e", "unit tests", "the unit tests"]
    )
    with mock.patch("nox.workflow.execute") as execute:
        execute.return_value = 0

        # Call the main function.
        with mock.patch.object(sys, "exit") as exit:
            nox.__main__.main()
            exit.assert_called_once_with(0)
        assert execute.called

        # Verify that the explicit sessions are listed in the config.
        config = execute.call_args[1]["global_config"]
        assert config.sessions == ["unit tests", "the unit tests"]


@pytest.mark.parametrize(
    "env,sessions", [("foo", ["foo"]), ("foo,bar", ["foo", "bar"])]
)
def test_main_session_from_nox_env_var(monkeypatch, env, sessions):
    monkeypatch.setenv("NOXSESSION", env)
    monkeypatch.setattr(sys, "argv", [sys.executable])

    with mock.patch("nox.workflow.execute") as execute:
        execute.return_value = 0

        # Call the main function.
        with mock.patch.object(sys, "exit") as exit:
            nox.__main__.main()
            exit.assert_called_once_with(0)
        assert execute.called

        # Verify that the sessions from the env var are listed in the config.
        config = execute.call_args[1]["global_config"]
        assert len(config.sessions) == len(sessions)
        for session in sessions:
            assert session in config.sessions


def test_main_positional_args(monkeypatch):
    monkeypatch.setattr(sys, "argv", [sys.executable, "1", "2", "3"])
    with mock.patch("nox.workflow.execute") as execute:
        execute.return_value = 0

        # Call the main function.
        with mock.patch.object(sys, "exit") as exit:
            nox.__main__.main()
            exit.assert_called_once_with(0)
        assert execute.called

        # Verify that the positional args are listed in the config.
        config = execute.call_args[1]["global_config"]
        assert config.posargs == ["1", "2", "3"]


def test_main_positional_with_double_hyphen(monkeypatch):
    monkeypatch.setattr(sys, "argv", [sys.executable, "--", "1", "2", "3"])
    with mock.patch("nox.workflow.execute") as execute:
        execute.return_value = 0

        # Call the main function.
        with mock.patch.object(sys, "exit") as exit:
            nox.__main__.main()
            exit.assert_called_once_with(0)
        assert execute.called

        # Verify that the positional args are listed in the config.
        config = execute.call_args[1]["global_config"]
        assert config.posargs == ["1", "2", "3"]


def test_main_positional_flag_like_with_double_hyphen(monkeypatch):
    monkeypatch.setattr(
        sys, "argv", [sys.executable, "--", "1", "2", "3", "-f", "--baz"]
    )
    with mock.patch("nox.workflow.execute") as execute:
        execute.return_value = 0

        # Call the main function.
        with mock.patch.object(sys, "exit") as exit:
            nox.__main__.main()
            exit.assert_called_once_with(0)
        assert execute.called

        # Verify that the positional args are listed in the config.
        config = execute.call_args[1]["global_config"]
        assert config.posargs == ["1", "2", "3", "-f", "--baz"]


def test_main_version(capsys, monkeypatch):
    monkeypatch.setattr(sys, "argv", [sys.executable, "--version"])

    with contexter.ExitStack() as stack:
        execute = stack.enter_context(mock.patch("nox.workflow.execute"))
        exit_mock = stack.enter_context(mock.patch("sys.exit"))
        nox.__main__.main()
        _, err = capsys.readouterr()
        assert VERSION in err
        exit_mock.assert_not_called()
        execute.assert_not_called()


def test_main_help(capsys, monkeypatch):
    monkeypatch.setattr(sys, "argv", [sys.executable, "--help"])

    with contexter.ExitStack() as stack:
        execute = stack.enter_context(mock.patch("nox.workflow.execute"))
        exit_mock = stack.enter_context(mock.patch("sys.exit"))
        nox.__main__.main()
        out, _ = capsys.readouterr()
        assert "help" in out
        exit_mock.assert_not_called()
        execute.assert_not_called()


def test_main_failure(monkeypatch):
    monkeypatch.setattr(sys, "argv", [sys.executable])
    with mock.patch("nox.workflow.execute") as execute:
        execute.return_value = 1
        with mock.patch.object(sys, "exit") as exit:
            nox.__main__.main()
            exit.assert_called_once_with(1)


def test_main_nested_config(capsys, monkeypatch):
    monkeypatch.setattr(
        sys,
        "argv",
        [
            "nox",
            "--noxfile",
            os.path.join(RESOURCES, "noxfile_nested.py"),
            "-s",
            "snack(cheese='cheddar')",
        ],
    )

    with mock.patch("sys.exit") as sys_exit:
        nox.__main__.main()
        stdout, stderr = capsys.readouterr()
        assert stdout == "Noms, cheddar so good!\n"
        assert "Session snack(cheese='cheddar') was successful." in stderr
        sys_exit.assert_called_once_with(0)


def test_main_session_with_names(capsys, monkeypatch):
    monkeypatch.setattr(
        sys,
        "argv",
        [
            "nox",
            "--noxfile",
            os.path.join(RESOURCES, "noxfile_spaces.py"),
            "-s",
            "cheese list(cheese='cheddar')",
        ],
    )

    with mock.patch("sys.exit") as sys_exit:
        nox.__main__.main()
        stdout, stderr = capsys.readouterr()
        assert stdout == "Noms, cheddar so good!\n"
        assert "Session cheese list(cheese='cheddar') was successful." in stderr
        sys_exit.assert_called_once_with(0)


def test_main_noxfile_options(monkeypatch):
    monkeypatch.setattr(
        sys,
        "argv",
        [
            "nox",
            "-l",
            "-s",
            "test",
            "--noxfile",
            os.path.join(RESOURCES, "noxfile_options.py"),
        ],
    )

    with mock.patch("nox.tasks.honor_list_request") as honor_list_request:
        honor_list_request.return_value = 0

        with mock.patch("sys.exit"):
            nox.__main__.main()

        assert honor_list_request.called

        # Verify that the config looks correct.
        config = honor_list_request.call_args[1]["global_config"]
        assert config.reuse_existing_virtualenvs is True


def test_main_noxfile_options_disabled_by_flag(monkeypatch):
    monkeypatch.setattr(
        sys,
        "argv",
        [
            "nox",
            "-l",
            "-s",
            "test",
            "--no-reuse-existing-virtualenvs",
            "--noxfile",
            os.path.join(RESOURCES, "noxfile_options.py"),
        ],
    )

    with mock.patch("nox.tasks.honor_list_request") as honor_list_request:
        honor_list_request.return_value = 0

        with mock.patch("sys.exit"):
            nox.__main__.main()

        assert honor_list_request.called

        # Verify that the config looks correct.
        config = honor_list_request.call_args[1]["global_config"]
        assert config.reuse_existing_virtualenvs is False


def test_main_noxfile_options_sessions(monkeypatch):
    monkeypatch.setattr(
        sys,
        "argv",
        ["nox", "-l", "--noxfile", os.path.join(RESOURCES, "noxfile_options.py")],
    )

    with mock.patch("nox.tasks.honor_list_request") as honor_list_request:
        honor_list_request.return_value = 0

        with mock.patch("sys.exit"):
            nox.__main__.main()

        assert honor_list_request.called

        # Verify that the config looks correct.
        config = honor_list_request.call_args[1]["global_config"]
        assert config.sessions == ["test"]


@pytest.mark.parametrize(("isatty_value", "expected"), [(True, True), (False, False)])
def test_main_color_from_isatty(monkeypatch, isatty_value, expected):
    monkeypatch.setattr(sys, "argv", [sys.executable])
    with mock.patch("nox.workflow.execute") as execute:
        execute.return_value = 0
        with mock.patch("sys.stdin.isatty") as isatty:
            isatty.return_value = isatty_value

            # Call the main function.
            with mock.patch.object(sys, "exit"):
                nox.__main__.main()

            config = execute.call_args[1]["global_config"]
            assert config.color == expected


@pytest.mark.parametrize(
    ("color_opt", "expected"),
    [
        ("--forcecolor", True),
        ("--nocolor", False),
        ("--force-color", True),
        ("--no-color", False),
    ],
)
def test_main_color_options(monkeypatch, color_opt, expected):
    monkeypatch.setattr(sys, "argv", [sys.executable, color_opt])
    with mock.patch("nox.workflow.execute") as execute:
        execute.return_value = 0

        # Call the main function.
        with mock.patch.object(sys, "exit"):
            nox.__main__.main()

        config = execute.call_args[1]["global_config"]
        assert config.color == expected


def test_main_color_conflict(capsys, monkeypatch):
    monkeypatch.setattr(sys, "argv", [sys.executable, "--forcecolor", "--nocolor"])
    with mock.patch("nox.workflow.execute") as execute:
        execute.return_value = 1

        # Call the main function.
        with mock.patch.object(sys, "exit") as exit:
            nox.__main__.main()
            exit.assert_called_with(1)

    _, err = capsys.readouterr()

    assert "color" in err
