from bootstrapvz.base import Task
from bootstrapvz.common import phases
from bootstrapvz.common.tasks import workspace
from bootstrapvz.common.tools import rel_path
import os
import shutil
import json

assets = rel_path(__file__, 'assets')


class CheckBoxPath(Task):
    description = 'Checking if the vagrant box file already exists'
    phase = phases.validation

    @classmethod
    def run(cls, info):
        box_basename = info.manifest.name.format(**info.manifest_vars)
        box_name = box_basename + '.box'
        box_path = os.path.join(info.manifest.bootstrapper['workspace'], box_name)
        if os.path.exists(box_path):
            from bootstrapvz.common.exceptions import TaskError
            msg = 'The vagrant box `{name}\' already exists at `{path}\''.format(name=box_name, path=box_path)
            raise TaskError(msg)
        info._vagrant['box_name'] = box_name
        info._vagrant['box_path'] = box_path


class CreateVagrantBoxDir(Task):
    description = 'Creating directory for the vagrant box'
    phase = phases.preparation
    predecessors = [workspace.CreateWorkspace]

    @classmethod
    def run(cls, info):
        info._vagrant['folder'] = os.path.join(info.workspace, 'vagrant')
        os.mkdir(info._vagrant['folder'])


class AddPackages(Task):
    description = 'Add packages that vagrant depends on'
    phase = phases.preparation

    @classmethod
    def run(cls, info):
        info.packages.add('openssh-server')
        info.packages.add('sudo')
        info.packages.add('nfs-client')


class CreateVagrantUser(Task):
    description = 'Creating the vagrant user'
    phase = phases.system_modification

    @classmethod
    def run(cls, info):
        from bootstrapvz.common.tools import log_check_call
        log_check_call(['chroot', info.root,
                        'useradd',
                        '--create-home', '--shell', '/bin/bash',
                        'vagrant'])


class PasswordlessSudo(Task):
    description = 'Allowing the vagrant user to use sudo without a password'
    phase = phases.system_modification

    @classmethod
    def run(cls, info):
        sudo_vagrant_path = os.path.join(info.root, 'etc/sudoers.d/vagrant')
        with open(sudo_vagrant_path, 'w') as sudo_vagrant:
            sudo_vagrant.write('vagrant ALL=(ALL) NOPASSWD:ALL')
        import stat
        ug_read_only = (stat.S_IRUSR | stat.S_IRGRP)
        os.chmod(sudo_vagrant_path, ug_read_only)


class AddInsecurePublicKey(Task):
    description = 'Adding vagrant insecure public key'
    phase = phases.system_modification
    predecessors = [CreateVagrantUser]

    @classmethod
    def run(cls, info):
        ssh_dir = os.path.join(info.root, 'home/vagrant/.ssh')
        os.mkdir(ssh_dir)

        authorized_keys_source_path = os.path.join(assets, 'authorized_keys')
        with open(authorized_keys_source_path, 'r') as authorized_keys_source:
            insecure_public_key = authorized_keys_source.read()

        authorized_keys_path = os.path.join(ssh_dir, 'authorized_keys')
        with open(authorized_keys_path, 'a') as authorized_keys:
            authorized_keys.write(insecure_public_key)

        import stat
        os.chmod(ssh_dir, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)
        os.chmod(authorized_keys_path, stat.S_IRUSR | stat.S_IWUSR)

        # We can't do this directly with python, since getpwnam gets its info from the host
        from bootstrapvz.common.tools import log_check_call
        log_check_call(['chroot', info.root,
                        'chown', 'vagrant:vagrant',
                        '/home/vagrant/.ssh', '/home/vagrant/.ssh/authorized_keys'])


class SetRootPassword(Task):
    description = 'Setting the root password to `vagrant\''
    phase = phases.system_modification

    @classmethod
    def run(cls, info):
        from bootstrapvz.common.tools import log_check_call
        log_check_call(['chroot', info.root, 'chpasswd'], 'root:vagrant')


class PackageBox(Task):
    description = 'Packaging the volume as a vagrant box'
    phase = phases.image_registration

    @classmethod
    def run(cls, info):
        vagrantfile_source = os.path.join(assets, 'Vagrantfile')
        vagrantfile = os.path.join(info._vagrant['folder'], 'Vagrantfile')
        shutil.copy(vagrantfile_source, vagrantfile)

        import random
        mac_address = '080027{mac:06X}'.format(mac=random.randrange(16 ** 6))
        from bootstrapvz.common.tools import sed_i
        sed_i(vagrantfile, '\\[MAC_ADDRESS\\]', mac_address)

        vagrant_provider = info.manifest.plugins['vagrant'].get('provider', 'virtualbox')
        metadata = {'provider': vagrant_provider}

        if vagrant_provider == 'libvirt':
            metadata['format'] = info.manifest.volume['backing']
            virtual_size = info.volume.size.bytes.get_qty_in('G')
            metadata['virtual_size'] = virtual_size

        metadata_file = os.path.join(info._vagrant['folder'], 'metadata.json')
        with open(metadata_file, 'w') as f:
            json.dump(metadata, f)

        from bootstrapvz.common.tools import log_check_call
        if vagrant_provider == 'libvirt':
            disk_name = 'box.img'
        else:
            disk_name = 'box-disk1.' + info.volume.extension
            ovf_path = os.path.join(info._vagrant['folder'], 'box.ovf')
            cls.write_ovf(info, ovf_path, mac_address, disk_name)

        disk_link = os.path.join(info._vagrant['folder'], disk_name)
        log_check_call(['ln', '-s', info.volume.image_path, disk_link])

        box_files = os.listdir(info._vagrant['folder'])
        log_check_call(['tar', '--create', '--gzip', '--dereference',
                        '--file', info._vagrant['box_path'],
                        '--directory', info._vagrant['folder']] + box_files
                       )
        import logging
        logging.getLogger(__name__).info('The vagrant box has been placed at ' + info._vagrant['box_path'])

    @classmethod
    def write_ovf(cls, info, destination, mac_address, disk_name):
        namespaces = {'ovf':     'http://schemas.dmtf.org/ovf/envelope/1',
                      'rasd':    'http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ResourceAllocationSettingData',
                      'vssd':    'http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_VirtualSystemSettingData',
                      'xsi':     'http://www.w3.org/2001/XMLSchema-instance',
                      'vbox':    'http://www.virtualbox.org/ovf/machine',
                      }

        def attr(element, name, value=None):
            for prefix, ns in namespaces.iteritems():
                name = name.replace(prefix + ':', '{' + ns + '}')
            if value is None:
                return element.attrib[name]
            else:
                element.attrib[name] = str(value)

        template_path = os.path.join(assets, 'box.ovf')
        import xml.etree.ElementTree as ET
        template = ET.parse(template_path)
        root = template.getroot()

        [disk_ref] = root.findall('./ovf:References/ovf:File', namespaces)
        attr(disk_ref, 'ovf:href', disk_name)

        # List of OVF disk format URIs
        # Snatched from VBox source (src/VBox/Main/src-server/ApplianceImpl.cpp:47)
        # ISOURI = "http://www.ecma-international.org/publications/standards/Ecma-119.htm"
        # VMDKStreamURI = "http://www.vmware.com/interfaces/specifications/vmdk.html#streamOptimized"
        # VMDKSparseURI = "http://www.vmware.com/specifications/vmdk.html#sparse"
        # VMDKCompressedURI = "http://www.vmware.com/specifications/vmdk.html#compressed"
        # VMDKCompressedURI2 = "http://www.vmware.com/interfaces/specifications/vmdk.html#compressed"
        # VHDURI = "http://go.microsoft.com/fwlink/?LinkId=137171"
        volume_uuid = info.volume.get_uuid()
        [disk] = root.findall('./ovf:DiskSection/ovf:Disk', namespaces)
        attr(disk, 'ovf:capacity', info.volume.size.bytes.get_qty_in('B'))
        attr(disk, 'ovf:format', info.volume.ovf_uri)
        attr(disk, 'vbox:uuid', volume_uuid)

        [system] = root.findall('./ovf:VirtualSystem', namespaces)
        attr(system, 'ovf:id', info._vagrant['box_name'])

        # Set the operating system
        [os_section] = system.findall('./ovf:OperatingSystemSection', namespaces)
        os_info = {'i386': {'id': 96, 'name': 'Debian'},
                   'amd64': {'id': 96, 'name': 'Debian_64'}
                   }.get(info.manifest.system['architecture'])
        attr(os_section, 'ovf:id', os_info['id'])
        [os_desc] = os_section.findall('./ovf:Description', namespaces)
        os_desc.text = os_info['name']
        [os_type] = os_section.findall('./vbox:OSType', namespaces)
        os_type.text = os_info['name']

        [sysid] = system.findall('./ovf:VirtualHardwareSection/ovf:System/'
                                 'vssd:VirtualSystemIdentifier', namespaces)
        sysid.text = info._vagrant['box_name']

        [machine] = system.findall('./vbox:Machine', namespaces)
        import uuid
        attr(machine, 'ovf:uuid', uuid.uuid4())
        attr(machine, 'ovf:name', info._vagrant['box_name'])
        from datetime import datetime
        attr(machine, 'ovf:lastStateChange', datetime.now().strftime('%Y-%m-%dT%H:%M:%SZ'))
        [nic] = machine.findall('./ovf:Hardware/ovf:Network/ovf:Adapter', namespaces)
        attr(machine, 'ovf:MACAddress', mac_address)

        [device_img] = machine.findall('./ovf:StorageControllers'
                                       '/ovf:StorageController[@name="SATA Controller"]'
                                       '/ovf:AttachedDevice/ovf:Image', namespaces)
        attr(device_img, 'uuid', '{' + str(volume_uuid) + '}')

        template.write(destination, xml_declaration=True)  # , default_namespace=namespaces['ovf']


class RemoveVagrantBoxDir(Task):
    description = 'Removing the vagrant box directory'
    phase = phases.cleaning
    successors = [workspace.DeleteWorkspace]

    @classmethod
    def run(cls, info):
        shutil.rmtree(info._vagrant['folder'])
        del info._vagrant['folder']
