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

import os

from anacondalib import VirtInstallMachineCase, disk_images, pixel_tests_ignore
from installer import Installer
from storage import Storage
from testlib import nondestructive, test_main  # pylint: disable=import-error
from utils import check_cockpit_module_js_error

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


@nondestructive
class TestStorageBasic(VirtInstallMachineCase):

    def testLocalStandardDisks(self):
        """
        Description:
            Test that the installer can detect and select local standard disks

        Expected results:
            - The installer should detect the local standard disks
            - The installer should allow the user to select the disks
            - Without any disks selected, the installer should not allow the user to continue
            - When the selected disk[s] does not have enough space for the installation,
              the installer should not allow the user to continue
            - The disk selection should be preserved when moving back and forth between screens
            - Removing the second disk and rescanning should reflect only the remaining disk
              (rhbz#2456923)
        """
        b = self.browser
        i = Installer(b, self.machine)
        s = Storage(b, self.machine)

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

        # Check the auto-selected disk's details
        s.check_disk_selected("vda", selected=True, size="16.1 GB")

        # Wait for the form to be ready before taking a screenshot
        i.check_next_disabled(False)
        b.assert_pixels(
            "#app",
            "storage-step-basic",
            ignore=pixel_tests_ignore,
        )

        # Unselect the only available disk
        s.select_disks([("vda", False)])
        s.wait_no_disks()
        # Check the next buton is disabled if no disks are selected
        i.check_next_disabled()

        # This attaches a disk to the running VM
        # However, since the storage module initialization is long completed
        # the newly added disk, will not be visible in the UI,
        # until the test clicks on the re-scan button
        dev = "vdb"
        vdb_image = self.add_disk(2, target=dev)
        s.rescan_disks(["vda", dev])

        # Check the next button is disabled when not sufficient disk space is available
        s.select_disks([("vda", False), (dev, True)])
        i.check_next_disabled()

        # Check that the disk selection persists when moving next and back
        s.select_disks([("vda", True), (dev, False)])
        s.check_disk_selected("vda", True)
        s.check_disk_selected(dev, False)
        i.next()
        i.back()
        s.check_disk_selected("vda", True)
        s.check_disk_selected(dev, False)

        # Try unselecting both disks and expect and error
        s.select_disks([("vda", False), (dev, False)])
        s.wait_no_disks()
        # Check the next button is disabled if no disks are selected
        i.check_next_disabled()

        # Pixel test the storage step when no disks are selected
        b.assert_pixels(
            "#app",
            "storage-step-no-disks",
            ignore=pixel_tests_ignore,
        )

        # Check that disk selection is kept on Next and Back
        s.select_disks([(dev, True), ("vda", True)])
        i.next()
        i.back()
        for disk in ["vda", dev]:
            s.check_disk_selected(disk)

        # rhbz#2456923: unplug second disk while it remains in the installer's selection, then rescan
        self.rem_disk(vdb_image)
        s.rescan_disks(["vda"])
        s.check_disk_selected("vda", selected=True)
        s.check_disk_selected(dev, selected=False)
        i.check_next_disabled(False)

    def testScenarioSelection(self):
        """
        Description:
            Test that the use-free-space scenario is conditionally available
            and the user selection for scenario is kept when moving back and forth
            between screens
        """
        b = self.browser
        i = Installer(b, self.machine)
        s = Storage(b, self.machine)

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

        # Check the default mode
        s.check_scenario_selected("erase-all")

        # Check that when an unpartitioned disk is selected the "Use free space" scenario is hidden
        s.wait_scenario_visible("use-free-space", False)

        # Check that when a partitioned disk is selected the "Use free space" scenario is visible
        s.partition_disk("/dev/vda", [("10GiB", "lvmpv")])
        s.rescan_disks()
        s.wait_scenario_visible("use-free-space")

        # Storage Configuration
        # The choice is preserved (stored in the backend).
        # The choice is available only if the partitioning was reset
        # (there is enough free space)
        s.set_scenario("use-free-space")
        i.next()
        i.back()
        s.check_scenario_selected("use-free-space")

    def testPartitioningObject(self):
        """
        Description:
            Test which partitioning object ends up being the AppliedPartitioning
            when we go back and forward the storage steps
        """

        b = self.browser
        i = Installer(b, self.machine)
        s = Storage(b, self.machine)

        # Read partitioning data before we do anything
        created_partitioning = s.dbus_get_created_partitioning()

        # Go to Review step
        i.open()
        i.reach(i.steps.REVIEW)

        # Read partitioning data after we went to Review step
        new_applied_partitioning = s.dbus_get_applied_partitioning()
        new_created_partitioning = s.dbus_get_created_partitioning()

        # A new AUTOMATIC partitioning object should be created each time the user enters the review page
        self.assertEqual(len(created_partitioning) + 1, len(new_created_partitioning))
        # The applied partitioning object should be the last one created
        self.assertEqual(new_applied_partitioning, new_created_partitioning[-1])

        created_partitioning = new_created_partitioning

        # Create a few partitioning objects and ensure that these will not mess up with the user's
        # configuration
        for _ in range(10):
            s.dbus_create_partitioning("AUTOMATIC")

        # Go back to the Disk Configuration page and re-enter the review screen.
        # This should create again a new partitioning object and apply it
        # no matter how many partitioning objects were created before
        i.reach_on_sidebar(i.steps.INSTALLATION_METHOD)
        i.reach(i.steps.REVIEW)
        new_applied_partitioning = s.dbus_get_applied_partitioning()
        new_created_partitioning = s.dbus_get_created_partitioning()

        self.assertEqual(len(created_partitioning) + 11, len(new_created_partitioning))
        self.assertEqual(new_applied_partitioning, new_created_partitioning[-1])

        # The applied partitioning should be reset also when going back to installation method
        i.click_step_on_sidebar(i.steps.INSTALLATION_METHOD)
        new_applied_partitioning = s.dbus_get_applied_partitioning()
        self.assertEqual(new_applied_partitioning, "")

    def testCockpitJsErrorHandling(self):
        """
        Description:
            Test that the installer can handle JS errors in the storage module
            and that it does not crash the installer

        Setup:
            - Generate a JS error in the storage module

        Expected results:
            - The installer should not crash
            - The installer should show a critical error dialog
        """
        b = self.browser
        m = self.machine
        i = Installer(b, m)
        s = Storage(b, m)

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

        check_cockpit_module_js_error(b, "Storage")

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

        s.partition_disk("/dev/vda", [("10GiB", "lvmpv")])
        s.partition_disk("/dev/vdb", [("10GiB", "lvmpv")])

        # Create VG on LUKS with the two available disks
        m.execute("""
        vgcreate -y vg /dev/vda1 /dev/vdb1
        lvcreate -y -n lv -l 100%FREE vg
        mkfs.ext4 -F /dev/vg/lv
        vgchange -a n vg
        """)

    @disk_images([("", 15), ("", 15)])
    def testDiskSelectionLVM(self):
        """
        Description:
            Test that the user can't select one PV when VG group spans across multiple disks

        Setup:
            - Create a VG group on LVM with two disks
            - Create a LV on the VG group

        Expected results:
            - The installer should not allow the user to select one of the disks and proceed with the installation
        """


        b = self.browser
        m = self.machine
        i = Installer(b, m)
        s = Storage(b, m)

        i.open()
        i.reach(i.steps.INSTALLATION_METHOD)
        s.set_scenario("erase-all")
        i.reach(i.steps.STORAGE_CONFIGURATION)
        i.next(should_fail=True)
        b.wait_in_text(
            "#anaconda-screen-storage-configuration-step-notification",
            "Selected disks vda contain volume group 'vg' that also uses further unselected disks"
        )


if __name__ == '__main__':
    test_main()
