# SPDX-License-Identifier: GPL-3.0-or-later
"""
Worker class to build Vagrant images.
"""

import logging
import os
import pathlib
import subprocess

from .. import library
from .amd64 import AMD64ImageBuilder

logger = logging.getLogger(__name__)

vagrant_ssh_keys = 'https://raw.githubusercontent.com/mitchellh/vagrant/' \
    'master/keys/vagrant.pub'


class VagrantImageBuilder(AMD64ImageBuilder):
    """Image builder for Vagrant package."""
    include_contrib = True
    vagrant_extension = '.box'

    @classmethod
    def get_target_name(cls):
        """Return the name of the target for an image builder."""
        return 'vagrant'

    def build(self):
        """Run the image building process."""
        # Create a .img hard disk image
        self.make_image()

        # Convert hard disk image from .img file to .vmdk file
        build_path = pathlib.Path(self.arguments.build_dir)
        vm_file = build_path / 'box-disk001.vmdk'
        self._create_vm_file(self.image_file, vm_file)
        os.remove(self.image_file)

        # Create metadata.json
        metadata_file = build_path / 'metadata.json'
        metadata_file.write_text('{"provider":"virtualbox"}')

        # Create Vagrantfile from a template in ./vagrant/ directory
        vagrant_file = pathlib.Path(self.arguments.build_dir) / 'Vagrantfile'
        vagrant_text = self._read_template('Vagrantfile')
        vagrant_file.write_text(vagrant_text)

        # Create box.ovf from a template in ./vagrant/ directory
        ovf_file = pathlib.Path(self.arguments.build_dir) / 'box.ovf'
        ovf_text = self._read_template('box.ovf')
        ovf_file.write_text(ovf_text)

        # Create a vagrant .box file for upload
        box_file = pathlib.Path(self.image_file).with_suffix(
            self.vagrant_extension)
        self._create_vagrant_box(box_file, vm_file, metadata_file,
                                 vagrant_file, ovf_file)

        # Create a hash file
        self._store_hash(box_file)

    def extra_setup(self, state):
        """Run additional setup needed by vagrant. Called from make_image()."""
        if self.arguments.distribution in ['unstable', 'sid']:
            # XXX: Only unstable will have VirtualBox's shared folder support
            # and time synchronization service.
            self._install_guest_additions(state, self.release_components)

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

        self._setup_vagrant_user(state)

    def _install_guest_additions(self, state, release_components):
        """Install VirtualBox Guest Additions into the VM image."""
        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

        library.install_package(state, 'virtualbox-guest-utils')

    def _install_dev_packages(self, state):
        """Install build deps and other useful packages for development."""
        logger.info('Installing freedombox build dependencies.')
        library.run_in_chroot(state,
                              ['apt-get', 'build-dep', '-y', 'freedombox'])

        logger.info('Installing extra development utilities.')
        packages = [
            'byobu', 'ncurses-term', 'parted', 'python3-dev', 'python3-pip',
            'python3-pytest'
        ]
        library.run_in_chroot(state, ['apt-get', 'install', '-y'] + packages)

    def _setup_vagrant_user(self, state):
        """Create vagrant user, setup SSH keys, groups, and sudo."""
        logger.info('Setting up vagrant user.')

        def _in_chroot(command):
            library.run_in_chroot(state, command)

        _in_chroot(
            ['adduser', '--disabled-password', '--gecos', '', 'vagrant'])

        _in_chroot(['mkdir', '/home/vagrant/.ssh'])
        _in_chroot([
            'wget', '-O', '/home/vagrant/.ssh/authorized_keys',
            vagrant_ssh_keys
        ])
        _in_chroot(['chown', '-R', 'vagrant:vagrant', '/home/vagrant/.ssh'])
        _in_chroot(['chmod', '0700', '/home/vagrant/.ssh'])
        _in_chroot(['chmod', '0600', '/home/vagrant/.ssh/authorized_keys'])

        _in_chroot(['usermod', '-a', '-G', 'sudo', 'vagrant'])
        file_path = pathlib.Path(
            library.path_in_mount(state, 'etc/sudoers.d/vagrant'))
        file_path.write_text('vagrant ALL=(ALL) NOPASSWD: ALL')

    def _create_vm_file(self, image_file, vm_file):
        """Convert the disk image into a virtual machine image."""
        library.run(['qemu-img', 'convert', '-O', 'vmdk', image_file, vm_file])

    def _read_template(self, file_name):
        """Read a template file for a constituent of vagrant .box file."""
        file_path = pathlib.Path(__file__).parent / 'vagrant' / file_name
        return file_path.read_text()

    def _create_vagrant_box(self, box_file, vm_file, metadata_file,
                            vagrant_file, ovf_file):
        """Create a vagrant .box file from constituents."""
        library.run([
            'tar', '-cvzf', box_file.name, vm_file.name, metadata_file.name,
            vagrant_file.name, ovf_file.name
        ],
                    cwd=vm_file.parent)

    def _store_hash(self, box_file):
        """Store the SHA-256 hash of the vagrant box file."""
        output = subprocess.check_output(['sha256sum', box_file])
        result = output.decode()
        logger.info('sha256sum: %s', result)
        hash_filename = box_file.with_suffix('.sha256')
        with open(hash_filename, 'w') as hash_file:
            hash_file.write(result)
