#!/usr/bin/python3

# This file is part of Cockpit.
#
# Copyright (C) 2021 Red Hat, Inc.
#
# Cockpit is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
#
# Cockpit is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Cockpit; If not, see <http://www.gnu.org/licenses/>.

import os
import sys

# import Cockpit's machinery for test VMs and its browser test API
TEST_DIR = os.path.dirname(__file__)
sys.path.append(os.path.join(TEST_DIR, "common"))
sys.path.append(os.path.join(os.path.dirname(TEST_DIR), "bots/machine"))

from machineslib import VirtualMachinesCase  # noqa
from testlib import nondestructive, test_main, wait, skipImage  # noqa


@nondestructive
class TestMachinesConsoles(VirtualMachinesCase):

    @skipImage('spice-server does not exist on RHEL 9', "rhel-9-0", "rhel-9-1", "centos-9-stream")
    def testExternalConsole(self):
        b = self.browser

        self.createVm("subVmTest1", graphics="spice")

        self.login_and_go("/machines")
        b.wait_in_text("body", "Virtual machines")
        self.waitVmRow("subVmTest1")

        b.wait_in_text("#vm-subVmTest1-system-state", "Running")  # running or paused
        self.goToVmPage("subVmTest1")

        # since VNC is not defined for this VM, the view for "Desktop Viewer" is rendered by default
        b.wait_in_text(".pf-c-console__manual-connection dl > div:first-child dd", "127.0.0.1")
        b.wait_in_text(".pf-c-console__manual-connection dl > div:nth-child(2) dd", "5900")

        b.click(".pf-c-console__remote-viewer-launch-vv")  # "Launch Remote Viewer" button
        b.wait_visible("#dynamically-generated-file")  # is .vv file generated for download?
        self.assertEqual(b.attr("#dynamically-generated-file", "href"),
                         u"data:application/x-virt-viewer,%5Bvirt-viewer%5D%0Atype%3Dspice%0Ahost%3D127.0.0.1%0Aport%3D5900%0Adelete-this-file%3D1%0Afullscreen%3D0%0A")

        # HACK: clicking 'Launch Remote Viewer' kills execution context and thus CDP fails
        b.reload()
        b.enter_page("/machines")

        # Go to the expanded console view
        b.click("button:contains(Expand)")

        # Check "More information"
        b.click('.pf-c-console__remote-viewer .pf-c-expandable-section__toggle')
        b.wait_in_text('.pf-c-expandable-section__content',
                       'Clicking "Launch remote viewer" will download')

        b.assert_pixels("#vm-subVmTest1-consoles-page", "vm-details-console-external")

    def testInlineConsole(self, urlroot=""):
        b = self.browser

        args = self.createVm("subVmTest1", "vnc")

        if urlroot != "":
            self.machine.execute(f'mkdir -p /etc/cockpit/ && echo "[WebService]\nUrlRoot={urlroot}" > /etc/cockpit/cockpit.conf')

        self.login_and_go("/machines", urlroot=urlroot)
        b.wait_in_text("body", "Virtual machines")
        self.waitVmRow("subVmTest1")

        b.wait_in_text("#vm-subVmTest1-system-state", "Running")  # running or paused
        self.goToVmPage("subVmTest1")

        # since VNC is defined for this VM, the view for "In-Browser Viewer" is rendered by default
        b.wait_visible(".pf-c-console__vnc canvas")

        # make sure the log file is full - then empty it and reboot the VM - the log file should fill up again
        wait(lambda: "login as 'cirros' user." in self.machine.execute(f"cat {args['logfile']}"), delay=3)

        self.machine.execute(f"echo '' > {args['logfile']}")
        b.click("#subVmTest1-system-vnc-sendkey button")
        b.click("#ctrl-alt-Delete")
        wait(lambda: "Requesting system reboot" in self.machine.execute(f"cat {args['logfile']}"), delay=3)

    def testInlineConsoleWithUrlRoot(self, urlroot=""):
        self.testInlineConsole(urlroot="/webcon")

    def testSerialConsole(self):
        b = self.browser
        m = self.machine
        name = "vmWithSerialConsole"

        self.createVm(name, graphics='vnc', ptyconsole=True)

        self.login_and_go("/machines")
        b.wait_in_text("body", "Virtual machines")
        self.waitVmRow(name)

        self.goToVmPage(name)
        b.wait_in_text(f"#vm-{name}-system-state", "Running")

        b.click("#pf-c-console__type-selector")
        b.wait_visible("#pf-c-console__type-selector + .pf-c-select__menu")
        b.click("#SerialConsole button")
        b.wait_not_present("#pf-c-console__type-selector + .pf-c-select__menu")

        b.wait_in_text(f"#{name}-terminal .xterm-accessibility-tree", f"Connected to domain '{name}'")

        # in case the OS already finished booting, press Enter into the console to re-trigger the login prompt
        b.focus(f"#{name}-terminal .xterm-accessibility-tree")
        b.key_press("\r")

        with b.wait_timeout(60):
            b.wait_in_text(f"#{name}-terminal .xterm-accessibility-tree", "cirros login")

        b.click(f"#{name}-serialconsole-disconnect")
        b.wait_text(f"#{name}-terminal", "Disconnected from serial console. Click the connect button.")

        b.click(f"#{name}-serialconsole-connect")
        b.wait_in_text(f"#{name}-terminal .xterm-accessibility-tree > div:nth-child(1)", "Connected to domain")

        b.click("button:contains(Expand)")
        b.assert_pixels("#vm-vmWithSerialConsole-consoles-page", "vm-details-console-serial",
                        ignore=[".pf-c-console__vnc"])

        # Add a second serial console
        m.execute("virsh destroy vmWithSerialConsole && virt-xml --add-device vmWithSerialConsole --console pty,target_type=virtio && virsh start vmWithSerialConsole")
        b.click("#pf-c-console__type-selector")
        b.wait_visible("#pf-c-console__type-selector + .pf-c-select__menu")
        b.click("li:contains('Serial console (console0)') button")
        b.wait(lambda: m.execute("ps aux | grep 'virsh -c qemu:///system console vmWithSerialConsole console0'"))
        b.click("#pf-c-console__type-selector")
        b.click("li:contains('Serial console (console1)') button")
        b.wait(lambda: m.execute("ps aux | grep 'virsh -c qemu:///system console vmWithSerialConsole console1'"))

        # disconnecting the serial console closes the pty channel
        self.allow_journal_messages("connection unexpectedly closed by peer",
                                    ".*Connection reset by peer")
        self.allow_browser_errors("Disconnection timed out.")
        self.allow_journal_messages(".* couldn't shutdown fd: Transport endpoint is not connected")
        self.allow_journal_messages("127.0.0.1:5900: couldn't read: Connection refused")

    def testSwitchConsoleFromSerialToGraphical(self):
        b = self.browser
        name = "vmForSwitching"

        self.createVm(name, graphics="vnc", ptyconsole=True)

        self.login_and_go("/machines")
        b.wait_in_text("body", "Virtual machines")

        self.waitVmRow(name)
        self.goToVmPage(name)
        b.wait_in_text(f"#vm-{name}-system-state", "Running")

        b.wait_visible(f"#vm-{name}-consoles")
        b.wait_visible(".pf-c-console__vnc canvas")

        b.click("#pf-c-console__type-selector")
        b.wait_visible("#pf-c-console__type-selector + .pf-c-select__menu")
        b.click("#SerialConsole button")
        b.wait_not_present("#pf-c-console__type-selector + .pf-c-select__menu")

        b.wait_not_present(".pf-c-console__vnc canvas")
        b.wait_visible(f"#{name}-terminal")

        b.wait_not_present("#navbar-oops")

        self.allowed_messages.append("connection unexpectedly closed by peer")


if __name__ == '__main__':
    test_main()
