from django.db import models
from django.conf import settings
from django.contrib.auth.models import User
from django.utils import timezone
from datetime import timedelta
from image.models import Disk
from interface_openstack.implementation.vm.instance import (
    OSVirtualMachineManager
)
import logging

logger = logging.getLogger(__name__)

interface = OSVirtualMachineManager(settings.CONNECTION)

ACTIONS = {
    "start": interface.start_vm,
    "stop": interface.stop_vm,
    "suspend": interface.suspend_vm,
    "wake_up": interface.wake_up_vm,
    "reset": interface.reset_vm,
    "reboot": interface.reboot_vm,
}


class Lease(models.Model):
    """ Users can use the virtual machine until the lease dates.
        After suspend interval the vm suspends and after delete it will be
        destroyed
    """
    name = models.CharField(max_length=100)
    description = models.CharField(blank=True, max_length=100)
    suspend_interval_in_sec = models.IntegerField(default=3600)
    delete_interval_in_sec = models.IntegerField(default=7200)


class Flavor(models.Model):
    """ Flavors are reasource packages, contains all information about
        resources accociated with the virtual machine
    """
    name = models.CharField(blank=False, max_length=100)
    description = models.CharField(blank=True, max_length=200)
    remote_id = models.CharField(
        max_length=100, help_text="ID of the instance on the backend"
    )
    ram = models.PositiveIntegerField(default=0)
    vcpu = models.PositiveIntegerField(default=0)
    initial_disk = models.PositiveIntegerField(default=0)
    priority = models.PositiveIntegerField(blank=True, null=True)

    @classmethod
    def create(cls, name, description, ram=0, vcpu=0,
               initial_disk=0, priority=0):
        try:
            remote_flavor = interface.create_flavor(name, ram, vcpu, initial_disk)

            flavor = cls(name=name, description=description,
                         remote_id=remote_flavor.id, ram=ram, vcpu=vcpu,
                         initial_disk=initial_disk, priority=priority)
            flavor.save()
            return flavor
        except Exception as e:
            logger.error(str(e))
            raise ValueError("Can't create Flavor in remote cloud.")

    def delete(self):
        try:
            interface.delete_flavor(self.remote_id)
            super(Flavor, self).delete()
        except Exception as e:
            if e.OpenStackError:
                logger.error("Can not delete the flavor in remote cloud")


class BaseMachineDescriptor(models.Model):
    """Virtual machine template.
    """
    OSTypes = (
        ('LINUX', 'The system based on Linux'),
        ('WINDOWS', 'Windows based system'),
    )
    CONN_PROTOCOL = tuple(
        [(key, val[0]) for key, val in settings.VM_ACCESS_PROTOCOLS.items()]
    )

    name = models.CharField(max_length=100,
                            verbose_name="name",
                            help_text="Human readable name of template.")
    description = models.TextField(verbose_name="description",
                                   blank=True,
                                   help_text="Description of the template.")
    system_type = models.CharField(max_length=50,
                                   choices=OSTypes,
                                   verbose_name="operating_system",
                                   help_text="The base name of the operating system"
                                   )
    distro = models.CharField(max_length=100,
                              blank=True,
                              verbose_name='system distribution',
                              help_text="The specific name and version of the installed OS"
                                        "e. g. Win 7, Ubuntu 18.04"
                              )
    access_protocol = models.CharField(max_length=50,
                                       choices=CONN_PROTOCOL,
                                       verbose_name="remote_access_protocol",
                                       help_text="The protocol which used to connect to the machine"
                                                 "that created from this template"
                                       )
    created_at = models.DateTimeField(auto_now_add=True,
                                      editable=False,
                                      help_text="Date, when the template created.")
    created_by = models.ForeignKey(User,
                                   on_delete=models.DO_NOTHING,
                                   related_name="created_templates",
                                   help_text="The user, who create the template")
    flavor = models.ForeignKey(Flavor,
                               help_text="Resources given to the vm",
                               verbose_name="flavor",
                               on_delete=models.CASCADE,
                               related_name='templates')
    lease = models.ForeignKey(Lease,
                              on_delete=models.CASCADE,
                              related_name='templates')
    network_id = models.CharField(max_length=100,
                                  verbose_name="network_id",
                                  help_text="The new instance will be in this network.",
                                  null=True,
                                  blank=True)


class Instance(BaseMachineDescriptor):
    """Virtual machine instance.
    """
    from template.models import ImageTemplate

    class Meta:
        default_permissions = ()
        permissions = (
            ('create_instance', 'Can create a new VM.'),
            ('create_template_from_instance', 'Can create template from instance.'),
            ('use_instance', 'Can access the VM connection info.'),
            ('operate_instance', 'Can use basic lifecycle methods of the VM.'),
            ('administer_instance', 'Can delete VM.'),
            ('access_console', 'Can access the graphical console of a VM.'),
            ('change_resources', 'Can change resources of a VM.'),
            ('manage_access', 'Can manage access rights for the VM.'),
            ('config_ports', 'Can configure port forwards.'),
        )
    remote_id = models.CharField(
        max_length=100, help_text="ID of the instance on the backend"
    )
    password = models.CharField(
        max_length=50, help_text="Original password of the instance"
    )
    time_of_suspend = models.DateTimeField(
        blank=True,
        help_text="After this point in time, the instance will be suspended"
    )
    time_of_delete = models.DateTimeField(
        blank=True,
        help_text="After this point in time, the instance will be deleted!"
    )
    status = models.CharField(max_length=50, verbose_name="instance_status", default="NO_STATE")
    deleted = models.BooleanField(
        help_text="Indicates if the instance is ready for garbage collection",
        default=False,
    )

    template = models.ForeignKey(ImageTemplate, related_name="vm", null=True,
                                 help_text="The base image of the vm",
                                 on_delete=models.DO_NOTHING)

    disks = models.ManyToManyField(Disk, related_name="instance",
                                   blank=True,
                                   help_text="Disks attached to instance",
                                   verbose_name="disks")

    @classmethod
    def create(cls, lease, owner, flavor, template, remote_id, params):
        params["password"] = cls.generate_password()
        inst = cls(lease=lease, flavor=flavor, created_by=owner, system_type=template.system_type,
                   distro=template.distro, access_protocol=template.access_protocol,
                   remote_id=remote_id, template=template, status="CREATING",
                   **params)
        inst.full_clean()

        inst.save()

        logger.info("New instance created")
        return inst

    @classmethod
    def create_instance_from_template(cls, params, template, owner, lease,
                                      networks, flavor):
        try:
            remote_id = interface.create_vm_from_template(params["name"],
                                                          template.image.remote_id,
                                                          flavor.remote_id,
                                                          networks,
                                                          )
            new_inst = cls.create(lease, owner, flavor, template,
                                  remote_id, params)
            return new_inst
        except Exception as e:
            logger.error(str(e))
            raise ValueError("Can't create Instance in remote cloud."
                             "Search the logs for more detail.")

    def clean(self, *args, **kwargs):
        self.time_of_suspend, self.time_of_delete = self.get_renew_times()
        super(Instance, self).clean(*args, **kwargs)

    def get_renew_times(self, lease=None):
        """Returns new suspend and delete times if renew would be called.
        """
        if lease is None:
            lease = self.lease
        return (
            timezone.now() + timedelta(
                seconds=lease.suspend_interval_in_sec),
            timezone.now() + timedelta(
                seconds=lease.delete_interval_in_sec)
        )

    def renew(self, lease=None):
        """Renew virtual machine, if a new lease is provided it changes it as well.
        """
        if lease is None:
            lease = self.lease
        else:
            self.lease = lease
        self.time_of_suspend, self.time_of_delete = self.get_renew_times(lease)
        self.save()

    def delete(self):
        try:
            interface.destroy_vm(self.remote_id)
            super(Instance, self).delete()
        except Exception as e:
            if e.OpenStackError:
                logger.error("Can not delete the instance in remote cloud")

    def execute_common_action(self, action):
        if ACTIONS[action]:
            return ACTIONS[action](self.remote_id)
        else:
            raise ValueError("This action is not supported!")

    def get_remote_instance(self):
        return interface.get_vm(self.remote_id)

    def update_status(self):
        remote = self.get_remote_instance()
        self.status = remote.status
        self.save()

    @classmethod
    def generate_password(self):
        return User.objects.make_random_password(
            allowed_chars='abcdefghijklmnopqrstuvwx'
            'ABCDEFGHIJKLMNOPQRSTUVWX123456789')

    def reset_password(self):
        self.password = self.generate_password()
        self.save()

    def change_name(self, new_name):
        self.name = new_name
        self.save()

    def change_description(self, new_description):
        self.description = new_description
        self.save()

    def destroy(self):
        self.deleted = True
        self.save()