from django.db import models
from django.contrib.auth import get_user_model

from uncloud_pay.models import Product, RecurringPeriod
from uncloud.models import UncloudModel, UncloudStatus

import uncloud_pay.models as pay_models
import uncloud_storage.models

class VMCluster(UncloudModel):
    name = models.CharField(max_length=128, unique=True)

class VMHost(UncloudModel):
    # 253 is the maximum DNS name length
    hostname = models.CharField(max_length=253, unique=True)

    vmcluster = models.ForeignKey(
        VMCluster, on_delete=models.CASCADE, editable=False, blank=True, null=True
    )

    # indirectly gives a maximum number of cores / VM - f.i. 32
    physical_cores = models.IntegerField(default=0)

    # determines the maximum usable cores - f.i. 320 if you overbook by a factor of 10
    usable_cores = models.IntegerField(default=0)

    # ram that can be used of the server
    usable_ram_in_gb = models.FloatField(default=0)

    status = models.CharField(
        max_length=32, choices=UncloudStatus.choices, default=UncloudStatus.PENDING
    )

    @property
    def vms(self):
        return VMProduct.objects.filter(vmhost=self)

    @property
    def used_ram_in_gb(self):
        return sum([vm.ram_in_gb for vm in VMProduct.objects.filter(vmhost=self)])

    @property
    def available_ram_in_gb(self):
        return self.usable_ram_in_gb - self.used_ram_in_gb

    @property
    def available_cores(self):
        return self.usable_cores - sum([vm.cores for vm in self.vms ])



class VMProduct(Product):
    vmhost = models.ForeignKey(
        VMHost, on_delete=models.CASCADE, editable=False, blank=True, null=True
    )

    vmcluster = models.ForeignKey(
        VMCluster, on_delete=models.CASCADE, editable=False, blank=True, null=True
    )

    name = models.CharField(max_length=32, blank=True, null=True)
    cores = models.IntegerField()
    ram_in_gb = models.FloatField()

    @property
    def recurring_price(self):
        return self.cores * 3 + self.ram_in_gb * 4

    @property
    def description(self):
        return "Virtual machine '{}': {} core(s), {}GB memory".format(
                self.name, self.cores, self.ram_in_gb)

    @staticmethod
    def allowed_recurring_periods():
        return list(filter(
            lambda pair: pair[0] in [RecurringPeriod.PER_365D,
                RecurringPeriod.PER_30D, RecurringPeriod.PER_HOUR],
            RecurringPeriod.choices))


    def __str__(self):
        return f"VM id={self.id},name={self.name},cores={self.cores},ram_in_gb={self.ram_in_gb}"


class VMWithOSProduct(VMProduct):
    primary_disk = models.ForeignKey('VMDiskProduct', on_delete=models.CASCADE, null=True)


class VMDiskImageProduct(UncloudModel):
    """
    Images are used for cloning/linking.

    They are the base for images.

    """

    owner = models.ForeignKey(
        get_user_model(), on_delete=models.CASCADE, editable=False
    )

    name = models.CharField(max_length=256)
    is_os_image = models.BooleanField(default=False)
    is_public = models.BooleanField(default=False, editable=False) # only allow admins to set this

    size_in_gb = models.FloatField(null=True, blank=True)
    import_url = models.URLField(null=True, blank=True)
    image_source = models.CharField(max_length=128, null=True)
    image_source_type = models.CharField(max_length=128, null=True)

    storage_class = models.CharField(max_length=32,
                                     choices = uncloud_storage.models.StorageClass.choices,
                                     default = uncloud_storage.models.StorageClass.SSD)

    status = models.CharField(
        max_length=32, choices=UncloudStatus.choices, default=UncloudStatus.PENDING
    )

    def __str__(self):
        return "VMDiskImage {} ({}): {} gb".format(self.id,
                                                   self.name,
                                                   self.size_in_gb)


# See https://docs.djangoproject.com/en/dev/ref/models/fields/#field-choices-enum-types
class VMDiskType(models.TextChoices):
    """
    Types of disks that can be attached to VMs
    """
    CEPH_SSD   = 'ceph/ssd'
    CEPH_HDD   = 'ceph/hdd'
    LOCAL_SSD  = 'local/ssd'
    LOCAL_HDD  = 'local/hdd'


class VMDiskProduct(Product):
    """
    The VMDiskProduct is attached to a VM.

    It is based on a VMDiskImageProduct that will be used as a basis.

    It can be enlarged, but not shrinked compared to the VMDiskImageProduct.
    """

    vm = models.ForeignKey(VMProduct, on_delete=models.CASCADE)

    image = models.ForeignKey(VMDiskImageProduct, on_delete=models.CASCADE,
                              blank=True, null=True)

    size_in_gb = models.FloatField(blank=True)

    disk_type = models.CharField(
        max_length=20,
        choices=VMDiskType.choices,
        default=VMDiskType.CEPH_SSD)

    def __str__(self):
        return f"Disk {self.size_in_gb}GB ({self.disk_type}) for {self.vm}"

    @property
    def recurring_price(self):
        if self.disk_type == VMDiskType.CEPH_SSD:
            price_per_gb = 3.5/10
        elif self.disk_type == VMDiskType.CEPH_HDD:
            price_per_gb = 1.5/100
        elif self.disk_type == VMDiskType.LOCAL_SSD:
            price_per_gb = 3.5/10
        elif self.disk_type == VMDiskType.CEPH_HDD:
            price_per_gb = 1.5/100

        return self.size_in_gb * price_per_gb


class VMNetworkCard(models.Model):
    vm = models.ForeignKey(VMProduct, on_delete=models.CASCADE)

    mac_address = models.BigIntegerField()

    ip_address = models.GenericIPAddressField(blank=True,
                                              null=True)


class VMSnapshotProduct(Product):
    gb_ssd = models.FloatField(editable=False)
    gb_hdd = models.FloatField(editable=False)

    vm = models.ForeignKey(VMProduct,
                           related_name='snapshots',
                           on_delete=models.CASCADE)