from mock import (
    patch, call, mock_open, ANY, Mock
)
from pytest import raises
import mock
import kiwi

from collections import namedtuple

from kiwi.defaults import Defaults
from kiwi.builder.install import InstallImageBuilder
from kiwi.exceptions import KiwiInstallBootImageError


class TestInstallImageBuilder:
    def setup(self):
        Defaults.set_platform_name('x86_64')
        boot_names_type = namedtuple(
            'boot_names_type', ['kernel_name', 'initrd_name']
        )
        self.setup = mock.Mock()
        kiwi.builder.install.SystemSetup = mock.Mock(
            return_value=self.setup
        )
        self.firmware = mock.Mock()
        self.firmware.efi_mode = mock.Mock(
            return_value='uefi'
        )
        kiwi.builder.install.FirmWare = mock.Mock(
            return_value=self.firmware
        )
        self.squashed_image = mock.Mock()
        kiwi.builder.install.FileSystemSquashFs = mock.Mock(
            return_value=self.squashed_image
        )
        self.iso_image = mock.Mock()
        self.iso_image.create_on_file.return_value = 42
        kiwi.builder.install.FileSystemIsoFs = mock.Mock(
            return_value=self.iso_image
        )
        self.mbrid = mock.Mock()
        self.mbrid.get_id = mock.Mock(
            return_value='0xffffffff'
        )
        kiwi.builder.install.SystemIdentifier = mock.Mock(
            return_value=self.mbrid
        )
        kiwi.builder.install.Path = mock.Mock()
        kiwi.builder.install.BootLoaderConfig.new = mock.Mock()
        self.checksum = mock.Mock()
        kiwi.builder.install.Checksum = mock.Mock(
            return_value=self.checksum
        )
        self.kernel = mock.Mock()
        self.kernel.get_kernel = mock.Mock()
        self.kernel.get_xen_hypervisor = mock.Mock()
        self.kernel.copy_kernel = mock.Mock()
        self.kernel.copy_xen_hypervisor = mock.Mock()
        kiwi.builder.install.Kernel = mock.Mock(
            return_value=self.kernel
        )
        self.xml_state = mock.Mock()
        self.xml_state.get_initrd_system = mock.Mock(
            return_value='kiwi'
        )
        self.xml_state.xml_data.get_name = mock.Mock(
            return_value='result-image'
        )
        self.xml_state.get_image_version = mock.Mock(
            return_value='1.2.3'
        )
        self.xml_state.build_type.get_kernelcmdline = mock.Mock(
            return_value='custom_kernel_options'
        )
        self.xml_state.get_oemconfig_oem_multipath_scan = mock.Mock(
            return_value=False
        )
        self.xml_state.get_installmedia_initrd_modules = mock.Mock(
            return_value=['module1', 'module2']
        )
        self.boot_image_task = mock.Mock()
        self.boot_image_task.boot_root_directory = 'initrd_dir'
        self.boot_image_task.initrd_filename = 'initrd'
        self.boot_image_task.get_boot_names = mock.Mock(
            return_value=boot_names_type(
                kernel_name='kernel_name',
                initrd_name='initrd-kernel_version'
            )
        )

        self.install_image = InstallImageBuilder(
            self.xml_state, 'root_dir', 'target_dir', self.boot_image_task
        )

    def setup_method(self, cls):
        self.setup()

    @patch('kiwi.builder.install.BootImage')
    def test_init_dracut_based(self, mock_boot_image):
        InstallImageBuilder(
            self.xml_state, 'root_dir', 'target_dir', None
        )
        mock_boot_image.new.assert_called_once_with(
            ANY, 'target_dir', 'root_dir'
        )

    def test_setup_ix86(self):
        Defaults.set_platform_name('i686')
        xml_state = mock.Mock()
        xml_state.xml_data.get_name = mock.Mock(
            return_value='result-image'
        )
        xml_state.get_image_version = mock.Mock(
            return_value='1.2.3'
        )
        xml_state.build_type.get_kernelcmdline = mock.Mock(
            return_value='custom_kernel_options'
        )
        xml_state.get_build_type_oemconfig_section = mock.Mock(
            return_value=None
        )
        install_image = InstallImageBuilder(
            xml_state, 'root_dir', 'target_dir', mock.Mock()
        )
        assert install_image.arch == 'ix86'

    @patch('kiwi.builder.install.DeviceProvider')
    @patch('kiwi.builder.install.BootLoaderConfig.new')
    @patch('kiwi.builder.install.IsoToolsBase.setup_media_loader_directory')
    @patch('kiwi.builder.install.shutil.copy')
    @patch('kiwi.builder.install.Temporary')
    @patch('kiwi.builder.install.Command.run')
    @patch('kiwi.builder.install.Defaults.get_grub_boot_directory_name')
    @patch('kiwi.builder.install.Iso')
    def test_create_install_iso(
        self, mock_Iso, mock_grub_dir, mock_command, mock_Temporary, mock_copy,
        mock_setup_media_loader_directory, mock_BootLoaderConfig,
        mock_DeviceProvider
    ):
        temp_squashfs = Mock()
        temp_squashfs.new_dir.return_value.name = 'temp-squashfs'

        temp_media_dir = Mock()
        temp_media_dir.new_dir.return_value.name = 'temp_media_dir'

        temp_esp_file = Mock()
        temp_esp_file.new_file.return_value.name = 'temp_esp'

        tmp_names = [temp_esp_file, temp_squashfs, temp_media_dir]

        def side_effect(prefix, path):
            return tmp_names.pop()

        bootloader_config = mock.Mock()
        mock_BootLoaderConfig.return_value = bootloader_config
        mock_Temporary.side_effect = side_effect

        self.firmware.bios_mode.return_value = False

        m_open = mock_open()
        with patch('builtins.open', m_open, create=True):
            self.install_image.create_install_iso()

        self.setup.import_cdroot_files.assert_called_once_with('temp_media_dir')

        self.checksum.md5.assert_called_once_with(
            'temp-squashfs/result-image.md5'
        )
        mock_copy.assert_called_once_with(
            'root_dir/boot/initrd-kernel_version',
            'temp_media_dir/initrd.system_image'
        )
        assert m_open.call_args_list == [
            call('temp_media_dir/config.isoclient', 'w'),
            call('initrd_dir/config.vmxsystem', 'w')
        ]
        assert m_open.return_value.write.call_args_list == [
            call('IMAGE="result-image.raw"\n'),
            call('IMAGE="result-image.raw"\n')
        ]
        kiwi.builder.install.FileSystemSquashFs.assert_called_once_with(
            custom_args={'compression': mock.ANY},
            device_provider=mock_DeviceProvider.return_value,
            root_dir='temp-squashfs'
        )
        self.squashed_image.create_on_file.assert_called_once_with(
            'target_dir/result-image.raw.squashfs'
        )
        mock_BootLoaderConfig.assert_called_once_with(
            'grub2', self.xml_state, root_dir='root_dir',
            boot_dir='temp_media_dir', custom_args={
                'grub_directory_name': mock_grub_dir.return_value
            }
        )
        bootloader_config.setup_install_boot_images.assert_called_once_with(
            lookup_path='initrd_dir', mbrid=self.mbrid
        )
        mock_setup_media_loader_directory.assert_called_once_with(
            'initrd_dir', 'temp_media_dir',
            bootloader_config.get_boot_theme.return_value
        )
        bootloader_config.write_meta_data.assert_called_once_with()
        bootloader_config.setup_install_image_config.assert_called_once_with(
            mbrid=self.mbrid
        )
        bootloader_config.write.assert_called_once_with()
        bootloader_config._create_embedded_fat_efi_image.assert_called_once_with(
            'temp_esp'
        )
        self.boot_image_task.create_initrd.assert_called_once_with(
            self.mbrid, 'initrd_kiwi_install', install_initrd=True
        )
        self.boot_image_task.cleanup.assert_called_once_with()
        self.kernel.copy_kernel.assert_called_once_with(
            'temp_media_dir/boot/x86_64/loader', '/linux'
        )
        self.kernel.copy_xen_hypervisor.assert_called_once_with(
            'temp_media_dir/boot/x86_64/loader', '/xen.gz'
        )
        assert mock_command.call_args_list == [
            call([
                'cp', '-l',
                'target_dir/result-image.x86_64-1.2.3.raw',
                'temp-squashfs/result-image.raw'
            ]),
            call([
                'mv', 'target_dir/result-image.raw.squashfs', 'temp_media_dir'
            ]),
            call([
                'mv', 'initrd', 'temp_media_dir/boot/x86_64/loader/initrd'
            ])
        ]
        self.iso_image.create_on_file.assert_called_once_with(
            'target_dir/result-image.x86_64-1.2.3.install.iso'
        )

        tmp_names = [temp_esp_file, temp_squashfs, temp_media_dir]
        self.install_image.initrd_system = 'dracut'

        m_open.reset_mock()
        with patch('builtins.open', m_open, create=True):
            self.install_image.create_install_iso()

        self.boot_image_task.include_module.assert_any_call('kiwi-dump')
        self.boot_image_task.include_module.assert_any_call('kiwi-dump-reboot')
        self.boot_image_task.omit_module.call_args_list == [
            call('multipath'), call('module1'), call('module2')
        ]
        self.boot_image_task.set_static_modules.assert_called_once_with(
            ['module1', 'module2']
        )

        self.boot_image_task.include_file.assert_called_once_with(
            '/config.bootoptions'
        )
        assert m_open.call_args_list == [
            call('temp_media_dir/config.isoclient', 'w'),
        ]
        assert m_open.return_value.write.call_args_list == [
            call('IMAGE="result-image.raw"\n')
        ]

        mock_BootLoaderConfig.reset_mock()
        tmp_names = [temp_esp_file, temp_squashfs, temp_media_dir]
        self.firmware.efi_mode.return_value = None
        self.firmware.bios_mode.return_value = True

        with patch('builtins.open', m_open, create=True):
            self.install_image.create_install_iso()

        mock_Iso.return_value.setup_isolinux_boot_path.assert_called_once_with()
        mock_BootLoaderConfig.assert_called_once_with(
            'isolinux', self.xml_state, root_dir='root_dir',
            boot_dir='temp_media_dir'
        )
        bootloader_config._create_embedded_fat_efi_image.assert_not_called()

    @patch('kiwi.builder.install.IsoToolsBase.setup_media_loader_directory')
    @patch('kiwi.builder.install.Temporary')
    @patch('kiwi.builder.install.Command.run')
    def test_create_install_iso_no_kernel_found(
        self, mock_command, mock_Temporary, mock_setup_media_loader_directory
    ):
        self.firmware.bios_mode.return_value = False
        self.kernel.get_kernel.return_value = False
        with patch('builtins.open'):
            with raises(KiwiInstallBootImageError):
                self.install_image.create_install_iso()

    @patch('kiwi.builder.install.IsoToolsBase.setup_media_loader_directory')
    @patch('kiwi.builder.install.Temporary')
    @patch('kiwi.builder.install.Command.run')
    def test_create_install_iso_no_hypervisor_found(
        self, mock_command, mock_Temporary, mock_setup_media_loader_directory
    ):
        self.firmware.bios_mode.return_value = False
        self.kernel.get_xen_hypervisor.return_value = False
        with patch('builtins.open'):
            with raises(KiwiInstallBootImageError):
                self.install_image.create_install_iso()

    @patch('kiwi.builder.install.Temporary')
    @patch('kiwi.builder.install.Command.run')
    @patch('kiwi.builder.install.Checksum')
    @patch('kiwi.builder.install.Compress')
    def test_create_install_pxe_no_kernel_found(
        self, mock_compress, mock_md5, mock_command, mock_Temporary
    ):
        self.firmware.bios_mode.return_value = False
        mock_Temporary.return_value.new_dir.return_value.name = 'tmpdir'
        self.kernel.get_kernel.return_value = False
        with patch('builtins.open'):
            with raises(KiwiInstallBootImageError):
                self.install_image.create_install_pxe_archive()

    @patch('kiwi.builder.install.Temporary')
    @patch('kiwi.builder.install.Command.run')
    @patch('kiwi.builder.install.Checksum')
    @patch('kiwi.builder.install.Compress')
    @patch('kiwi.builder.install.os.symlink')
    def test_create_install_pxe_no_hypervisor_found(
        self, mock_symlink, mock_compress, mock_md5, mock_command,
        mock_Temporary
    ):
        self.firmware.bios_mode.return_value = False
        mock_Temporary.return_value.new_dir.return_value.name = 'tmpdir'
        self.kernel.get_xen_hypervisor.return_value = False
        with patch('builtins.open'):
            with raises(KiwiInstallBootImageError):
                self.install_image.create_install_pxe_archive()

    @patch('kiwi.builder.install.Temporary')
    @patch('kiwi.builder.install.Command.run')
    @patch('kiwi.builder.install.ArchiveTar')
    @patch('kiwi.builder.install.Checksum')
    @patch('kiwi.builder.install.Compress')
    @patch('kiwi.builder.install.shutil.copy')
    @patch('kiwi.builder.install.os.symlink')
    @patch('kiwi.builder.install.os.chmod')
    def test_create_install_pxe_archive(
        self, mock_chmod, mock_symlink, mock_copy, mock_compress,
        mock_md5, mock_archive, mock_command, mock_Temporary
    ):
        mock_Temporary.return_value.new_dir.return_value.name = 'tmpdir'

        archive = mock.Mock()
        mock_archive.return_value = archive

        checksum = mock.Mock()
        mock_md5.return_value = checksum

        compress = mock.Mock()
        mock_compress.return_value = compress

        m_open = mock_open()
        with patch('builtins.open', m_open, create=True):
            self.install_image.create_install_pxe_archive()

        mock_compress.assert_called_once_with(
            keep_source_on_compress=True,
            source_filename='target_dir/result-image.x86_64-1.2.3.raw'
        )
        compress.xz.assert_called_once_with(None)
        assert mock_command.call_args_list[0] == call(
            [
                'mv', compress.compressed_filename,
                'tmpdir/result-image.x86_64-1.2.3.xz'
            ]
        )
        mock_md5.assert_called_once_with(
            'target_dir/result-image.x86_64-1.2.3.raw'
        )
        checksum.md5.assert_called_once_with(
            'tmpdir/result-image.x86_64-1.2.3.md5'
        )
        assert m_open.call_args_list == [
            call('initrd_dir/config.vmxsystem', 'w'),
            call('tmpdir/result-image.x86_64-1.2.3.append', 'w')
        ]
        assert m_open.return_value.write.call_args_list == [
            call('IMAGE="result-image.raw"\n'),
            call('pxe=1 custom_kernel_options\n')
        ]
        self.kernel.copy_kernel.assert_called_once_with(
            'tmpdir', 'pxeboot.result-image.x86_64-1.2.3.kernel'
        )
        mock_symlink.assert_called_once_with(
            'pxeboot.result-image.x86_64-1.2.3.kernel',
            'tmpdir/result-image.x86_64-1.2.3.kernel'
        )
        self.kernel.copy_xen_hypervisor.assert_called_once_with(
            'tmpdir', '/pxeboot.result-image.x86_64-1.2.3.xen.gz'
        )
        self.boot_image_task.create_initrd.assert_called_once_with(
            self.mbrid, 'initrd_kiwi_install', install_initrd=True
        )
        self.boot_image_task.cleanup.assert_called_once_with()
        assert mock_command.call_args_list[1] == call(
            [
                'mv', 'initrd',
                'tmpdir/pxeboot.result-image.x86_64-1.2.3.initrd'
            ]
        )
        mock_chmod.assert_called_once_with(
            'tmpdir/pxeboot.result-image.x86_64-1.2.3.initrd', 420
        )
        mock_archive.assert_called_once_with(
            'target_dir/result-image.x86_64-1.2.3.install.tar'
        )
        archive.create.assert_called_once_with('tmpdir')

        mock_chmod.reset_mock()
        mock_copy.reset_mock()
        self.install_image.initrd_system = 'dracut'
        m_open.reset_mock()
        with patch('builtins.open', m_open, create=True):
            self.install_image.create_install_pxe_archive()

        assert mock_copy.call_args_list == [
            call(
                'root_dir/boot/initrd-kernel_version',
                'tmpdir/result-image.x86_64-1.2.3.initrd'
            ),
            call(
                'root_dir/config.bootoptions',
                'tmpdir/result-image.x86_64-1.2.3.config.bootoptions'
            )
        ]
        assert mock_chmod.call_args_list == [
            call('tmpdir/result-image.x86_64-1.2.3.initrd', 420),
            call('tmpdir/pxeboot.result-image.x86_64-1.2.3.initrd', 420)
        ]
        assert m_open.call_args_list == [
            call('tmpdir/result-image.x86_64-1.2.3.append', 'w'),
        ]
        assert m_open.return_value.write.call_args_list == [
            call(
                ' '.join([
                    'rd.kiwi.install.pxe',
                    'rd.kiwi.install.image=http://example.com/image.xz',
                    'custom_kernel_options\n'
                ])
            )
        ]

        self.boot_image_task.include_module.assert_any_call('kiwi-dump')
        self.boot_image_task.include_module.assert_any_call('kiwi-dump-reboot')
        self.boot_image_task.omit_module.call_args_list == [
            call('multipath'), call('module1'), call('module2')
        ]
        self.boot_image_task.set_static_modules.assert_called_once_with(
            ['module1', 'module2']
        )
