# Copyright 2021 Offensive Security
# SPDX-license-identifier: GPL-3.0-only

from kali_tweaks.utils import (
    apt_install_pkgs,
    disable_service_now,
    enable_service_now,
    get_helper,
    install_program,
    is_pkg_installed,
    is_program_installed,
    is_service_up,
    logger,
    run,
    run_as_root,
    say,
)


class VirtualizationSetting:
    def __init__(self):
        self.type = None
        self.pretty_name = None
        self.config_desc = None
        self.undo_desc = None
        self.help_url = "https://www.kali.org/docs/virtualization/"
        self.packages = []  # packages to install
        self.scripts = []  # scripts to install
        self.config_script = None  # a config script to execute
        self.services = []  # systemd services to start

    def detect(self):
        res = run("systemd-detect-virt")
        if res.returncode != 0:
            return

        virt = res.stdout.strip()
        logger.debug("Detected virt: %s", virt)

        self.type = virt

        # There is no need to explicitly start the virt services, as they
        # are not "network services" (see [1]), therefore they are either:
        # 1. already started if they were already installed when the
        #    system was booted.
        # 2. started by apt when kali-tweaks installs the corresponding
        #    package.
        #
        # That's why there's no need to list it here. We only mention
        # services that are not auto-started and need an explicit start
        # and enable, like xrdp, which is indeed a network service.
        #
        # [1]: https://www.kali.org/docs/policy/kali-linux-network-service-policy/

        if virt in ["kvm", "qemu"]:
            self.pretty_name = virt.upper()
            self.config_desc = "Install additional packages for the SPICE protocol"
            self.packages = ["spice-vdagent", "xserver-xorg-video-qxl"]
        elif virt == "oracle":
            self.pretty_name = "VirtualBox"
            self.config_desc = "Install additional packages for VirtualBox"
            self.help_url += "install-virtualbox-guest-additions/"
            self.packages = ["virtualbox-guest-x11"]
        elif virt == "vmware":
            self.pretty_name = "VMWare"
            self.config_desc = "Install additional packages and scripts for VMware"
            self.help_url += "install-vmware-guest-tools/"
            self.packages = ["open-vm-tools-desktop"]
            self.scripts = [
                "/usr/local/sbin/mount-shared-folders",
                "/usr/local/sbin/restart-vm-tools",
            ]
        elif virt == "microsoft":
            self.pretty_name = "Hyper-V"
            self.config_desc = "Configure the system for Hyper-V Enhanced Session Mode"
            self.undo_desc = "Remove configuration for Hyper-V Enhanced Session Mode"
            self.help_url += "install-hyper-v-guest-enhanced-session-mode/"
            self.packages = ["hyperv-daemons", "xrdp"]
            if is_pkg_installed("pipewire"):
                self.packages.append("pipewire-module-xrdp")
            elif is_pkg_installed("pulseaudio"):
                self.packages.append("pulseaudio-module-xrdp")
            self.config_script = get_helper("hyperv-enhanced-mode")
            self.services = ["xrdp"]
        else:
            logger.debug("Unsupported virt: %s", virt)
            self.type = None

    def is_configurable(self):
        """
        Return whether this virtualization tech is configurable.
        """
        if self.packages or self.scripts or self.config_script or self.services:
            return True
        return False

    def is_unconfigurable(self):
        """
        Return whether this virtualization tech can be unconfigured.
        """
        if self.config_script or self.services:
            return True
        return False

    def needs_configuration(self):
        """
        Return whether the system needs to be configured for this
        virtualization tech.
        """
        # Do we need to install packages?
        for pkg in self.packages:
            if not is_pkg_installed(pkg):
                return True

        # Do we need to install scripts?
        for script in self.scripts:
            if not is_program_installed(script):
                return True

        # Do we need to run a config script?
        if self.config_script:
            res = run(f"{self.config_script} check")
            if res.returncode != 0:  # unconfigured
                return True

        # Do we need to start a service?
        for svc in self.services:
            if not is_service_up(svc):
                return True

        return False

    def configure(self):
        """
        Configure for the virtualization type.

        This method raises RuntimeError if something goes wrong.
        """
        # Install missing packages
        packages = []
        for pkg in self.packages:
            if not is_pkg_installed(pkg):
                packages.append(pkg)
        if packages:
            apt_install_pkgs(packages)

        # Install scripts
        for script in self.scripts:
            install_program(script)

        # Run config script
        if self.config_script:
            say(f"Configuring {self.pretty_name}")
            res = run_as_root(f"{self.config_script} enable", interactive=True)
            if res.returncode != 0:
                raise RuntimeError(
                    f"Couldn't configure the system for {self.pretty_name}"
                )

        # Enable services
        for svc in self.services:
            enable_service_now(svc)

    def unconfigure(self):
        """
        Unconfigure for the virtualization type.

        This is not perfect. For example, we stop services. But did
        we really started it, or was it started already before?
        """
        # Disable services
        for svc in reversed(self.services):
            disable_service_now(svc)

        # Run config script
        if self.config_script:
            say(f"Unconfiguring {self.pretty_name}")
            res = run_as_root(f"{self.config_script} disable", interactive=True)
            if res.returncode != 0:
                raise RuntimeError(
                    f"Couldn't unconfigure the system for {self.pretty_name}"
                )

        # Scripts and packages that might have been installed by the
        # configure() method are left behind.
