"""Test the hub hook."""
import logging
import os
from pathlib import Path
from unittest.mock import PropertyMock, call, patch

import pytest
from click.testing import CliRunner
from edgetest.interface import cli
from edgetest.schema import EdgetestValidator, Schema
from edgetest.utils import parse_cfg

from edgetest_hub.plugin import addoption, create_issue

CFG = """
[edgetest.envs.myenv]
upgrade =
    myupgrade
command =
    pytest tests -m 'not integration'
"""

CFG_HUB_ISSUE_TRUE = """
[edgetest.hub]
git_repo_org = test-org
git_repo_name = test-repo
pr_reviewers = abc123,efg456
open_issue_on_fail = True
[edgetest.envs.myenv]
upgrade =
    myupgrade
command =
    pytest tests -m 'not integration'
"""

CFG_HUB_ISSUE_FALSE = """
[edgetest.hub]
git_repo_org = test-org
git_repo_name = test-repo
pr_reviewers = abc123,efg456
open_issue_on_fail = False
[edgetest.envs.myenv]
upgrade =
    myupgrade
command =
    pytest tests -m 'not integration'
"""

CFG_HUB_URL = """
[edgetest.hub]
git_url = mycustomgit.com
git_repo_org = test-org
git_repo_name = test-repo
pr_reviewers = abc123,efg456
open_issue_on_fail = True
[edgetest.envs.myenv]
upgrade =
    myupgrade
command =
    pytest tests -m 'not integration'
"""

PIP_LIST = """
[{"name": "myupgrade", "version": "0.2.0"}]
"""

TABLE_OUTPUT = """

============= =============== =================== =================
 Environment   Passing tests   Upgraded packages   Package version
------------- --------------- ------------------- -----------------
 myenv         True            myupgrade           0.2.0
============= =============== =================== =================

"""


@pytest.mark.parametrize("config", [CFG, CFG_HUB_ISSUE_TRUE])
def test_addoption(config, tmpdir):
    """Test the addoption hook."""
    location = tmpdir.mkdir("mylocation")
    conf_loc = Path(str(location), "myconfig.ini")
    with open(conf_loc, "w") as outfile:
        outfile.write(config)

    schema = Schema()
    addoption(schema=schema)

    cfg = parse_cfg(filename=conf_loc)
    validator = EdgetestValidator(schema=schema.schema)

    assert validator.validate(cfg)


@patch("edgetest_hub.plugin._run_command", autospec=True)
@patch("edgetest.lib.EnvBuilder", autospec=True)
@patch("edgetest.core.Popen", autospec=True)
@patch("edgetest.utils.Popen", autospec=True)
def test_hub_notoken(mock_popen, mock_cpopen, mock_builder, mock_run_command):
    """Test hub and git in setting up PR of changes."""
    mock_popen.return_value.communicate.return_value = (PIP_LIST, "error")
    type(mock_popen.return_value).returncode = PropertyMock(return_value=0)
    mock_cpopen.return_value.communicate.return_value = ("output", "error")
    type(mock_cpopen.return_value).returncode = PropertyMock(return_value=0)

    runner = CliRunner()

    with runner.isolated_filesystem() as loc:
        with open("setup.cfg", "w") as outfile:
            outfile.write(CFG_HUB_ISSUE_TRUE)

        result = runner.invoke(cli, ["--config=setup.cfg"])

    assert result.exit_code == 0
    assert mock_run_command.called is False


@patch.dict(os.environ, {"GITHUB_TOKEN": "abcd1234"})
@patch("edgetest_hub.plugin._run_command", autospec=True)
@patch("edgetest.lib.EnvBuilder", autospec=True)
@patch("edgetest.core.Popen", autospec=True)
@patch("edgetest.utils.Popen", autospec=True)
def test_hub_withtoken_nopr(mock_popen, mock_cpopen, mock_builder, mock_run_command):
    """Test hub and git in setting up PR of changes."""
    mock_popen.return_value.communicate.return_value = (PIP_LIST, "error")
    type(mock_popen.return_value).returncode = PropertyMock(return_value=0)
    mock_cpopen.return_value.communicate.return_value = ("output", "error")
    type(mock_cpopen.return_value).returncode = PropertyMock(return_value=0)

    mock_run_command.side_effect = [(None, None)] * 10  # 10 calls
    expected_calls_no_pr = [
        call("git", "config", "user.name", "Jenkins"),
        call("git", "config", "user.email", "noreply@capitalone.com"),
        call(
            "git",
            "remote",
            "set-url",
            "origin",
            "https://abcd1234@github.com/test-org/test-repo.git",
        ),
        call("git", "config", "--global", "hub.protocol", "https"),
        call(
            "git",
            "config",
            "--global",
            "--add",
            "hub.host",
            "github.com",
        ),
        call(
            "git",
            "push",
            "https://abcd1234@github.com/test-org/test-repo.git",
            "--delete",
            "dep-updates",
        ),
        call("git", "branch", "-D", "dep-updates"),
        call("git", "clean", "-fd"),
        call("git", "checkout", "-b", "dep-updates", "develop"),
        call("git", "diff-index", "--quiet", "HEAD"),
    ]

    runner = CliRunner()

    with runner.isolated_filesystem() as loc:
        with open("setup.cfg", "w") as outfile:
            outfile.write(CFG_HUB_ISSUE_TRUE)

        result = runner.invoke(cli, ["--config=setup.cfg"])

    assert mock_run_command.called is True
    assert mock_run_command.mock_calls == expected_calls_no_pr


@patch.dict(os.environ, {"GITHUB_TOKEN": "abcd1234"})
@patch("edgetest_hub.plugin._run_command", autospec=True)
@patch("edgetest.lib.EnvBuilder", autospec=True)
@patch("edgetest.core.Popen", autospec=True)
@patch("edgetest.utils.Popen", autospec=True)
def test_hub_withtoken_withpr(mock_popen, mock_cpopen, mock_builder, mock_run_command):
    """Test hub and git in setting up PR of changes."""
    mock_popen.return_value.communicate.return_value = (PIP_LIST, "error")
    type(mock_popen.return_value).returncode = PropertyMock(return_value=0)
    mock_cpopen.return_value.communicate.return_value = ("output", "error")
    type(mock_cpopen.return_value).returncode = PropertyMock(return_value=0)

    mock_run_command.side_effect = (
        [(None, None)] * 9 + [RuntimeError()] + [(None, None)] * 4
    )  # 14 calls
    expected_calls_with_pr = [
        call("git", "config", "user.name", "Jenkins"),
        call("git", "config", "user.email", "noreply@capitalone.com"),
        call(
            "git",
            "remote",
            "set-url",
            "origin",
            "https://abcd1234@github.com/test-org/test-repo.git",
        ),
        call("git", "config", "--global", "hub.protocol", "https"),
        call(
            "git",
            "config",
            "--global",
            "--add",
            "hub.host",
            "github.com",
        ),
        call(
            "git",
            "push",
            "https://abcd1234@github.com/test-org/test-repo.git",
            "--delete",
            "dep-updates",
        ),
        call("git", "branch", "-D", "dep-updates"),
        call("git", "clean", "-fd"),
        call("git", "checkout", "-b", "dep-updates", "develop"),
        call("git", "diff-index", "--quiet", "HEAD"),
        call("git", "add", "setup.cfg", "requirements.txt"),
        call("git", "commit", "-m", "environmentally friendly"),
        call("git", "push", "origin", "dep-updates"),
        call(
            "hub",
            "pull-request",
            "-b",
            "develop",
            "-m",
            "[EDGETEST] Updating test-repo dependency versions",
            "-r",
            "abc123,efg456",
            "--push",
        ),
    ]

    runner = CliRunner()

    with runner.isolated_filesystem() as loc:
        with open("setup.cfg", "w") as outfile:
            outfile.write(CFG_HUB_ISSUE_TRUE)

        result = runner.invoke(cli, ["--config=setup.cfg"])

    assert mock_run_command.called is True
    assert mock_run_command.mock_calls == expected_calls_with_pr


@patch.dict(os.environ, {"GITHUB_TOKEN": "abcd1234"})
@patch("edgetest_hub.plugin._run_command", autospec=True)
@patch("edgetest.lib.EnvBuilder", autospec=True)
@patch("edgetest.core.Popen", autospec=True)
@patch("edgetest.utils.Popen", autospec=True)
def test_hub_custom_url(mock_popen, mock_cpopen, mock_builder, mock_run_command):
    """Test hub and git in setting up PR of changes."""
    mock_popen.return_value.communicate.return_value = (PIP_LIST, "error")
    type(mock_popen.return_value).returncode = PropertyMock(return_value=0)
    mock_cpopen.return_value.communicate.return_value = ("output", "error")
    type(mock_cpopen.return_value).returncode = PropertyMock(return_value=0)

    mock_run_command.side_effect = [(None, None)] * 10  # 10 calls
    expected_calls_no_pr = [
        call("git", "config", "user.name", "Jenkins"),
        call("git", "config", "user.email", "noreply@capitalone.com"),
        call(
            "git",
            "remote",
            "set-url",
            "origin",
            "https://abcd1234@mycustomgit.com/test-org/test-repo.git",
        ),
        call("git", "config", "--global", "hub.protocol", "https"),
        call(
            "git",
            "config",
            "--global",
            "--add",
            "hub.host",
            "mycustomgit.com",
        ),
        call(
            "git",
            "push",
            "https://abcd1234@mycustomgit.com/test-org/test-repo.git",
            "--delete",
            "dep-updates",
        ),
        call("git", "branch", "-D", "dep-updates"),
        call("git", "clean", "-fd"),
        call("git", "checkout", "-b", "dep-updates", "develop"),
        call("git", "diff-index", "--quiet", "HEAD"),
    ]

    runner = CliRunner()

    with runner.isolated_filesystem() as loc:
        with open("setup.cfg", "w") as outfile:
            outfile.write(CFG_HUB_URL)

        result = runner.invoke(cli, ["--config=setup.cfg"])

    assert mock_run_command.called is True
    assert mock_run_command.mock_calls == expected_calls_no_pr


@patch.dict(os.environ, {"GITHUB_TOKEN": "abcd1234"})
@patch("edgetest_hub.plugin._run_command", autospec=True)
@patch("edgetest.lib.EnvBuilder", autospec=True)
@patch("edgetest.core.Popen", autospec=True)
@patch("edgetest.utils.Popen", autospec=True)
def test_hub_issue(mock_popen, mock_cpopen, mock_builder, mock_run_command):
    """Test hub and opening of issue."""
    mock_popen.return_value.communicate.return_value = (PIP_LIST, "error")
    type(mock_popen.return_value).returncode = PropertyMock(return_value=0)
    mock_cpopen.return_value.communicate.return_value = ("output", "error")
    type(mock_cpopen.return_value).returncode = PropertyMock(return_value=0)

    runner = CliRunner()

    with runner.isolated_filesystem() as loc:
        with open("setup.cfg", "w") as outfile:
            outfile.write(CFG_HUB_ISSUE_TRUE)

        result = runner.invoke(cli, ["--config=setup.cfg", "--notest"])

    expected_call = [
        call(
            "hub",
            "issue",
            "create",
            "--message",
            "[EDGETEST] Issue updating dependencies",
            "--message",
            "Edgetest ran, but there were some issues with the tests passing. Edgetest created an issue to let you know.",
            "--message",
            "| Environment   | Passing tests   | Upgraded packages   | Package version   |\n|---------------|-----------------|---------------------|-------------------|\n| myenv         | False           | myupgrade           | 0.2.0             |",
        )
    ]
    mock_run_command.assert_has_calls(expected_call)


@patch.dict(os.environ, {"GITHUB_TOKEN": "abcd1234"})
@patch("edgetest_hub.plugin._run_command", autospec=True)
@patch("edgetest.lib.EnvBuilder", autospec=True)
@patch("edgetest.core.Popen", autospec=True)
@patch("edgetest.utils.Popen", autospec=True)
def test_hub_issue_false(mock_popen, mock_cpopen, mock_builder, mock_run_command):
    """Test hub and opening of issue when the flag is False"""
    mock_popen.return_value.communicate.return_value = (PIP_LIST, "error")
    type(mock_popen.return_value).returncode = PropertyMock(return_value=0)
    mock_cpopen.return_value.communicate.return_value = ("output", "error")
    type(mock_cpopen.return_value).returncode = PropertyMock(return_value=0)

    runner = CliRunner()

    with runner.isolated_filesystem() as loc:
        with open("setup.cfg", "w") as outfile:
            outfile.write(CFG_HUB_ISSUE_FALSE)

        result = runner.invoke(cli, ["--config=setup.cfg", "--notest"])

    mock_run_command.assert_not_called()


@patch("edgetest_hub.plugin._run_command", autospec=True)
def test_hub_issue_logging_and_exception(mock_run_command, caplog):
    """Test hub and opening of issue."""
    mock_run_command.return_value = ("", "")
    with caplog.at_level(logging.INFO):
        create_issue("test")
    assert mock_run_command.called
    assert "Creating issue." in caplog.text

    mock_run_command.side_effect = RuntimeError()
    with caplog.at_level(logging.INFO):
        create_issue("test")
    assert mock_run_command.called
    assert "There was a problem creating an Issue." in caplog.text
