import json
import logging
import mock
import pytest
import requests_mock
import requests
import os

from pubtools._quay import exceptions
from pubtools._quay.utils import misc
from .utils.misc import sort_dictionary_sortable_values, compare_logs, mock_entry_point

# flake8: noqa: E501

LOG = logging.getLogger()
logging.basicConfig()
LOG.setLevel(logging.INFO)


def test_run_entrypoint(caplog):
    def side_effect_entrypoint(*args):
        LOG.warning("log generated by entrypoint")
        LOG.info("environment variable: {0}".format(os.environ["PYXIS_PASSWORD"]))
        return {"pyxis": "data"}

    with mock_entry_point(
        "pubtools-pyxis", "console_scripts", "pubtools-pyxis-get-operator-indices"
    ) as entrypoint:
        entrypoint.side_effect = side_effect_entrypoint
        ret_val = misc.run_entrypoint(
            ("pubtools-pyxis", "console_scripts", "pubtools-pyxis-get-operator-indices"),
            "pubtools-pyxis-get-operator-indices",
            [
                "--pyxis-krb-principal",
                "some-principal@REDHAT.COM",
                "--organization",
                "redhat-operators",
                "--ocp-versions-range",
                "v4.5",
                "--pyxis-krb-ktfile",
                "/etc/pub/some.keytab",
            ],
            {"PYXIS_PASSWORD": "some-password"},
        )
        expected_logs = [
            "Running task with arguments:",
            "pubtools-pyxis-get-operator-indices --pyxis-krb-principal some-principal@REDHAT\.COM --organization \\\\",
            "redhat-operators --ocp-versions-range v4.5 --pyxis-krb-ktfile /etc/pub/some\.keytab",
            "log generated by entrypoint",
            "environment variable: some-password",
        ]
        compare_logs(caplog, expected_logs)


def test_run_entrypoint_mod(caplog):
    def side_effect_entrypoint(*args):
        LOG.warning("log generated by entrypoint")
        LOG.info("environment variable: {0}".format(os.environ["PYXIS_PASSWORD"]))
        return {"return": "value"}

    with mock_entry_point(
        "pubtools-sign", "modules", "pubtools-sign-cosign-signature-list"
    ) as entrypoint:
        entrypoint.side_effect = side_effect_entrypoint
        ret_val = misc.run_entrypoint(
            ("pubtools-sign", "modules", "pubtools-sign-cosign-signature-list"),
            None,
            ["some-ep-argument"],
            {"PYXIS_PASSWORD": "some-password"},
        )
        expected_logs = [
            "Running task with arguments:",
            "pubtools-sign-cosign-signature-list some-ep-argument",
            "log generated by entrypoint",
            "environment variable: some-password",
        ]
        compare_logs(caplog, expected_logs)


def test_run_entrypoint_without_args(caplog):
    def side_effect_entrypoint(*args):
        LOG.warning("log generated by entrypoint")
        LOG.info("environment variable: {0}".format(os.environ["PYXIS_PASSWORD"]))
        return {"pyxis": "data"}

    with mock_entry_point(
        "pubtools-pyxis", "console_scripts", "pubtools-pyxis-get-operator-indices"
    ) as entrypoint:
        entrypoint.side_effect = side_effect_entrypoint
        ret_val = misc.run_entrypoint(
            ("pubtools-pyxis", "console_scripts", "pubtools-pyxis-get-operator-indices"),
            "pubtools-pyxis-get-operator-indices",
            [],
            {"PYXIS_PASSWORD": "some-password"},
        )
        expected_logs = [
            "Running task with arguments:",
            "pubtools-pyxis-get-operator-indices",
            "log generated by entrypoint",
            "environment variable: some-password",
        ]
        compare_logs(caplog, expected_logs)


def test_run_entrypoint_not_capture_stdout(capsys):
    def side_effect_entrypoint(*args):
        print("print generated by entrypoint")

    with mock_entry_point(
        "pubtools-pyxis", "console_scripts", "pubtools-pyxis-upload-signatures"
    ) as entrypoint:
        entrypoint.side_effect = side_effect_entrypoint
        misc.run_entrypoint(
            ("pubtools-pyxis", "console_scripts", "pubtools-pyxis-upload-signatures"),
            "pubtools-pyxis-upload-signature",
            [],
            {},
            False,
        )
        captured = capsys.readouterr()
        assert captured.out == "print generated by entrypoint\n"


def test_get_internal_repo_name():
    internal_name = misc.get_internal_container_repo_name("namespace/repo")
    assert internal_name == "namespace----repo"


def test_get_internal_repo_name_no_delimeter():
    internal_name = misc.get_internal_container_repo_name("namespace-repo")
    assert internal_name == "namespace-repo"


def test_get_internal_repo_name_errors():
    with pytest.raises(ValueError, match="Input repository containing a delimeter.*"):
        misc.get_internal_container_repo_name("/namespacerepo")

    with pytest.raises(ValueError, match="Input repository containing a delimeter.*"):
        misc.get_internal_container_repo_name("namespacerepo/")

    with pytest.raises(ValueError, match="Input repository containing a delimeter.*"):
        misc.get_internal_container_repo_name("name/space/repo")


def test_get_external_repo_name():
    internal_name = misc.get_external_container_repo_name("namespace----repo")
    assert internal_name == "namespace/repo"


def test_get_external_repo_name_no_delimeter():
    internal_name = misc.get_external_container_repo_name("namespace-repo")
    assert internal_name == "namespace-repo"


def test_get_external_repo_name_errors():
    with pytest.raises(ValueError, match="Input repository containing a delimeter.*"):
        misc.get_external_container_repo_name("----namespacerepo")

    with pytest.raises(ValueError, match="Input repository containing a delimeter.*"):
        misc.get_external_container_repo_name("namespacerepo----")

    with pytest.raises(ValueError, match="Input repository containing a delimeter.*"):
        misc.get_external_container_repo_name("name----space----repo")


def test_get_pyxis_ssl_paths_hook(fake_cert_key_paths):
    cert, key = misc.get_pyxis_ssl_paths({"pyxis_server": "some-url"})
    assert cert == "/path/to/file.crt"
    assert key == "/path/to/file.key"


def test_get_pyxis_ssl_paths_target_settings():
    cert, key = misc.get_pyxis_ssl_paths(
        {
            "pyxis_server": "some-url",
            "pyxis_ssl_cert": "/some/file.crt",
            "pyxis_ssl_key": "/some/file.key",
        }
    )
    assert cert == "/some/file.crt"
    assert key == "/some/file.key"


def test_get_pyxis_ssl_paths_error():
    with pytest.raises(ValueError, match="No key and certificate paths were.*"):
        cert, key = misc.get_pyxis_ssl_paths({"pyxis_server": "some-url"})


@mock.patch("time.sleep")
@mock.patch("pubtools._quay.utils.misc.timestamp")
def test_run_with_retries(mock_timestamp, mock_sleep):
    mock_timestamp.side_effect = [ValueError, 10]
    misc.run_with_retries(misc.timestamp, "Get timestamp")

    assert mock_timestamp.call_count == 2
    mock_sleep.assert_called_once_with(0)


@mock.patch("time.sleep")
@mock.patch("pubtools._quay.utils.misc.timestamp")
def test_run_with_retries_fail(mock_timestamp, mock_sleep):
    mock_timestamp.side_effect = [ValueError] * 4
    with pytest.raises(ValueError):
        misc.run_with_retries(misc.timestamp, "Get timestamp")

    assert mock_timestamp.call_count == 4
    assert mock_sleep.call_count == 3
    assert mock_sleep.call_args_list[0] == mock.call(0)
    assert mock_sleep.call_args_list[1] == mock.call(10)
    assert mock_sleep.call_args_list[2] == mock.call(20)


@mock.patch("time.sleep")
@mock.patch("pubtools._quay.utils.misc.timestamp")
def test_retry(mock_timestamp, mock_sleep):
    mock_timestamp.side_effect = [1, 2, 3, 4]

    @misc.retry("Add value to timestamp")
    def add_value_to_timestamp(value):
        timestamp = misc.timestamp()
        if isinstance(value, str):
            raise ValueError("Not a number!")
        return timestamp + value

    with pytest.raises(ValueError):
        add_value_to_timestamp("not a number")

    assert mock_timestamp.call_count == 4
    assert mock_sleep.call_count == 3
    assert mock_sleep.call_args_list[0] == mock.call(0)
    assert mock_sleep.call_args_list[1] == mock.call(10)
    assert mock_sleep.call_args_list[2] == mock.call(20)


@mock.patch(
    "builtins.open",
    new_callable=mock.mock_open,
    read_data='{"auths": {"registry": {"auth": "dXNlcjpwYXNz"}}}',
)
@mock.patch("os.path.isfile")
def test_get_basic_auth(mock_isfile, mock_file):
    mock_isfile.return_value = True
    assert misc.get_basic_auth("registry") == ["user", "pass"]


@mock.patch("pubtools._quay.utils.misc.run_entrypoint")
def test_get_repo_metadata(
    mock_run_entrypoint,
    target_settings,
    container_multiarch_push_item,
    operator_push_item_ok,
    fake_cert_key_paths,
):
    hub = mock.MagicMock()
    mock_run_entrypoint.return_value = {"key": "value"}
    res = misc.pyxis_get_repo_metadata("some_repo", target_settings)

    assert res == {"key": "value"}
    mock_run_entrypoint.assert_called_once_with(
        ("pubtools-pyxis", "console_scripts", "pubtools-pyxis-get-repo-metadata"),
        "pubtools-pyxis-get-repo-metadata",
        [
            "--pyxis-server",
            "pyxis-url.com",
            "--pyxis-ssl-crtfile",
            "/path/to/file.crt",
            "--pyxis-ssl-keyfile",
            "/path/to/file.key",
            "--repo-name",
            "some_repo",
        ],
        {},
    )


def test_set_aws_kms_environment_variables_missing_setting(target_settings, caplog):
    caplog.set_level(logging.WARNING)
    misc.set_aws_kms_environment_variables(target_settings, "test_profile")
    expected_logs = [
        "Target settings are missing the aws_kms_credentials property, "
        "cannot set AWS KMS environment variables"
    ]
    compare_logs(caplog, expected_logs)


def test_set_aws_kms_environment_variables_missing_profile(target_settings, caplog):
    caplog.set_level(logging.WARNING)
    target_settings["aws_kms_credentials"] = {
        "other_profile": {
            "aws_access_key_id": "id1",
            "aws_secret_access_key": "key1",
            "aws_default_region": "us-east-1",
        }
    }
    misc.set_aws_kms_environment_variables(target_settings, "test_profile")
    expected_logs = ["AWS KMS profile test_profile not found in the target settings"]
    compare_logs(caplog, expected_logs)


def test_set_aws_kms_environment_variables_missing_variables(target_settings, caplog):
    caplog.set_level(logging.WARNING)
    target_settings["aws_kms_credentials"] = {"test_profile": {}}
    misc.set_aws_kms_environment_variables(target_settings, "test_profile")
    expected_logs = [
        "Cannot set AWS KMS environment variable AWS_ACCESS_KEY_ID, "
        "value missing in profile test_profile",
        "Cannot set AWS KMS environment variable AWS_SECRET_ACCESS_KEY, "
        "value missing in profile test_profile",
        "Cannot set AWS KMS environment variable AWS_DEFAULT_REGION, "
        "value missing in profile test_profile",
    ]
    compare_logs(caplog, expected_logs)

    assert "AWS_ACCESS_KEY_ID" not in os.environ
    assert "AWS_SECRET_ACCESS_KEY" not in os.environ
    assert "AWS_DEFAULT_REGION" not in os.environ


def test_set_aws_kms_environment_variables(target_settings, caplog):
    caplog.set_level(logging.WARNING)
    target_settings["aws_kms_credentials"] = {
        "test_profile": {
            "aws_access_key_id": "id1",
            "aws_secret_access_key": "key1",
            "aws_default_region": "us-east-1",
        }
    }
    misc.set_aws_kms_environment_variables(target_settings, "test_profile")
    expected_logs = []
    compare_logs(caplog, expected_logs)

    assert os.environ["AWS_ACCESS_KEY_ID"] == "id1"
    assert os.environ["AWS_SECRET_ACCESS_KEY"] == "key1"
    assert os.environ["AWS_DEFAULT_REGION"] == "us-east-1"
