# Copyright (C) 2012 Yahoo! Inc.
# All Rights Reserved.
#
#    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 warnings

from oslotest import base as test_base
import testscenarios

from oslo_utils import imageutils

from unittest import mock

load_tests = testscenarios.load_tests_apply_scenarios


class ImageUtilsHumanRawTestCase(test_base.BaseTestCase):
    _image_name = [
        ('disk_config', dict(image_name='disk.config')),
    ]

    _file_format = [
        ('raw', dict(file_format='raw')),
    ]

    _virtual_size = [
        ('64M', dict(virtual_size='64M', exp_virtual_size=67108864)),
        (
            '64M_with_byte_hint',
            dict(
                virtual_size='64M (67108844 bytes)', exp_virtual_size=67108844
            ),
        ),
        ('64M_byte', dict(virtual_size='67108844', exp_virtual_size=67108844)),
        (
            '64_MiB_with_byte_hint',
            dict(
                virtual_size='64 MiB (67108844 bytes)',
                exp_virtual_size=67108844,
            ),
        ),
        ('4.4M', dict(virtual_size='4.4M', exp_virtual_size=4613735)),
        (
            '4.4M_with_byte_hint',
            dict(
                virtual_size='4.4M (4592640 bytes)', exp_virtual_size=4592640
            ),
        ),
        (
            '4.4_MiB_with_byte_hint',
            dict(
                virtual_size='4.4 MiB (4592640 bytes)',
                exp_virtual_size=4592640,
            ),
        ),
        ('2K', dict(virtual_size='2K', exp_virtual_size=2048)),
        (
            '2K_with_byte_hint',
            dict(virtual_size='2K (2048 bytes)', exp_virtual_size=2048),
        ),
        (
            '2_KiB_with_byte_hint',
            dict(virtual_size='2 KiB (2048 bytes)', exp_virtual_size=2048),
        ),
        (
            '1e+03_MiB',
            dict(virtual_size='1e+03 MiB', exp_virtual_size=1048576000),
        ),
    ]

    _disk_size = [
        ('96K', dict(disk_size='96K', exp_disk_size=98304)),
        ('96_KiB', dict(disk_size='96 KiB', exp_disk_size=98304)),
        ('96K_byte', dict(disk_size='98304', exp_disk_size=98304)),
        ('98304_B', dict(disk_size='98304 B', exp_disk_size=98304)),
        ('3.1G', dict(disk_size='3.1G', exp_disk_size=3328599655)),
        ('3.1_GiB', dict(disk_size='3.1 GiB', exp_disk_size=3328599655)),
        ('unavailable', dict(disk_size='unavailable', exp_disk_size=0)),
        ('1e+03_MiB', dict(disk_size='1e+03 MiB', exp_disk_size=1048576000)),
    ]

    _garbage_before_snapshot = [
        ('no_garbage', dict(garbage_before_snapshot=None)),
        ('garbage_before_snapshot_list', dict(garbage_before_snapshot=False)),
        ('garbage_after_snapshot_list', dict(garbage_before_snapshot=True)),
    ]

    _snapshot_count = [
        ('no_snapshots', dict(snapshot_count=None)),
        ('one_snapshots', dict(snapshot_count=1)),
        ('three_snapshots', dict(snapshot_count=3)),
    ]

    @classmethod
    def generate_scenarios(cls):
        cls.scenarios = testscenarios.multiply_scenarios(
            cls._image_name,
            cls._file_format,
            cls._virtual_size,
            cls._disk_size,
            cls._garbage_before_snapshot,
            cls._snapshot_count,
        )

    def _initialize_img_info(self):
        return (
            f'image: {self.image_name}',
            f'file_format: {self.file_format}',
            f'virtual_size: {self.virtual_size}',
            f'disk_size: {self.disk_size}',
        )

    def _insert_snapshots(self, img_info):
        img_info = img_info + ('Snapshot list:',)
        img_info = img_info + (
            'ID        '
            'TAG                 '
            'VM SIZE                '
            'DATE       '
            'VM CLOCK',
        )
        for i in range(self.snapshot_count):
            img_info = img_info + (
                f'{i + 1}        '
                f'd9a9784a500742a7bb95627bb3aace38    '
                f'0 2012-08-20 10:52:46 '
                f'00:00:00.000',
            )
        return img_info

    def _base_validation(self, image_info):
        self.assertEqual(image_info.image, self.image_name)
        self.assertEqual(image_info.file_format, self.file_format)
        self.assertEqual(image_info.virtual_size, self.exp_virtual_size)
        self.assertEqual(image_info.disk_size, self.exp_disk_size)
        if self.snapshot_count is not None:
            self.assertEqual(len(image_info.snapshots), self.snapshot_count)

    @mock.patch('debtcollector.deprecate')
    def test_qemu_img_info_human_format(self, mock_deprecate):
        img_info = self._initialize_img_info()
        if self.garbage_before_snapshot is True:
            img_info = img_info + ('blah BLAH: bb',)
        if self.snapshot_count is not None:
            img_info = self._insert_snapshots(img_info)
        if self.garbage_before_snapshot is False:
            img_info = img_info + ('junk stuff: bbb',)
        example_output = '\n'.join(img_info)
        warnings.simplefilter("always", FutureWarning)
        image_info = imageutils.QemuImgInfo(example_output)
        mock_deprecate.assert_called()
        self._base_validation(image_info)


ImageUtilsHumanRawTestCase.generate_scenarios()


class ImageUtilsHumanQemuTestCase(ImageUtilsHumanRawTestCase):
    _file_format = [
        ('qcow2', dict(file_format='qcow2')),
    ]

    _qcow2_cluster_size = [
        ('65536', dict(cluster_size='65536', exp_cluster_size=65536)),
    ]

    _qcow2_encrypted = [
        ('no_encryption', dict(encrypted=None)),
        ('encrypted', dict(encrypted='yes')),
    ]

    _qcow2_backing_file = [
        ('no_backing_file', dict(backing_file=None)),
        (
            'backing_file_path',
            dict(
                backing_file='/var/lib/nova/a328c7998805951a_2',
                exp_backing_file='/var/lib/nova/a328c7998805951a_2',
            ),
        ),
        (
            'backing_file_path_with_actual_path',
            dict(
                backing_file='/var/lib/nova/a328c7998805951a_2 '
                '(actual path: /b/3a988059e51a_2)',
                exp_backing_file='/b/3a988059e51a_2',
            ),
        ),
    ]

    _qcow2_backing_file_format = [
        ('no_backing_file_format', dict(backing_file_format=None)),
        (
            'backing_file_format',
            dict(backing_file_format='qcow2', exp_backing_file_format='qcow2'),
        ),
    ]

    @classmethod
    def generate_scenarios(cls):
        cls.scenarios = testscenarios.multiply_scenarios(
            cls._image_name,
            cls._file_format,
            cls._virtual_size,
            cls._disk_size,
            cls._garbage_before_snapshot,
            cls._snapshot_count,
            cls._qcow2_cluster_size,
            cls._qcow2_encrypted,
            cls._qcow2_backing_file,
            cls._qcow2_backing_file_format,
        )

    @mock.patch("debtcollector.deprecate")
    def test_qemu_img_info_human_format(self, mock_deprecate):
        img_info = self._initialize_img_info()
        img_info = img_info + (f'cluster_size: {self.cluster_size}',)
        if self.backing_file is not None:
            img_info = img_info + (f'backing file: {self.backing_file}',)
            if self.backing_file_format is not None:
                img_info = img_info + (
                    f'backing file format: {self.backing_file_format}',
                )
        if self.encrypted is not None:
            img_info = img_info + (f'encrypted: {self.encrypted}',)
        if self.garbage_before_snapshot is True:
            img_info = img_info + ('blah BLAH: bb',)
        if self.snapshot_count is not None:
            img_info = self._insert_snapshots(img_info)
        if self.garbage_before_snapshot is False:
            img_info = img_info + ('junk stuff: bbb',)
        example_output = '\n'.join(img_info)
        warnings.simplefilter("always", FutureWarning)
        image_info = imageutils.QemuImgInfo(example_output)
        mock_deprecate.assert_called()
        self._base_validation(image_info)
        self.assertEqual(image_info.cluster_size, self.exp_cluster_size)
        if self.backing_file is not None:
            self.assertEqual(image_info.backing_file, self.exp_backing_file)
            if self.backing_file_format is not None:
                self.assertEqual(
                    image_info.backing_file_format,
                    self.exp_backing_file_format,
                )
        if self.encrypted is not None:
            self.assertEqual(image_info.encrypted, self.encrypted)


ImageUtilsHumanQemuTestCase.generate_scenarios()


class ImageUtilsBlankTestCase(test_base.BaseTestCase):
    def test_qemu_img_info_blank(self):
        example_output = '\n'.join(
            [
                'image: None',
                'file_format: None',
                'virtual_size: None',
                'disk_size: None',
                'cluster_size: None',
                'backing_file: None',
                'backing_file_format: None',
            ]
        )
        image_info = imageutils.QemuImgInfo()
        self.assertEqual(str(image_info), example_output)
        self.assertEqual(len(image_info.snapshots), 0)


class ImageUtilsJSONTestCase(test_base.BaseTestCase):
    @mock.patch("debtcollector.deprecate")
    def test_qemu_img_info(self, mock_deprecate):
        img_output = '''{
                       "virtual-size": 41126400,
                       "filename": "fake_img",
                       "cluster-size": 65536,
                       "format": "qcow2",
                       "actual-size": 13168640,
                       "format-specific": {"data": {"foo": "bar"}},
                       "encrypted": true
                      }'''
        image_info = imageutils.QemuImgInfo(img_output, format='json')
        mock_deprecate.assert_not_called()
        self.assertEqual(41126400, image_info.virtual_size)
        self.assertEqual('fake_img', image_info.image)
        self.assertEqual(65536, image_info.cluster_size)
        self.assertEqual('qcow2', image_info.file_format)
        self.assertEqual(13168640, image_info.disk_size)
        self.assertEqual("bar", image_info.format_specific["data"]["foo"])
        self.assertEqual('yes', image_info.encrypted)
        # test for Bug #1996426
        expected_str = "format_specific: {'data': {'foo': 'bar'}}"
        self.assertIn(expected_str, str(image_info))

    @mock.patch("debtcollector.deprecate")
    def test_qemu_img_info_blank(self, mock_deprecate):
        img_output = '{}'
        image_info = imageutils.QemuImgInfo(img_output, format='json')
        mock_deprecate.assert_not_called()
        self.assertIsNone(image_info.virtual_size)
        self.assertIsNone(image_info.image)
        self.assertIsNone(image_info.cluster_size)
        self.assertIsNone(image_info.file_format)
        self.assertIsNone(image_info.disk_size)
        self.assertIsNone(image_info.format_specific)
        self.assertIsNone(image_info.encrypted)
