#!/usr/bin/python3
#
# Copyright (C) 2022 Red Hat, Inc.
# SPDX-License-Identifier: LGPL-2.1-or-later

import os

from anacondalib import INSTALLER_VM_MEMORY_MB, VirtInstallMachineCase, disk_images, pixel_tests_ignore, run_boot
from installer import Installer
from review import Review
from storage import EFI_PARTITION_DEFAULT_SIZE, EFI_PARTITION_DEFAULT_SIZE_MB, Storage
from storagelib import StorageCase  # pylint: disable=import-error
from testlib import nondestructive, skipImage, test_main  # pylint: disable=import-error
from utils import move_standard_fedora_disk_to_MBR_disk, move_standard_fedora_disk_to_win_disk, pretend_default_scheme

TEST_DIR = os.path.dirname(__file__)
ROOT_DIR = os.path.dirname(TEST_DIR)
BOTS_DIR = f'{ROOT_DIR}/bots'


@nondestructive
class TestStorageMountPoints(VirtInstallMachineCase, StorageCase):
    @run_boot("bios", "efi")
    @disk_images([("", 15), ("fedora-43", "15")])
    @skipImage("btrfs support missing on fedora-eln image", "fedora-eln-boot")
    def testBasic(self):
        """
        Description:
            Test the basic functionality of the mount point mapping table.

        Expected results:
            - The mount point mapping table is displayed correctly.
            - The user can select a device / mount point / format type.
            - The user can add and remove rows.

            - The 'Next' button is disabled when the mount point mapping table is not valid.
            - Swap partitions are not selectable for the required mount point rows.
            - The review screen should show the correct mountpoints and devices
        """
        b = self.browser
        m = self.machine
        i = Installer(b, m)
        s = Storage(b, m)
        r = Review(b, m)
        dev = "vdb"

        i.open()
        i.reach(i.steps.INSTALLATION_METHOD)

        s.select_mountpoint([("vda", False), ("vdb", True)])

        # verify gathered requests
        # root partition is not auto-mapped
        s.check_mountpoint_row(1, "/", "Select a device", True)
        s.select_mountpoint_row_device(1, "root")
        s.check_mountpoint_row_format_type(1, "btrfs")
        next_idx = 2

        def select_boot_mountpoints(next_idx=next_idx):
            if self.is_efi:
                s.select_mountpoint_row_device(next_idx, f"{dev}2")
                s.check_mountpoint_row_format_type(next_idx, "EFI System Partition")
                next_idx += 1

            s.check_mountpoint_row(next_idx, "/boot", "Select a device", False)
            s.select_mountpoint_row_device(next_idx, f"{dev}3")
            s.check_mountpoint_row_format_type(next_idx, "ext4")
            next_idx += 1

            return next_idx

        next_idx = select_boot_mountpoints()

        s.add_mountpoint_row()
        s.select_mountpoint_row_device(next_idx, "home")
        s.check_mountpoint_row_reformat(next_idx, False)
        s.select_mountpoint_row_mountpoint(next_idx, "/home")
        s.check_mountpoint_row_format_type(next_idx, "btrfs")

        # Toggle reformat option
        s.select_mountpoint_row_reformat(next_idx - 1)

        if self.is_efi:
            s.select_mountpoint_row_reformat(next_idx - 2)

        # FIXME: https://github.com/cockpit-project/cockpit/pull/21392
        # b.assert_pixels(
        #     "#app",
        #     "mount-point-mapping-table",
        #     ignore=pixel_tests_ignore,
        # )

        if self.is_efi:
            i.next(should_fail=True)

        i.reach(i.steps.REVIEW)

        # verify review screen
        r.check_disk(dev, "16.1 GB vdb (Virtio Block Device)")

        next_idx = 2
        if self.is_efi:
            r.check_disk_row(dev, "/boot/efi", f"vdb{next_idx}", "105 MB", True, "efi")
        next_idx += 1
        r.check_disk_row(dev, "/boot", f"vdb{next_idx}", "2.10 GB", True, "ext4")
        r.check_disk_row(dev, "/", f"vdb{next_idx + 1}", "11.8 GB", True, "btrfs subvolume")
        r.check_disk_row(dev, "/home", f"vdb{next_idx + 1}", "11.8 GB", False)

        applied_partitioning = s.dbus_get_applied_partitioning()

        # When re-entering the mount points mapping page new partitioning should be created,
        # but the previous selection should be preserved
        i.reach_on_sidebar(i.steps.INSTALLATION_METHOD)
        s.select_mountpoint([("vda", False), ("vdb", True)])
        s.check_mountpoint_row(1, "/", "root", True)
        next_idx = 2
        if self.is_efi:
            s.check_mountpoint_row(next_idx, "/boot/efi", f"{dev}2", False)
            next_idx += 1
        s.check_mountpoint_row(next_idx, "/boot", f"{dev}3", False)

        if self.is_efi:
            i.next(should_fail=True)
        i.next()
        new_applied_partitioning = s.dbus_get_applied_partitioning()
        self.assertNotEqual(new_applied_partitioning, applied_partitioning)

        # When going back and forward 'Next' button should not be disabled
        # https://bugzilla.redhat.com/show_bug.cgi?id=2242086
        i.back(previous_page=i.steps.CUSTOM_MOUNT_POINT)
        i.check_next_disabled(False)

        # Swap partitions should not be selectable for the required mount point rows
        # https://bugzilla.redhat.com/show_bug.cgi?id=2239836
        i.back()

        disk = "/dev/vdb"
        m.execute(f"sgdisk --new=0:0:0 {disk}")
        m.execute(f"mkswap {disk}5")
        s.rescan_disks()
        s.select_mountpoint([("vda", False), ("vdb", True)])
        s.check_mountpoint_row_device_available(1, f"{dev}5", True, True)

    def _testNoRootMountPoint_partition_disk(self):
        b = self.browser
        m = self.machine
        s = Storage(b, m)

        # EFI system partition, /boot on ext4, / on xfs, /home on btrfs
        disk = "/dev/vda"
        s.partition_disk(disk, [(EFI_PARTITION_DEFAULT_SIZE, "efi"), ("1GiB", "ext4"), ("10GiB", "xfs"), ("", "ext4")])

    def testNoRootMountPoint(self):
        """
        Description:
            Test the case when the root mount point is not selected.

        Expected results:
            - The 'Next' button is disabled when the mount point mapping table does
              not contain a root mount point.
            - When the user deselects reformat for "/" and goes next/back, the
              reformat choice is preserved (rhbz#2443812).
        """
        b = self.browser
        m = self.machine
        i = Installer(b, m)
        s = Storage(b, m)

        i.open()
        i.reach(i.steps.INSTALLATION_METHOD)

        dev = "vda"
        s.select_mountpoint([(dev, True)])

        # verify gathered requests
        s.select_mountpoint_row_device(2, f"{dev}1")
        s.select_mountpoint_row_device(3, f"{dev}2")
        s.check_mountpoint_row(1, "/", "Select a device", True)
        s.check_mountpoint_row(2, "/boot/efi", f"{dev}1", False, "EFI System Partition")
        s.check_mountpoint_row(3, "/boot", f"{dev}2", False, "ext4")

        # Test moving back and forth between screens.
        i.back()
        i.next(next_page=i.steps.CUSTOM_MOUNT_POINT)
        s.check_mountpoint_row(1, "/", "Select a device", True)
        s.check_mountpoint_row(2, "/boot/efi", f"{dev}1", False, "EFI System Partition")
        s.check_mountpoint_row(3, "/boot", f"{dev}2", False, "ext4")

        s.add_mountpoint_row()
        s.select_mountpoint_row_device(4, f"{dev}4")
        s.check_mountpoint_row_format_type(4, "ext4")
        s.select_mountpoint_row_mountpoint(4, "/home")
        s.check_mountpoint_row_reformat(4, False)

        i.check_next_disabled()

        # BZ#2443812: deselected reformat for "/" is maintained after next/back.
        s.select_mountpoint_row_device(1, f"{dev}3")
        s.select_mountpoint_row_reformat(1, False)
        i.next(next_page=i.steps.ACCOUNTS)
        i.back(previous_page=i.steps.CUSTOM_MOUNT_POINT)
        s.check_mountpoint_row_reformat(1, False)

    def _testMultipleDisks_partition_disk(self):
        b = self.browser
        m = self.machine
        s = Storage(b, m)

        # EFI system partition, /boot partition, /
        disk1 = "/dev/vda"
        s.partition_disk(disk1, [(EFI_PARTITION_DEFAULT_SIZE, "efi"), ("1GiB", "xfs"), ("", "xfs")])

        # /dev/vdb1 /home partition
        disk2 = "/dev/vdb"
        s.partition_disk(disk2, [("", "xfs")])

    @disk_images([("", 15), ("", 10)])
    def testMultipleDisks(self):
        """
        Description:
            Tests the case when multiple disks are selected, and also checks that bootloader
            partition requirements are correctly enforced

        Expected results:
            - The mount point mapping scenario is available and can be used with
              multiple disks
            - FIXME: The bootloader requirements (EFI system partition) are verified for the selected disk[s]
        """
        b = self.browser
        m = self.machine
        i = Installer(b, m)
        s = Storage(b, m)
        r = Review(b, m)

        dev1 = "vda"
        dev2 = "vdb"

        i.open()
        i.reach(i.steps.INSTALLATION_METHOD)

        # FIXME: Currently we don't check the partition constraints for mountable partitions,
        # we only check that the biosboot requirement is met for BIOS installations
        #
        # # Select only vdb disk and verify that mount point assignment is
        # # not available (missing EFI system partition)
        # s.select_disks([(dev1, False), (dev2, True)])
        # s.wait_scenario_available("mount-point-mapping", False)

        # Select only vda disk and verify that the partitioning request is correct
        s.select_mountpoint([(dev1, True), (dev2, False)])

        s.check_mountpoint_row_device_available(1, "vdb1", False)
        s.check_mountpoint_row_device_available(1, "vda3")

        # Go back and change the disk selection. The partitioning should be re-created
        i.back()

        s.select_mountpoint([(dev1, True), (dev2, True)])

        s.check_mountpoint_row_device_available(1, "vda2", True)
        s.check_mountpoint_row_device_available(1, "vdb1")

        s.select_mountpoint_row_device(1, f"{dev1}3")
        s.check_mountpoint_row(1, "/", f"{dev1}3", True, "xfs")

        s.select_mountpoint_row_device(2, f"{dev1}1")
        s.check_mountpoint_row(2, "/boot/efi", f"{dev1}1", False, "EFI System Partition")

        s.select_mountpoint_row_device(3, f"{dev1}2")
        s.check_mountpoint_row(3, "/boot", f"{dev1}2", False, "xfs")

        s.add_mountpoint_row()
        s.select_mountpoint_row_device(4, f"{dev2}1")
        s.select_mountpoint_row_mountpoint(4, "/home")
        s.check_mountpoint_row(4, "/home", f"{dev2}1", False, "xfs")

        i.reach(i.steps.REVIEW)

        # verify review screen
        disk = "vda"
        r.check_disk(disk, "16.1 GB vda (Virtio Block Device)")
        r.check_disk_row(disk, "/boot/efi", "vda1", EFI_PARTITION_DEFAULT_SIZE_MB, False)
        r.check_disk_row(disk, "/boot", "vda2", "1.07 GB", False)
        r.check_disk_row(disk, "/", "vda3", "14.5 GB", True, "xfs")

        disk = "vdb"
        r.check_disk(disk, "10.7 GB vdb (Virtio Block Device)")
        r.check_disk_row(disk, "/home", "vdb1", "10.7 GB", False)

        b.assert_pixels(
            "#app",
            "review-multiple-disks",
            ignore=pixel_tests_ignore,
        )

    def _testPayloadSizeCheck_partition_disk(self):
        b = self.browser
        m = self.machine
        s = Storage(b, m)

        # EFI system partition, /boot partition, /
        disk1 = "/dev/vda"
        s.partition_disk(disk1, [(EFI_PARTITION_DEFAULT_SIZE, "efi"), ("1GiB", "xfs"), ("1GiB", "xfs"), ("1GiB", "xfs"), ("", "xfs")])

    @disk_images([("", 15)])
    def testPayloadSizeCheck(self):
        """
        Description:
            Test free space check

        Expected results:
            - A warning about insufficient space is showed to the user on the review screen
            - The warning prevents continuing with the installation
            - There are hints beside the checked mount points in case of failed check
            - The link for changing partitioning layout moves user to the right page
        """
        b = self.browser
        m = self.machine
        i = Installer(b, m, scenario="mount-point-mapping")
        s = Storage(b, m)
        r = Review(b, m)

        i.open()
        i.reach(i.steps.INSTALLATION_METHOD)

        dev = "vda"
        s.select_mountpoint([(dev, True)])

        # perform assignment with not enough space on mount points
        # single mount point used to count the required space
        s.select_mountpoint_row_device(1, f"{dev}3")
        s.select_mountpoint_row_device(2, f"{dev}1")
        s.select_mountpoint_row_device(3, f"{dev}2")

        i.reach(i.steps.REVIEW)

        r.check_size_error()

        # check that there is a hint with required size beside the device
        r.check_disk_row(dev, "/", f"{dev}3", "1.07 GB", True, "xfs", is_encrypted=False)
        r.check_disk_mount_point_helper_text(dev, "/", "Needs at least")

        # check that it is not possible to continue on the next page (install)
        i.check_next_disabled()
        b.click(f"#{i.steps.REVIEW}-next-confirmation-checkbox")
        i.check_next_disabled()

        ##### go back on sidebar to installation method and update the assignment

        i.click_step_on_sidebar(i.steps.INSTALLATION_METHOD)

        s.select_mountpoint([(dev, True)])

        # modify assignment adding small "/usr", still not enough space on mount points
        # two mount points used to count the required space
        s.add_mountpoint_row()
        s.select_mountpoint_row_mountpoint(4, "/usr")
        s.select_mountpoint_row_device(4, f"{dev}4")
        s.select_mountpoint_row_reformat(4)

        i.reach(i.steps.REVIEW)

        r.check_size_error()

        # check that there is a hint suitable for multiple devices
        r.check_disk_row(dev, "/", f"{dev}3", "1.07 GB", True, "xfs", is_encrypted=False)
        r.check_disk_mount_point_helper_text(dev, "/", "May need more free space")
        r.check_disk_row(dev, "/usr", f"{dev}4", "1.07 GB", True, "xfs", is_encrypted=False)
        r.check_disk_mount_point_helper_text(dev, "/usr", "May need more free space")

        # check that it is not possible to continue on the next page (install)
        i.check_next_disabled()
        b.click(f"#{i.steps.REVIEW}-next-confirmation-checkbox")
        i.check_next_disabled()

        ###### go back by alert button and update the assignment

        b.click(f"#{i.steps.REVIEW}-change-partition-layout")

        # modify assignment adding big enough "/usr"
        # two mount points used to count the required space
        s.select_mountpoint_row_device(4, f"{dev}5")

        i.reach(i.steps.REVIEW)

        r.check_size_error(False)

        # check that it is possible to continue on the next page (install)
        i.check_next_disabled()
        b.click(f"#{i.steps.REVIEW}-next-confirmation-checkbox")
        i.check_next_disabled(False)

    def _testEncryptedUnlock_partition_disk(self):
        b = self.browser
        m = self.machine
        s = Storage(b, m)

        disk1 = "/dev/vda"
        s.partition_disk(
            disk1,
            [(EFI_PARTITION_DEFAULT_SIZE, "efi"), ("1GiB", None), ("1GiB", None), ("1GiB", None), ("1GiB", "xfs")],
        )
        s.create_luks_partition(f"{disk1}2", "einszwei", "encrypted-vol0", "xfs")
        s.create_luks_partition(f"{disk1}3", "einszweidrei", "encrypted-vol1", "xfs")
        s.create_luks_partition(f"{disk1}4", "einszweidreivier", "encrypted-vol2", "xfs")

    def testEncryptedUnlock(self):
        """
        Description:
            Test 'Mount point mapping' with encrypted partitions

        Expected results:
            - The 'Unlock' dialog is available for unlocking the partitions before entering the mount point mapping
            - The user can unlock the disk using a passphrase.
            - The user can select the unlocked devices in the mount point mapping
              table.
            - The review screen should show the correct mountpoints and devices
        """
        b = self.browser
        m = self.machine
        i = Installer(b, m)
        s = Storage(b, m)
        dev1 = "vda"

        i.open()
        i.reach(i.steps.INSTALLATION_METHOD)

        s.unlock_all_encrypted()
        s.unlock_device("1234", ["vda2", "vda3", "vda4"], [])
        b.click("#unlock-device-dialog-close-btn")

        s.unlock_all_encrypted()
        s.unlock_device("einszwei", ["vda2", "vda3", "vda4"], ["vda2"])

        # The 'Unlock' dialog should stay open unless all LUKS devices are unlocked
        s.unlock_device("einszweidrei", ["vda3", "vda4"], ["vda3"])

        # The 'Unlock' dialog closes when all LUKS devices are unlocked
        s.unlock_device("einszweidreivier", ["vda4"], ["vda4"])

        s.select_mountpoint([(dev1, True)])
        b.wait_not_present(f"#{i.steps.CUSTOM_MOUNT_POINT}-table tbody tr:nth-child(4) td[data-label='Format type'] #unlock-luks-btn")

        self._testEncryptedUnlock(b, m)

    def _testEncryptedUnlock(self, b, m):
        dev1 = "vda"

        i = Installer(b, m)
        s = Storage(b, m)
        r = Review(b, m)

        s.check_mountpoint_row_mountpoint(2, "/boot/efi")
        s.check_mountpoint_row_mountpoint(3, "/boot")
        s.select_mountpoint_row_device(2, f"{dev1}1")
        s.select_mountpoint_row_device(3, f"{dev1}5")

        s.check_mountpoint_row_mountpoint(1, "/")

        toggle_selector = f"#{i.steps.CUSTOM_MOUNT_POINT}-table-row-1-device-select-toggle:not([disabled]):not([aria-disabled=true])"
        b.click(toggle_selector)

        b.click(".pf-v6-c-menu__content li:nth-of-type(3) button")

        b.wait_not_present(".pf-v6-c-menu")
        b.wait_js_func("ph_text", f"#{i.steps.CUSTOM_MOUNT_POINT}-table-row-1 .pf-v6-c-select__toggle-text", "luks")

        s.check_mountpoint_row_format_type(1, "xfs")

        i.reach(i.steps.REVIEW)
        r.check_disk_row(dev1, "/", "vda2", "", True, "xfs", True)

    def testEncryptedUnlockCockpit(self):
        """
        Description:
            Test 'Mount point mapping' with encrypted partitions created using Cockpit Storage

        Expected results:
            - The user can create an encrypted partition with cockpit storage
            - The 'Unlock' dialog is available for unlocking the partitions before entering the mount point mapping
            - The user can unlock the disk using a passphrase
            - The user can selected the unlocked devices in the mount point mapping
              table
            - The review screen should show the correct mountpoints and devices
        """
        b = self.browser
        m = self.machine
        i = Installer(b, m)
        s = Storage(b, m)
        _r = Review(b, m)  # noqa: F841

        i.open()
        i.reach(i.steps.INSTALLATION_METHOD)
        s.check_disk_selected("vda")

        s.modify_storage()
        s.confirm_entering_cockpit_storage()
        b.wait_visible(".cockpit-storage-integration-sidebar")
        s.check_constraint("/", True)
        s.check_constraint("/boot/efi", True)
        s.check_constraint("/boot", False)

        frame = "iframe[name='cockpit-storage']"
        b._wait_present(frame)
        b.switch_to_frame("cockpit-storage")
        b._wait_present("#storage.ct-page-fill")

        self.click_dropdown(self.card_row("Storage", 1), "Create partition table")
        self.confirm()

        self.click_dropdown(self.card_row("Storage", 2), "Create partition")
        self.dialog({"size": 524, "type": "efi", "mount_point": "/boot/efi"})

        # FIXME: Cockpit uses MB/GB for units whereas anaconda uses MiB/GiB, so
        # when anaconda backend recommends a 1GiB boot partition and Cockpit
        # creates a 1GB partition, it is rounded down to 931MiB and doesn't
        # meet the boot partition requirement, causing the test to fail. For
        # now, we create a 1100MiB partition to ensure the requirement is met,
        # but this should be addressed in the future by using consistent units
        # in both Cockpit and Anaconda.
        self.click_dropdown(self.card_row("Storage", 3), "Create partition")
        self.dialog({
            "size": 1100,
            "type": "xfs",
            "crypto": self.default_crypto_type,
            "passphrase": "einszwei",
            "passphrase2": "einszwei",
        })

        self.click_dropdown(self.card_row("Storage", 4), "Create partition")
        self.dialog({
            "size": 1100,
            "type": "xfs",
            "crypto": self.default_crypto_type,
            "passphrase": "einszweidrei",
            "passphrase2": "einszweidrei",
        })

        self.click_dropdown(self.card_row("Storage", 5), "Create partition")
        self.dialog({
            "size": 1100,
            "type": "xfs",
            "crypto": self.default_crypto_type,
            "passphrase": "einszweidreivier",
            "passphrase2": "einszweidreivier",
        })

        self.click_dropdown(self.card_row("Storage", 6), "Create partition")
        self.dialog({"size": 1100, "type": "ext4"})

        # Exit the cockpit-storage iframe
        b.switch_to_top()
        s.return_to_installation()
        s.return_to_installation_confirm()
        i.wait_current_page(i.steps.INSTALLATION_METHOD)

        s.select_mountpoint([("vda", True)])

        self._testEncryptedUnlock(b, m)

    def _testDuplicateDeviceNames_partition_disk(self):
        b = self.browser
        m = self.machine
        s = Storage(b, m)

        disk = "/dev/vda"
        btrfsname = "home"
        s.partition_disk(
            disk,
            [
                (EFI_PARTITION_DEFAULT_SIZE, "efi"),
                ("1GiB", "ext4"),
                ("5GiB", "btrfs", "-f", "-L", "root"),
                ("5GiB", "btrfs", "-f", "-L", btrfsname),
                ("", "btrfs", "-f", "-L", btrfsname)
            ]
        )

    @skipImage("btrfs support missing on fedora-eln image", "fedora-eln-boot")
    def testDuplicateDeviceNames(self):
        """
        Description:
            Test the case when the devices with the same name exist

        Expected results:
            - The devices with the same name are displayed in the mount point mapping
              table
            - The user can select the devices with the same name in the mount point mapping
              table
            - The review screen should show the correct mountpoints and devices

        References:
            - https://bugzilla.redhat.com/show_bug.cgi?id=2237878
        """
        b = self.browser
        m = self.machine
        i = Installer(b, m)
        s = Storage(b, m)
        r = Review(b, m)

        btrfsname = "home"
        btrfs_volume_ids = s.get_btrfs_volume_ids(btrfsname)

        i.open()
        i.reach(i.steps.INSTALLATION_METHOD)

        s.select_mountpoint([("vda", True)])

        s.select_mountpoint_row_device(1, "root")
        s.select_mountpoint_row_device(2, "vda1")
        s.select_mountpoint_row_device(3, "vda2")
        s.add_mountpoint_row()
        s.select_mountpoint_row_mountpoint(4, "/home/joe")
        s.select_mountpoint_row_device(4, "home", device_id=btrfs_volume_ids[0])
        s.add_mountpoint_row()
        s.select_mountpoint_row_mountpoint(5, "/home/alan")
        s.select_mountpoint_row_device(5, "home", device_id=btrfs_volume_ids[1])

        i.reach(i.steps.REVIEW)

        # verify review screen
        disk = "vda"
        r.check_disk(disk, "16.1 GB vda (Virtio Block Device)")

        r.check_disk_row(disk, "/boot/efi", "vda1", EFI_PARTITION_DEFAULT_SIZE_MB, False)
        r.check_disk_row(disk, "/boot", "vda2", "1.07 GB", False)
        r.check_disk_row(disk, "/", "vda3", "5.37 GB", True, "btrfs volume")
        r.check_disk_row(disk, "/home/joe", "vda4", "5.37 GB", False, "btrfs volume")
        r.check_disk_row(disk, "/home/alan", "vda5", "3.77 GB", False, "btrfs volume")

    def _testUnusableFormats_partition_disk(self):
        b = self.browser
        m = self.machine
        s = Storage(b, m)

        disk = "/dev/vda"
        s.partition_disk(disk, [(EFI_PARTITION_DEFAULT_SIZE, "efi"), ("1GiB", "ext4"), ("1GiB", None), ("1GiB", "lvmpv")])

    def testUnusableFormats(self):
        """
        Description:
            Test the case when the devices with unusable formats exist

        Expected results:
            - The devices with unusable formats are not displayed in the mount point mapping
              table
        """
        b = self.browser
        m = self.machine
        i = Installer(b, m)
        s = Storage(b, m)

        dev = "vda"

        i.open()
        i.reach(i.steps.INSTALLATION_METHOD)
        s.rescan_disks()

        s.select_mountpoint([(dev, True)])

        # unformatted and unmountable devices should not be available
        s.check_mountpoint_row_device_available(1, f"{dev}3", False)
        s.check_mountpoint_row_device_available(1, f"{dev}4", False)

    def _testExtendedPartition_partition_disk(self):
        b = self.browser
        m = self.machine
        s = Storage(b, m)

        # Configure disk layout with a logical Btrfs partition
        disk = "/dev/vda"
        s.partition_disk(
            disk,
            [
                ("512MiB", "efi"),
                ("1GiB", "ext4"),
                ("", "extended"),
                ("10GiB", "logical")
            ],
            is_mbr=True
        )

        # Create subvolumes for the logical Btrfs partition
        m.execute(f"""
        mkfs.btrfs -f -L BTRFS {disk}5
        mount {disk}5 /mnt
        btrfs subvolume create /mnt/root
        btrfs subvolume create /mnt/home
        umount /mnt
        """)

    def testExtendedPartition(self):
        """
        Description:
            Test 'Mount point mapping' with a disk that has an extended partition

        Expected results:
            - The extended partition is not selectable
            - The other partitions are selectable
        """
        b = self.browser
        m = self.machine
        i = Installer(b, m)
        s = Storage(b, m)

        i.open()
        i.reach(i.steps.INSTALLATION_METHOD)

        s.select_mountpoint([("vda", True)])

        s.select_mountpoint_row_device(1, "root")
        s.select_mountpoint_row_device(2, "vda1")
        s.select_mountpoint_row_device(3, "vda2")
        s.add_mountpoint_row()
        s.select_mountpoint_row_mountpoint(4, "/home")
        s.select_mountpoint_row_device(4, "home")

    def _testVfatWithoutESP_partition_disk(self):
        b = self.browser
        m = self.machine
        s = Storage(b, m)

        disk = "/dev/vda"
        # Vfat partition, /boot on ext4, / on xfs
        s.partition_disk(disk, [(EFI_PARTITION_DEFAULT_SIZE, "vfat"), ("1GiB", "ext4"), ("", "xfs")])

    def testVfatWithoutESP(self):
        """
        Description:
            Test that assigning /boot/efi to a vfat partition without the ESP
            flag shows an appropriate error message.

        Expected results:
            - When a vfat partition without ESP type is selected for /boot/efi,
              an error about missing ESP marking is shown.
        """
        b = self.browser
        m = self.machine
        i = Installer(b, m)
        s = Storage(b, m)

        dev = "vda"

        i.open()
        i.reach(i.steps.INSTALLATION_METHOD)

        s.select_mountpoint([(dev, True)])

        s.select_mountpoint_row_device(2, f"{dev}1")
        s.select_mountpoint_row_device(1, f"{dev}3")
        s.select_mountpoint_row_device(3, f"{dev}2")

        s.wait_mountpoint_table_column_helper(2, "device", text="must be on an EFI System Partition")

    def _testEncryptedHomeSecondDisk_partition_disk(self):
        b = self.browser
        m = self.machine
        s = Storage(b, m)

        disk1 = "/dev/vda"
        disk2 = "/dev/vdb"
        # On disk1: EFI, /boot (ext4), and root (xfs)
        s.partition_disk(disk1, [("512MiB", "efi"), ("1GiB", "ext4"), ("", "xfs")])
        # On disk2: whole-disk LUKS with xfs inside (no partition table)
        m.execute(f"wipefs -a {disk2}")
        s.create_luks_partition(disk2, "homepass", "encrypted-home", "xfs")

    @disk_images([("", 15), ("", 10)])
    def testEncryptedHomeSecondDisk(self):
        """
        Description:
            Test encrypted /home reuse on a separate disk via 'Mount point mapping'.

        Expected results:
            - The 'Unlock' dialog is available and used to unlock the /home disk
            - The user can assign /boot, /boot/efi, and / on disk1
            - The user can assign /home to the unlocked encrypted device on disk2
            - The review screen shows /home as encrypted and mounted (not reformatted)

        References:
            - https://bugzilla.redhat.com/show_bug.cgi?id=2396646
        """
        b = self.browser
        m = self.machine
        i = Installer(b, m)
        s = Storage(b, m)
        r = Review(b, m)

        dev1 = "vda"
        dev2 = "vdb"

        i.open()
        i.reach(i.steps.INSTALLATION_METHOD)
        s.select_disks([(dev1, True), (dev2, True)])

        # Unlock the encrypted second disk (home)
        s.unlock_all_encrypted()
        s.unlock_device("homepass", [f"{dev2}"], [f"{dev2}"])

        # Proceed to mount point mapping with both disks selected
        s.select_mountpoint()

        # Assign /, /boot/efi and /boot on disk1
        s.select_mountpoint_row_device(1, f"{dev1}3")
        next_idx = 2
        s.select_mountpoint_row_device(next_idx, f"{dev1}1")
        s.check_mountpoint_row_format_type(next_idx, "EFI System Partition")
        next_idx += 1
        s.select_mountpoint_row_device(next_idx, f"{dev1}2")
        s.check_mountpoint_row_format_type(next_idx, "ext4")
        next_idx += 1

        # Add and assign /home to the unlocked encrypted device on disk2
        s.add_mountpoint_row()
        s.select_mountpoint_row_mountpoint(next_idx, "/home")
        luks_device = s.get_luks_device_name(f"/dev/{dev2}")
        s.select_mountpoint_row_device(next_idx, luks_device)

        # Verify review shows /home as encrypted and mounted on disk2
        i.reach(i.steps.REVIEW)
        r.check_disk(dev1, "16.1 GB vda (Virtio Block Device)")
        r.check_disk_row(dev1, "/boot/efi", f"{dev1}1", "537 MB", False, "efi")
        r.check_disk_row(dev1, "/boot", f"{dev1}2", "1.07 GB", False, "ext4")
        r.check_disk_row(dev1, "/", f"{dev1}3", "14.5 GB", True, "xfs")

        r.check_disk(dev2, "10.7 GB vdb (Virtio Block Device)")
        r.check_disk_row(dev2, "/home", "", "10.7 GB", False, "xfs", is_encrypted=True, action="mount")


# TODO: these tests should be made nondestructive
class TestStorageMountPointsDestructive(VirtInstallMachineCase, StorageCase):
    provision = {"machine1": {"memory_mb": INSTALLER_VM_MEMORY_MB}}

    def _testMBRParttable_partition_disk(self):
        storage = Storage(self.browser, self.machine)
        move_standard_fedora_disk_to_MBR_disk(storage, self.machine, "vda", "vdb")

    @disk_images([("", 15), ("fedora-rawhide", 15)])
    # https://fedoraproject.org/wiki/Changes/Anaconda_Drop_Support_UEFI_on_MBR
    # Fedora installer has phased out support for booting in UEFI mode on MBR-partitioned disks.
    @run_boot("bios")
    def testMBRParttable(self):
        """
        Description:
            Test the case when the disk is MBR partitioned.

        Expected results:
            - The mount point mapping scenario is available and can be used with
              MBR partitioned disks.
        """
        b = self.browser
        m = self.machine
        i = Installer(b, m)
        s = Storage(b, m)
        r = Review(b, m)

        # This is required for home-reuse availability check
        pretend_default_scheme(self, "BTRFS")

        i.open()
        i.reach(i.steps.INSTALLATION_METHOD)

        s.wait_scenario_available("mount-point-mapping", True)

        s.select_mountpoint([("vda", True), ("vdb", False)])

        s.select_mountpoint_row_device(1, "root")
        s.select_mountpoint_row_device(2, "vda1")
        s.add_mountpoint_row()
        s.select_mountpoint_row_mountpoint(3, "/home")
        s.select_mountpoint_row_device(3, "home")

        s.select_mountpoint_row_reformat(2)

        i.reach(i.steps.REVIEW)

        # verify review screen
        disk = "vda"
        r.check_disk(disk, "16.1 GB vda (Virtio Block Device)")

        r.check_disk_row(disk, "/boot", "vda1", "1.07 GB", True, "ext4")
        r.check_disk_row(disk, "/", "vda2", "14.0 GB", True, "btrfs subvolume")
        r.check_disk_row(disk, "/home", "vda2", "14.0 GB", False, "btrfs subvolume")

    def _testWindowsSingleDiskHomeReuse_partition_disk(self):
        storage = Storage(self.browser, self.machine)
        move_standard_fedora_disk_to_win_disk(storage, self.machine, "vda", "vdb")

    @disk_images([("", 35), ("fedora-rawhide", 15)])
    def testWindowsSingleDiskHomeReuse(self):
        """
        Description:
            Test home reuse via 'Mount point mapping' installation method with
            a disk containig dual boot setup with Windows and Fedora (shared EFI partition).

        Expected results:
            - The mount point mapping scenario is available and can be used for reuse
              of the home partition.
            - The home reuse expected layout is shown in the review screen
        """
        b = self.browser
        m = self.machine
        i = Installer(b, m)
        s = Storage(b, m)
        r = Review(b, m)

        i.open()
        i.reach(i.steps.INSTALLATION_METHOD)

        s.wait_scenario_available("mount-point-mapping", True)

        s.select_mountpoint([("vda", True), ("vdb", False)])

        s.select_mountpoint_row_device(1, "root")
        s.select_mountpoint_row_device(2, "vda1")
        s.select_mountpoint_row_device(3, "vda4")
        s.select_mountpoint_row_reformat(3)
        s.add_mountpoint_row()
        s.select_mountpoint_row_mountpoint(4, "/home")
        s.select_mountpoint_row_device(4, "home")
        s.check_mountpoint_row_reformat(4, False)

        i.next(should_fail=True)
        i.reach(i.steps.REVIEW)

        dev_win_fed = "vda"
        r.check_disk(dev_win_fed, f"37.6 GB {dev_win_fed} (Virtio Block Device)")
        r.check_disk_row(dev_win_fed, "/boot/efi", f"{dev_win_fed}1", "105 MB", False, "vfat", is_encrypted=False,
                         action="mount")
        r.check_disk_row(dev_win_fed, "/boot", f"{dev_win_fed}4", "1.07 GB", True, "ext4", is_encrypted=False)
        r.check_disk_row(dev_win_fed, "/home", f"{dev_win_fed}5", "14.0 GB", False, "btrfs subvolume", is_encrypted=False,
                         action="mount")
        r.check_disk_row(dev_win_fed, "/", f"{dev_win_fed}5", "14.0 GB", True, "btrfs subvolume", is_encrypted=False)


if __name__ == '__main__':
    test_main()
