#!/usr/bin/python3
# SPDX-License-Identifier: GPL-3.0-or-later
"""
Utility for converting VirtualBox disk images into Vagrant boxes.
"""

import argparse
import logging
import os
import random
import shutil
import string
import subprocess
import sys
import time

vm_name = 'freedom-maker-vagrant-package'

password = ''.join(random.SystemRandom().choice(string.ascii_letters +
                                                string.digits)
                   for x in range(20))

logger = logging.getLogger(__name__)  # pylint: disable=invalid-name


def main():
    """The main entry point."""
    logging.basicConfig(level=logging.INFO)

    parser = argparse.ArgumentParser(
        description='Convert VirtualBox disk image into Vagrant box')
    parser.add_argument('image', help='Disk image file (.vdi) to be converted')
    parser.add_argument('--distribution',
                        default='unstable',
                        help='Debian release used in built image')
    parser.add_argument('--release-components',
                        nargs='+',
                        help='Release components used in apt sources list')
    parser.add_argument(
        '--output',
        default='package.box',
        help='Path of the output vagrant box file (default: package.box)')

    arguments = parser.parse_args()

    check_requirements()

    set_fbx_user_password(arguments)

    # In case an old VM exists, try to clean up first.
    stop_vm(ignore_errors=True)
    delete_vm(ignore_errors=True)

    setup_vm(arguments)
    start_vm()

    create_vagrant_user()
    set_ssh_key()
    setup_sudo()
    if arguments.distribution in ['unstable', 'sid']:
        # XXX: Only unstable will have VirtualBox's shared folder
        # support and time synchronization service.
        install_guest_additions(arguments.release_components)

    if arguments.distribution not in ['stable', 'bullseye']:
        # XXX: Stable will not have packages required to build
        # freedombox package. This requires latest debhelper version,
        # which can be installed from backports.
        install_dev_packages()

    stop_vm()
    package_vm(arguments)
    delete_vm()


def check_requirements():
    """Check that the necessary requirements are available."""
    if os.geteuid() != 0:
        logger.error('Due to limitations of the tools involved, you need to '
                     'run this command as "root" user or using the "sudo" '
                     'command.')
        sys.exit(-1)

    if not shutil.which('VBoxManage'):
        logger.error('"VBoxManage" command not found.  It is provided by the '
                     'package "virtualbox" in the "contrib" section of the '
                     'Debian repository.')
        sys.exit(-1)

    if not shutil.which('vagrant'):
        logger.error('"vagrant" command not found.  On Debian based '
                     'systems it is provided by the package "vagrant".')
        sys.exit(-1)


def set_fbx_user_password(arguments):
    """Set password for 'fbx' user using passwd-in-image script."""
    passwd_tool = os.path.join(os.path.dirname(__file__), 'passwd-in-image')
    subprocess.run([
        'sudo', 'python3', passwd_tool, arguments.image, 'fbx', '--password',
        password
    ],
                   check=True)


def setup_vm(arguments):
    """Create and configure VirtualBox VM."""
    subprocess.run([
        'VBoxManage', 'createvm', '--name', vm_name, '--ostype', 'Debian_64',
        '--register'
    ],
                   check=True)
    subprocess.run([
        'VBoxManage', 'storagectl', vm_name, '--name', 'SATA Controller',
        '--add', 'sata', '--controller', 'IntelAHCI'
    ],
                   check=True)
    subprocess.run([
        'VBoxManage', 'storageattach', vm_name, '--storagectl',
        'SATA Controller', '--port', '0', '--device', '0', '--type', 'hdd',
        '--medium', arguments.image
    ],
                   check=True)
    subprocess.run([
        'VBoxManage', 'modifyvm', vm_name, '--pae', 'on', '--memory', '1024',
        '--vram', '128', '--nic1', 'nat', '--natpf1', ',tcp,,2222,,22'
    ],
                   check=True)


def start_vm():
    """Start the VM."""
    subprocess.run(['VBoxManage', 'startvm', vm_name, '--type', 'headless'],
                   check=True)
    time.sleep(180)


def create_vagrant_user():
    """Create vagrant user."""
    run_vm_command('sudo adduser --disabled-password --gecos "" vagrant')
    run_vm_command(
        'sudo sed -i "s/fbx/fbx vagrant/g" /etc/security/access.conf')


def set_ssh_key():
    """Install insecure public key for vagrant user.

    This will be replaced by Vagrant during first boot.
    """
    run_vm_command('sudo mkdir /home/vagrant/.ssh')
    run_vm_command(
        'sudo wget -O /home/vagrant/.ssh/authorized_keys '
        'https://raw.githubusercontent.com/mitchellh/vagrant/master/keys/'
        'vagrant.pub')
    run_vm_command('sudo chown -R vagrant:vagrant /home/vagrant/.ssh')
    run_vm_command('sudo chmod 0700 /home/vagrant/.ssh')
    run_vm_command('sudo chmod 0600 /home/vagrant/.ssh/authorized_keys')


def setup_sudo():
    """Setup password-less sudo for vagrant user."""
    run_vm_command('sudo usermod -a -G sudo vagrant')
    run_vm_command('sudo su -c "echo \'vagrant ALL=(ALL) NOPASSWD: ALL\' '
                   '>/etc/sudoers.d/vagrant"')


def install_guest_additions(release_components):
    """Install VirtualBox Guest Additions into the VM."""
    if 'contrib' not in release_components:
        logger.warning('Skipping installation of VirtualBox Guest Additions.')
        logger.warning('virtualbox-guest-utils is only available with the '
                       '"contrib" release component.')
        logger.warning('The following release components were used in this '
                       'build: ' + ' '.join(release_components))
        return

    run_vm_command('sudo apt install -y virtualbox-guest-utils')


def install_dev_packages():
    """Install build deps and other useful packages for development."""
    run_vm_command('sudo apt build-dep -y freedombox')
    run_vm_command('sudo apt install -y byobu ncurses-term parted '
                   'python3-dev python3-pip python3-pytest '
                   'python3-pytest-django sshpass')


def stop_vm(ignore_errors=False):
    """Shutdown the VM."""
    logger.info('Stopping the VM...')
    run_vm_command('sudo shutdown now', ignore_errors)
    time.sleep(30)

    # This is a backup step in case the above did not work for some reason.
    subprocess.run(['VBoxManage', 'controlvm', vm_name, 'poweroff'])


def package_vm(arguments):
    """Convert the VM into a Vagrant box."""
    subprocess.run([
        'vagrant', 'package', '--base', vm_name, '--output', arguments.output
    ],
                   check=True)


def delete_vm(ignore_errors=False):
    """Delete the VM."""
    logger.info('Deleting the VM...')
    subprocess.run(['VBoxManage', 'modifyvm', vm_name, '--hda', 'none'],
                   check=not ignore_errors)
    subprocess.run(['VBoxManage', 'unregistervm', vm_name, '--delete'],
                   check=not ignore_errors)


def run_vm_command(command, ignore_errors=False):
    """Send a command to the VM through SSH."""
    echo = subprocess.Popen(['echo', password], stdout=subprocess.PIPE)
    process = subprocess.Popen([
        'sshpass', '-p', password, 'ssh', '-o', 'UserKnownHostsFile=/dev/null',
        '-o', 'StrictHostKeyChecking=no', '-t', '-t', '-p', '2222',
        'fbx@127.0.0.1', command
    ],
                               stdin=echo.stdout)
    process.communicate()
    if not ignore_errors and process.returncode:
        logger.error('Command run in VM failed: ' + command)
        stop_vm(ignore_errors=True)
        delete_vm(ignore_errors=True)
        sys.exit(-1)


if __name__ == '__main__':
    main()
