#!/usr/bin/python3
#
# This file is part of Freedom Maker.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
"""
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(
        '--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()

    if arguments.distribution not in ['stable', 'buster']:
        # 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():
    """Install VirtualBox Guest Additions into the VM."""
    run_vm_command('sudo sed -i "s/main/main contrib/g" /etc/apt/sources.list')
    run_vm_command('sudo apt-get update')
    run_vm_command('sudo DEBIAN_FRONTEND=noninteractive apt install -y '
                   'linux-headers-$(uname -r) virtualbox-guest-dkms '
                   '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."""
    run_vm_command('sudo shutdown now', ignore_errors)
    time.sleep(30)


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."""
    subprocess.run(['VBoxManage', 'modifyvm', vm_name, '--hda', 'none'],
                   ignore_errors)
    subprocess.run(['VBoxManage', 'unregistervm', vm_name, '--delete'],
                   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()
