import mock
import pytest

from uaclient import exceptions
from uaclient.clouds.identity import (
    NoCloudTypeReason,
    cloud_instance_factory,
    get_cloud_type,
    get_instance_id,
)

M_PATH = "uaclient.clouds.identity."


class TestGetInstanceID:
    @mock.patch(M_PATH + "system.subp", return_value=("my-iid\n", ""))
    def test_use_cloud_init_query(self, m_subp):
        """Get instance_id from cloud-init query."""
        assert "my-iid" == get_instance_id()
        assert [
            mock.call(["cloud-init", "query", "instance_id"])
        ] == m_subp.call_args_list

    @mock.patch(
        M_PATH + "system.subp",
        side_effect=exceptions.ProcessExecutionError(
            "cloud-init query instance_id"
        ),
    )
    def test_none_when_cloud_init_query_fails(self, m_subp):
        """Return None when cloud-init query fails."""
        assert None is get_instance_id()
        assert [
            mock.call(["cloud-init", "query", "instance_id"])
        ] == m_subp.call_args_list


class TestGetCloudType:
    @mock.patch(M_PATH + "system.which", return_value="/usr/bin/cloud-id")
    @mock.patch(M_PATH + "system.subp", return_value=("somecloud\n", ""))
    def test_use_cloud_id_when_available(self, m_subp, m_which):
        """Use cloud-id utility to discover cloud type."""
        assert ("somecloud", None) == get_cloud_type.__wrapped__()
        assert [mock.call("cloud-id")] == m_which.call_args_list

    @mock.patch(M_PATH + "system.which", return_value="/usr/bin/cloud-id")
    @mock.patch(
        M_PATH + "system.subp",
        side_effect=exceptions.ProcessExecutionError("cloud-id"),
    )
    def test_error_when_cloud_id_fails(self, m_subp, m_which):
        assert (
            None,
            NoCloudTypeReason.CLOUD_ID_ERROR,
        ) == get_cloud_type.__wrapped__()

    @pytest.mark.parametrize(
        "settings_overrides",
        (
            (
                """
                settings_overrides:
                  cloud_type: "azure"
                """
            ),
            (
                """
                settings_overrides:
                  other_setting: "blah"
                """
            ),
        ),
    )
    @mock.patch("os.path.exists", return_value=True)
    @mock.patch("uaclient.system.load_file")
    @mock.patch("uaclient.system.which", return_value="/usr/bin/cloud-id")
    @mock.patch("uaclient.system.subp", return_value=("test", ""))
    def test_cloud_type_when_using_settings_override(
        self, m_subp, m_which, m_load_file, _m_path_exists, settings_overrides
    ):
        if "azure" in settings_overrides:
            expected_value = "azure"
        else:
            expected_value = "test"

        m_load_file.return_value = settings_overrides
        assert get_cloud_type.__wrapped__() == (expected_value, None)


@mock.patch(M_PATH + "get_cloud_type")
class TestCloudInstanceFactory:
    def test_raise_error_when_unable_to_get_cloud_type(self, m_get_cloud_type):
        """Raise appropriate error when unable to determine cloud_type."""
        m_get_cloud_type.return_value = (
            None,
            NoCloudTypeReason.NO_CLOUD_DETECTED,
        )
        with pytest.raises(exceptions.CloudFactoryNoCloudError):
            cloud_instance_factory()
        assert 1 == m_get_cloud_type.call_count

    def test_raise_error_when_not_supported(self, m_get_cloud_type):
        """Raise appropriate error when unable to determine cloud_type."""
        m_get_cloud_type.return_value = ("unsupported-cloud", None)
        with pytest.raises(exceptions.CloudFactoryUnsupportedCloudError):
            cloud_instance_factory()

    @pytest.mark.parametrize("cloud_type", ("aws", "azure"))
    def test_raise_error_when_not_viable_for_ubuntu_pro(
        self, m_get_cloud_type, cloud_type
    ):
        """Raise error when AWS or Azure instance is not viable auto-attach."""
        m_get_cloud_type.return_value = (cloud_type, None)

        def fake_invalid_instance():
            instance = mock.Mock()
            instance.is_viable = False
            return instance

        if cloud_type == "aws":
            M_INSTANCE_PATH = "uaclient.clouds.aws.UAAutoAttachAWSInstance"
        else:
            M_INSTANCE_PATH = "uaclient.clouds.azure.UAAutoAttachAzureInstance"

        with mock.patch(M_INSTANCE_PATH) as m_instance:
            m_instance.side_effect = fake_invalid_instance
            with pytest.raises(exceptions.CloudFactoryNonViableCloudError):
                cloud_instance_factory()

    @pytest.mark.parametrize(
        "cloud_type", ("aws", "aws-gov", "aws-china", "azure")
    )
    def test_return_cloud_instance_on_viable_clouds(
        self, m_get_cloud_type, cloud_type
    ):
        """Return UAAutoAttachInstance when matching cloud_type is viable."""
        m_get_cloud_type.return_value = (cloud_type, None)

        fake_instance = mock.Mock()
        fake_instance.is_viable = True

        def fake_viable_instance():
            return fake_instance

        if cloud_type == "azure":
            M_INSTANCE_PATH = "uaclient.clouds.azure.UAAutoAttachAzureInstance"
        else:
            M_INSTANCE_PATH = "uaclient.clouds.aws.UAAutoAttachAWSInstance"

        with mock.patch(M_INSTANCE_PATH) as m_instance:
            m_instance.side_effect = fake_viable_instance
            assert fake_instance == cloud_instance_factory()
