# Copyright (C) 2020 Red Hat, Inc.
#
# This work is licensed under the GNU GPLv2 or later.
# See the COPYING file in the top-level directory.

# This file is a collection of code used for testing
# code paths primarily via our uitests/

import os


def fake_job_info():
    import random
    total = 1024 * 1024 * 1024
    fakepcent = random.choice(range(1, 100))
    remaining = ((total / 100) * fakepcent)
    return [None, None, None, total, None, remaining]


def fake_interface_addresses(iface, source):
    import libvirt
    mac = iface.macaddr
    if source == libvirt.VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_AGENT:
        ret = {
            'enp1s0': {'hwaddr': mac, 'addrs': [
                {'addr': '10.0.0.1', 'prefix': 24, 'type': 0},
                {'addr': 'fd00:beef::1', 'prefix': 128, 'type': 1},
                {'addr': 'fe80::1', 'prefix': 64, 'type': 1}],
            },
            'lo': {'hwaddr': '00:00:00:00:00:00', 'addrs': [
                {'addr': '127.0.0.1', 'prefix': 8, 'type': 0},
                {'addr': '::1', 'prefix': 128, 'type': 1}],
            },
        }
    else:
        ret = {'vnet0': {'hwaddr': mac, 'addrs': [
            {'addr': '10.0.0.3', 'prefix': 0, 'type': 0}],
        }}
    return ret


def fake_dhcp_leases():
    ret = [{
        'clientid': 'XXX',
        'expirytime': 1598570993,
        'hostname': None,
        'iaid': '1448103320',
        'iface': 'virbr1',
        'ipaddr': 'fd00:beef::2',
        'mac': 'BAD',
        'prefix': 64,
        'type': 1}, {
        'clientid': 'YYY',
        'expirytime': 1598570993,
        'hostname': None,
        'iaid': None,
        'iface': 'virbr1',
        'ipaddr': '10.0.0.2',
        'mac': 'NOPE',
        'prefix': 24,
        'type': 0}]
    return ret


def schedule_fake_agent_event(conn, cb):
    import libvirt
    vmname = conn.config.CLITestOptions.fake_agent_event
    backend = conn.get_backend()
    state = libvirt.VIR_CONNECT_DOMAIN_EVENT_AGENT_LIFECYCLE_STATE_CONNECTED
    reason = libvirt.VIR_CONNECT_DOMAIN_EVENT_AGENT_LIFECYCLE_REASON_CHANNEL

    def time_cb():
        dom = backend.lookupByName(vmname)
        cb(backend, dom, state, reason, None)

    conn.timeout_add(500, time_cb)


def schedule_fake_nodedev_event(conn, lifecycle_cb, update_cb):
    import libvirt
    nodename = conn.config.CLITestOptions.fake_nodedev_event
    backend = conn.get_backend()

    def lifecycle_cb_wrapper():
        nodedev = backend.nodeDeviceLookupByName(nodename)
        state = libvirt.VIR_NODE_DEVICE_EVENT_CREATED
        reason = 0
        lifecycle_cb(backend, nodedev, state, reason, None)

    def update_cb_wrapper():
        nodedev = backend.nodeDeviceLookupByName(nodename)
        update_cb(backend, nodedev, None)

    conn.timeout_add(500, lifecycle_cb_wrapper)
    conn.timeout_add(1000, update_cb_wrapper)


def fake_openauth(conn, cb, data):
    ignore = conn
    import libvirt
    creds = [
        [libvirt.VIR_CRED_USERNAME, "Username", None, None, None],
        [libvirt.VIR_CRED_PASSPHRASE, "Password", None, None, None],
    ]
    cb(creds, data)
    assert all([bool(cred[4]) for cred in creds])


class fakeVirtBootstrap:
    @staticmethod
    def bootstrap(**kwargs):
        import time
        import logging
        log = logging.getLogger("virtBootstrap")
        log.info("mock virtBootstrap msg1")
        kwargs["progress_cb"]({"status": "msg1"})
        time.sleep(.5)
        log.info("mock virtBootstrap msg2")
        kwargs["progress_cb"]({"status": "msg2"})
        time.sleep(.5)
        log.info("mock virtBootstrap msg3")
        kwargs["progress_cb"]({"status": "msg3"})
        if "username" in kwargs:
            raise RuntimeError("fakeVirtBootstrap mock auth failure!")


class CLITestOptionsClass:
    """
    Helper class for parsing and tracking --test-* options.
    The suboptions are:

    * first-run: Run the app with fresh gsettings values saved to
        a keyfile, mimicking a first app run. Also sets
        GSETTINGS to use memory backend, in case any other app
        preferences would be affected. The ui testsuite sets this
        for most tests.

    * xmleditor-enabled: Force the xmleditor gsettings preference on.

    * gsettings-keyfile: Override the gsettings values with those
        from the passed in keyfile, to test with different default
        settings.

    * leak-debug: Enabling this will tell us, at app exit time,
        which vmmGObjects were not garbage collected. This is caused
        by circular references to other objects, like a signal that
        wasn't disconnected. It's not a big deal, but if we have objects
        that can be created and destroyed a lot over the course of
        the app lifecycle, every non-garbage collected class is a
        memory leak. So it's nice to poke at this every now and then
        and try to track down what we need to add to class _cleanup handling.

    * no-events: Force disable libvirt event APIs for testing fallback

    * break_setfacl: For setfacl calls to fail, for test scenarios.
        This is hit via the directory search permissions checking
        for disk image usage for qemu

    * enable-libguestfs: Force enable the libguestfs gsetting
    * disable-libguestfs: Force disable the libguestfs gsetting

    * test-managed-save: Triggers a couple conditions for testing
        managed save issues

    * test-vm-run-fail: Make VM run fail, so we can test the error path

    * spice-agent: Make spice-agent detection return true in viewer.py

    * firstrun-uri: If set, use this as the initial connection URI
        if we are doing firstrun testing
    * fake-no-libvirtd: If doing firstrun testing, fake that
        libvirtd is not installed
    * fake-vnc-username: Fake VNC username auth request
    * fake-console-resolution: Fake viewer console resolution response.
        Spice doesn't return values here when we are just testing
        against seabios in uitests, this fakes it to hit more code paths
    * fake-systray: Enable the fake systray window
    * fake-virtbootstrap: Mock the virtBootstrap module, since getting
        it to actually work across fedora versions is hard
    * object-denylist=NAME: Make object initialize for that name
        fail to test some connection code paths
    * conn-crash: Test connection abruptly closing like when
        libvirtd is restarted.
    * fake-agent-event: Fake a qemu guest agent API event
    * fake-nodedev-event: Fake nodedev API events
    * fake-openauth: Fake user+pass response from libvirt openauth,
        for testing the TCP URI auth dialog
    * fake-session-error: Fake a connection open error that
        triggers logind session lookup
    * short-poll: Use a polling interval of only .1 seconds to speed
        up the uitests a bit
    """
    def __init__(self, test_options_str):
        optset = set()
        for optstr in test_options_str:
            optset.update(set(optstr.split(",")))

        first_run = self._parse(optset)
        self._process(first_run)

    def _parse(self, optset):
        def _get(optname):
            if optname not in optset:
                return False
            optset.remove(optname)
            return True

        def _get_value(optname):
            for opt in optset:
                if opt.startswith(optname + "="):
                    optset.remove(opt)
                    return opt.split("=", 1)[1]

        first_run = _get("first-run")
        self.leak_debug = _get("leak-debug")
        self.no_events = _get("no-events")
        self.xmleditor_enabled = _get("xmleditor-enabled")
        self.gsettings_keyfile = _get_value("gsettings-keyfile")
        self.break_setfacl = _get("break-setfacl")
        self.disable_libguestfs = _get("disable-libguestfs")
        self.enable_libguestfs = _get("enable-libguestfs")
        self.test_managed_save = _get("test-managed-save")
        self.test_vm_run_fail = _get("test-vm-run-fail")
        self.spice_agent = _get("spice-agent")
        self.firstrun_uri = _get_value("firstrun-uri")
        self.fake_no_libvirtd = _get("fake-no-libvirtd")
        self.fake_vnc_username = _get("fake-vnc-username")
        self.fake_console_resolution = _get("fake-console-resolution")
        self.fake_systray = _get("fake-systray")
        self.object_denylist = _get_value("object-denylist")
        self.conn_crash = _get("conn-crash")
        self.fake_agent_event = _get_value("fake-agent-event")
        self.fake_nodedev_event = _get_value("fake-nodedev-event")
        self.fake_openauth = _get("fake-openauth")
        self.fake_session_error = _get("fake-session-error")
        self.short_poll = _get("short-poll")
        self.fake_virtbootstrap = _get("fake-virtbootstrap")

        if optset:  # pragma: no cover
            raise RuntimeError("Unknown --test-options keys: %s" % optset)

        return first_run

    def _process(self, first_run):
        if first_run:
            # So other settings like gtk are reset and not affected
            os.environ["GSETTINGS_BACKEND"] = "memory"

        if first_run and not self.gsettings_keyfile:
            import atexit
            import tempfile
            filename = tempfile.mktemp(prefix="virtmanager-firstrun-keyfile")
            self.gsettings_keyfile = filename
            atexit.register(lambda: os.unlink(filename))

        if self.break_setfacl:
            import virtinst.diskbackend
            def fake_search(*args, **kwargs):
                raise RuntimeError("Fake search fix fail from test suite")
            virtinst.diskbackend.SETFACL = "getfacl"
            # pylint: disable=protected-access
            virtinst.diskbackend._fix_perms_chmod = fake_search