import ipaddress

from rest_framework import serializers

from oca import OpenNebulaException

from .models import OpenNebulaManager


class VirtualMachineTemplateSerializer(serializers.Serializer):
    """Serializer to map the virtual machine template instance into JSON format."""
    id = serializers.IntegerField(read_only=True)
    name = serializers.SerializerMethodField()
    cores = serializers.SerializerMethodField()
    disk_size = serializers.SerializerMethodField()
    memory = serializers.SerializerMethodField()

    def get_cores(self, obj):
        if hasattr(obj.template, 'vcpu'):
            return obj.template.vcpu

        return ''

    def get_disk_size(self, obj):
        template = obj.template
        disk_size = 0
        try:
            for disk in template.disks:
                disk_size += int(disk.size)
            return disk_size / 1024
        except:
            return 0

    def get_memory(self, obj):
        return int(obj.template.memory)/1024

    def get_name(self, obj):
        return obj.name.strip('public-')


class VirtualMachineSerializer(serializers.Serializer):
    """Serializer to map the virtual machine instance into JSON format."""

    name = serializers.SerializerMethodField()
    cores = serializers.IntegerField(source='template.vcpu')
    disk = serializers.IntegerField(write_only=True)
    set_memory = serializers.IntegerField(write_only=True, label='Memory')
    memory = serializers.SerializerMethodField()

    disk_size = serializers.SerializerMethodField()
    ipv4 = serializers.SerializerMethodField()
    ipv6 = serializers.SerializerMethodField()
    vm_id = serializers.IntegerField(read_only=True, source='id')
    state = serializers.CharField(read_only=True, source='str_state')
    price = serializers.SerializerMethodField()
    ssh_key = serializers.CharField(write_only=True)
    configuration = serializers.SerializerMethodField()

    template_id = serializers.ChoiceField(
                choices=[(key.id, key.name) for key in
                         OpenNebulaManager().try_get_templates()
                         ],
                source='template.template_id',
                write_only=True,
                default=[]
            )

    def create(self, validated_data):
        owner = validated_data['owner']
        ssh_key = validated_data['ssh_key']
        cores = validated_data['template']['vcpu']
        memory = validated_data['set_memory']
        disk = validated_data['disk']

        template_id = validated_data['template']['template_id']
        specs = {
                    'cpu': cores,
                    'disk_size': disk,
                    'memory': memory,
                }

        try:
            manager = OpenNebulaManager(email=owner.email,
                                        password=owner.password,
                                        )
            opennebula_id = manager.create_vm(template_id=template_id,
                                              ssh_key=ssh_key,
                                              specs=specs)
        except OpenNebulaException as err:
            raise serializers.ValidationError("OpenNebulaException occured. {0}".format(err))

        return manager.get_vm(opennebula_id)

    def get_memory(self, obj):
        return int(obj.template.memory)/1024

    def get_disk_size(self, obj):
        template = obj.template
        disk_size = 0
        for disk in template.disks:
            disk_size += int(disk.size)
        return disk_size / 1024

    def get_price(self, obj):
        template = obj.template
        price = float(template.vcpu) * 5.0
        price += (int(template.memory)/1024 * 2.0)
        for disk in template.disks:
            price += int(disk.size)/1024 * 0.6
        return price

    def get_configuration(self, obj):
        template_id = obj.template.template_id
        template = OpenNebulaManager().get_template(template_id)
        return template.name.strip('public-')

    def get_ipv4(self, obj):
        nic = obj.template.nics[0]
        if 'vm-ipv6-nat64-ipv4' in nic.network and is_in_v4_range(nic.mac):
            return str(v4_from_mac(nic.mac))
        else:
            return '-'

    def get_ipv6(self, obj):
        nic = obj.template.nics[0]
        return nic.ip6_global

    def get_name(self, obj):
        return obj.name.strip('public-')


def hexstr2int(string):
    return int(string.replace(':', ''), 16)


FIRST_MAC = hexstr2int('02:00:b3:39:79:4d')
FIRST_V4 = ipaddress.ip_address('185.203.112.2')
COUNT = 1000


def v4_from_mac(mac):
    """Calculates the IPv4 address from a MAC address.

    mac: string (the colon-separated representation)
    returns: ipaddress.ip_address object with the v4 address
    """
    return FIRST_V4 + (hexstr2int(mac) - FIRST_MAC)


def is_in_v4_range(mac):
    return FIRST_MAC <= hexstr2int(mac) < FIRST_MAC + 1000