import sys
from datetime import datetime

from django.core.management.base import BaseCommand
from django.utils import timezone
from django.contrib.auth import get_user_model

from opennebula.models import VM as VMModel
from uncloud_vm.models import VMHost, VMProduct, VMNetworkCard, VMDiskImageProduct, VMDiskProduct

from uncloud_pay.models import Order

import logging

log = logging.getLogger(__name__)

def convert_mac_to_int(mac_address: str):
    # Remove octet connecting characters
    mac_address = mac_address.replace(':', '')
    mac_address = mac_address.replace('.', '')
    mac_address = mac_address.replace('-', '')
    mac_address = mac_address.replace(' ', '')

    # Parse the resulting number as hexadecimal
    mac_address = int(mac_address, base=16)

    return mac_address


def get_vm_price(core, ram, ssd_size, hdd_size, n_of_ipv4, n_of_ipv6):
    total = 3 * core + 4 * ram + (3.5 * ssd_size/10.) + (1.5 * hdd_size/100.) + 8 * n_of_ipv4 + 0 * n_of_ipv6

    # TODO: Find some reason about the following magical subtraction.
    total -= 8

    return total


def create_nics(one_vm, vm_product):
    for nic in one_vm.nics:
        mac_address = convert_mac_to_int(nic.get('MAC'))
        ip_address = nic.get('IP', None) or nic.get('IP6_GLOBAL', None)

        VMNetworkCard.objects.update_or_create(
            mac_address=mac_address, vm=vm_product, defaults={'ip_address': ip_address}
        )


def sync_disk_and_image(one_vm, vm_product, disk_owner):
    """
    a) Check all opennebula disk if they are in the uncloud VM, if not add
    b) Check all uncloud disks and remove them if they are not in the opennebula VM
    """

    vmdisknum = 0

    one_disks_extra_data = []

    for disk in one_vm.disks:
        vmowner = one_vm.owner
        name = disk.get('image')
        vmdisknum += 1

        log.info("Checking disk {} for VM {}".format(name, one_vm))

        is_os_image, is_public, status = True, False, 'active'

        image_size_in_gb = disk.get('image_size_in_gb')
        disk_size_in_gb = disk.get('size_in_gb')
        storage_class = disk.get('storage_class')
        image_source = disk.get('source')
        image_source_type = disk.get('source_type')

        image, _ = VMDiskImageProduct.objects.update_or_create(
            name=name,
            defaults={
                'owner': disk_owner,
                'is_os_image': is_os_image,
                'is_public': is_public,
                'size_in_gb': image_size_in_gb,
                'storage_class': storage_class,
                'image_source': image_source,
                'image_source_type': image_source_type,
                'status': status
            }
        )

        # identify vmdisk from opennebula - primary mapping key
        extra_data = {
            'opennebula_vm': one_vm.vmid,
            'opennebula_size_in_gb': disk_size_in_gb,
            'opennebula_source': disk.get('opennebula_source'),
            'opennebula_disk_num': vmdisknum
        }
        # Save for comparing later
        one_disks_extra_data.append(extra_data)

        try:
            vm_disk = VMDiskProduct.objects.get(extra_data=extra_data)
        except VMDiskProduct.DoesNotExist:
            vm_disk = VMDiskProduct.objects.create(
                owner=vmowner,
                vm=vm_product,
                image=image,
                size_in_gb=disk_size_in_gb,
                extra_data=extra_data
            )

    # Now remove all disks that are not in above extra_data list
    for disk in VMDiskProduct.objects.filter(vm=vm_product):
        extra_data = disk.extra_data
        if not extra_data in one_disks_extra_data:
            log.info("Removing disk {} from VM {}".format(disk, vm_product))
            disk.delete()

    disks = [ disk.extra_data for disk in VMDiskProduct.objects.filter(vm=vm_product) ]
    log.info("VM {} has disks: {}".format(vm_product, disks))

class Command(BaseCommand):
    help = 'Migrate Opennebula VM to regular (uncloud) vm'

    def add_arguments(self, parser):
        parser.add_argument('--disk-owner', required=True, help="The user who owns the the opennebula disks")

    def handle(self, *args, **options):
        log.debug("{} {}".format(args, options))

        disk_owner = get_user_model().objects.get(username=options['disk_owner'])

        for one_vm in VMModel.objects.all():

            if not one_vm.last_host:
                log.warning("No VMHost for VM {} - VM might be on hold - skipping".format(one_vm.vmid))
                continue

            try:
                vmhost = VMHost.objects.get(hostname=one_vm.last_host)
            except VMHost.DoesNotExist:
                log.error("VMHost {} does not exist, aborting".format(one_vm.last_host))
                raise

            cores = one_vm.cores
            ram_in_gb = one_vm.ram_in_gb
            owner = one_vm.owner
            status = 'active'

            ssd_size = sum([ disk['size_in_gb'] for disk in one_vm.disks if disk['pool_name'] in ['ssd', 'one'] ])
            hdd_size = sum([ disk['size_in_gb'] for disk in one_vm.disks if disk['pool_name'] in ['hdd'] ])

            # List of IPv4 addresses and Global IPv6 addresses
            ipv4, ipv6 = one_vm.ips

            # TODO: Insert actual/real creation_date, starting_date, ending_date
            #       instead of pseudo one we are putting currently
            creation_date = starting_date = datetime.now(tz=timezone.utc)

            # Price calculation based on datacenterlight.ch
            one_time_price = 0
            recurring_period = 'per_month'
            recurring_price = get_vm_price(cores, ram_in_gb,
                                           ssd_size, hdd_size,
                                           len(ipv4), len(ipv6))

            try:
                vm_product = VMProduct.objects.get(extra_data__opennebula_id=one_vm.vmid)
            except VMProduct.DoesNotExist:
                order = Order.objects.create(
                    owner=owner,
                    creation_date=creation_date,
                    starting_date=starting_date
                )
                vm_product = VMProduct(
                    extra_data={ 'opennebula_id': one_vm.vmid },
                    name=one_vm.uncloud_name,
                    order=order
                )

            # we don't use update_or_create, as filtering by json AND setting json
            # at the same time does not work

            vm_product.vmhost = vmhost
            vm_product.owner = owner
            vm_product.cores = cores
            vm_product.ram_in_gb = ram_in_gb
            vm_product.status = status

            vm_product.save()

            # Create VMNetworkCards
            create_nics(one_vm, vm_product)

            # Create VMDiskImageProduct and VMDiskProduct
            sync_disk_and_image(one_vm, vm_product, disk_owner=disk_owner)