class TestRackspaceCtrl(unittest.TestCase):
    def setUp(self):
        """ Set up the objects used by most of the tests. """

        self.ctrl = RackspaceCtrl("valid_user", "valid_api_key", "http://foo.bar:8888")
        self.ctrl.post_fn = stub_rackspace_identity_post
        self.driver_cls = MockLibCloudDriver

    def test_authenticate_valid_user(self):
        """ Test authentication with a valid user and api key. """

        auth_result = self.ctrl.authenticate()
        self.assertEqual(auth_result, True)
        self.assertIsNotNone(self.ctrl.token)

    def test_authenticate_empty_user(self):
        """ Ensure authentication with empty string as username fails. """

        ctrl = RackspaceCtrl("", "valid_api_key", "http://foo.bar:8888")
        ctrl.post_fn = stub_rackspace_identity_post

        auth_result = ctrl.authenticate()
        self.assertEqual(auth_result, False)
        self.assertIsNone(ctrl.token)

    def test_authenticate_empty_apikey(self):
        """ Ensure authentication with empty string as api_key fails. """

        ctrl = RackspaceCtrl("valid_user", "", "http://foo.bar:8888")
        ctrl.post_fn = stub_rackspace_identity_post

        auth_result = ctrl.authenticate()
        self.assertEqual(auth_result, False)
        self.assertIsNone(ctrl.token)

    def test_authenticate_invalid_user(self):
        """  Ensure authentication with invalid user credentials fails. """

        ctrl = RackspaceCtrl("invalid_user", "invalid_api_key", "http://foo.bar:8888")
        ctrl.post_fn = stub_rackspace_identity_post

        auth_result = ctrl.authenticate()
        self.assertEqual(auth_result, False)
        self.assertIsNone(ctrl.token)

    def test_list_regions(self):
        """ Ensure that list_regions returns the correct result. """

        self.ctrl.authenticate()
        regions = self.ctrl.list_regions()

        expected_regions = [{"IAD": "iad"}, {"DFW": "dfw"}, {"SYD": "syd"}, {"ORD": "ord"}]

        self.assertCountEqual(regions, expected_regions)

    def test_set_region(self):
        """ Ensure that set_region sets 'region' and 'driver'. """

        self.ctrl.authenticate()

        result = self.ctrl.set_region("iad")

        self.assertEqual(result, True)
        self.assertEqual(self.ctrl.region, "iad")
        self.assertIsNotNone(self.ctrl.driver)

    def test_set_invalid_region(self):
        """ Ensure that calling 'set_region' with an invalid param fails. """

        self.ctrl.authenticate()

        result = self.ctrl.set_region("invalid")

        self.assertEqual(result, False)
        self.assertIsNone(self.ctrl.region)
        self.assertIsNone(self.ctrl.driver)

    def test_token_parsed(self):
        """ Ensure that the token is set. """

        ctrl = RackspaceCtrl("valid_user", "valid_api_key", "http://foo.bar:8888")
        ctrl.post_fn = stub_rackspace_identity_post

        ctrl.authenticate()

        self.assertEqual("abcdefgh0123456789", ctrl.token)

    def test_upload_file(self):
        self.ctrl.storage_driver = mock.MagicMock()
        mock_container = mock.MagicMock()
        mock_container.list_objects = mock.MagicMock(return_value=[])
        self.ctrl.storage_driver.create_container = mock.MagicMock(return_value=mock_container)
        test_data = b"abcdef"
        test_data_hash = hashlib.md5(test_data).hexdigest()

        test_file = tempfile.NamedTemporaryFile()
        with test_file.file as f:
            f.write(test_data)

        return_value = self.ctrl.upload_file(test_file.name, "test_folder/test.txt")

        upload_file_args = self.ctrl.storage_driver.upload_object_via_stream.call_args_list[0]
        upload_hash_args = self.ctrl.storage_driver.upload_object_via_stream.call_args_list[1]

        self.assertEqual(upload_file_args[0][2], "test_folder/test.txt")
        self.assertEqual(upload_hash_args[0][2], "test_folder/test.txt.md5")
        self.assertEqual(upload_file_args[0][0].name, test_file.name)
        self.assertEqual(upload_hash_args[0][0].read(), test_data_hash)
        self.assertEqual(return_value, True)

    def test_upload_file__exists(self):
        test_data = b"abcdef"
        test_data_hash = hashlib.md5(test_data).hexdigest()

        test_file = tempfile.NamedTemporaryFile()
        with test_file.file as f:
            f.write(test_data)

        self.ctrl.storage_driver = mock.MagicMock()
        mock_container = mock.MagicMock()
        mock_container.list_objects = mock.MagicMock(
            return_value=[MockStorageObject("test_folder/test.txt"), MockStorageObject("test_folder/test.txt.md5")]
        )
        self.ctrl.storage_driver.create_container = mock.MagicMock(return_value=mock_container)

        mock_container.get_object = mock.MagicMock(return_value=MockStorageObject("", bytes(test_data_hash, "utf8")))

        return_value = self.ctrl.upload_file(test_file.name, "test_folder/test.txt")

        self.assertFalse(self.ctrl.storage_driver.upload_object_via_stream.called)
        self.assertEqual(return_value, False)

    def test_download_file__exists__same_hash(self):
        test_data = b"abcdefghi"
        test_data_hash = hashlib.md5(test_data).hexdigest()
        test_file = tempfile.NamedTemporaryFile()
        with test_file.file as f:
            f.write(test_data)

        self.ctrl.storage_driver = mock.MagicMock()
        mock_container = mock.MagicMock()
        self.ctrl.storage_driver.get_container = mock.MagicMock(return_value=mock_container)

        file_object = MockStorageObject("test_file.txt")
        file_object.download = mock.MagicMock()
        file_hash_object = MockStorageObject("test_file.txt", bytes(test_data_hash, "utf8"))

        mock_container.get_object = lambda name: {"test_file.txt": file_object, "test_file.txt.md5": file_hash_object}[
            name
        ]

        self.ctrl.download_file("test_file.txt", test_file.name)

        self.assertFalse(file_object.download.called)

    def test_download_file__exists__different_hash(self):
        test_data = b"abcdefghij"
        test_data_hash = "some_garbage_hash"
        test_file = tempfile.NamedTemporaryFile()
        with test_file.file as f:
            f.write(test_data)

        self.ctrl.storage_driver = mock.MagicMock()
        mock_container = mock.MagicMock()
        self.ctrl.storage_driver.get_container = mock.MagicMock(return_value=mock_container)

        file_object = MockStorageObject("test_file.txt")
        file_object.download = mock.MagicMock()
        file_hash_object = MockStorageObject("test_file.txt", bytes(test_data_hash, "utf8"))

        mock_container.get_object = lambda name: {"test_file.txt": file_object, "test_file.txt.md5": file_hash_object}[
            name
        ]

        self.ctrl.download_file("test_file.txt", test_file.name)

        file_object.download.assert_called_once_with(test_file.name)

    def test_find_storage_image_names(self):
        self.ctrl.storage_driver = mock.MagicMock()
        mock_container = mock.MagicMock()
        self.ctrl.storage_driver.get_container = mock.MagicMock(return_value=mock_container)
        mock_container.list_objects = mock.MagicMock(
            return_value=[
                MockStorageObject("images/IOS/test_image_1.image"),
                MockStorageObject("images/IOS/test_image_1.image.md5"),
                MockStorageObject("images/IOS/test_image_2.img"),
                MockStorageObject("images/IOS/test_image_2.img.md5"),
                MockStorageObject("images/IOS/test_image_3.img"),
                MockStorageObject("projects/project1.zip"),
                MockStorageObject("test_image_1.image"),
            ]
        )

        image_names = self.ctrl.find_storage_image_names(["test_image_1.image", "test_image_2.img"])

        self.assertDictEqual(
            image_names,
            {"test_image_1.image": "images/IOS/test_image_1.image", "test_image_2.img": "images/IOS/test_image_2.img"},
        )

    def test_find_storage_image_names__missing(self):
        self.ctrl.storage_driver = mock.MagicMock()
        mock_container = mock.MagicMock()
        self.ctrl.storage_driver.get_container = mock.MagicMock(return_value=mock_container)
        mock_container.list_objects = mock.MagicMock(
            return_value=[
                MockStorageObject("images/IOS/test_image_1.image"),
                MockStorageObject("images/IOS/test_image_1.image.md5"),
                MockStorageObject("images/IOS/test_image_2.img.md5"),
                MockStorageObject("projects/project1.zip"),
                MockStorageObject("test_image_1.image"),
            ]
        )

        self.assertRaises(Exception, self.ctrl.find_storage_image_names, ["test_image_1.image", "test_image_2.img"])