# Copyright 2019-2020 VMware, Inc.
# All rights reserved. -- VMware Confidential
"""
Script to add a Hard disk to an existing VM
"""

import json
import ssl
import logging
import traceback
import utils
from pyVmomi import vim  #pylint: disable=E0401,E0611
from pyVim.task import WaitForTask  #pylint: disable=E0401
from l10n import msgMetadata as _T, localizedString as _
from patch_errors import UserError
from configure_added_disk import configure_added_disk

ssl.match_hostname = lambda cert, hostname: True
logger = logging.getLogger(__name__)

MAX_SUPPORTED_DEVICE_COUNT = 15
FILE_OPERATION = 'create'
DISK_MODE = 'persistent'


def get_device_unit_number(vm_obj, controller_key):
    """
    This method gets the unit number which will be associated with the disk
    while creating the device spec
    :param vm_obj: VM Object
    :param controller_key: Controller key which identifies the SCSI controller
    :return: unit_number of the disk which will be associated with the disk
    """
    try:
        #Get all the unit numbers associated with the scsi controller in list
        # and get the largest among them and add 1 to it and if the no after
        # addition is 7 then return 8 as the unit number 7 is always associated
        # with scsi controller
        unit_number_list = []
        for dev in vm_obj.config.hardware.device:
            if isinstance(dev, vim.vm.device.VirtualDisk) and \
                    dev.controllerKey == controller_key:
                unit_number_list.append(dev.unitNumber)
        #if the scsi controller is not associated with any device then the unit
        #Number will be zero
        unit_number = max(unit_number_list) if len(unit_number_list)> 0 else -1
        unit_number = int(unit_number) +1
        if unit_number == 7:
            return 8
        return unit_number
    except Exception: #pylint: disable=W0715
        cause = _(_T('get.unit.no.error',
                     'Getting new device unit number failed due to %s.'),
                  traceback.format_exc())
        raise UserError(cause=cause)


def get_available_scsi_controller(vm_obj):
    """
    This method gets the available scsi controller to which new device will
    be added
    :param vm_obj: VM object
    :type vm_obj VM Object instance

    :return: scsi controller to which new disk will be added
    """
    try:
        controller = None
        for dev in vm_obj.config.hardware.device:
            if isinstance(dev, vim.vm.device.VirtualSCSIController):
                controller = dev
                count = MAX_SUPPORTED_DEVICE_COUNT - len(dev.device)
                if count > 0:
                    return controller

        cause = _(_T("no.scsi.slots.available.on.vm.error",
                     "No SCSI slots available for adding a new disk."))

        resolution = _(_T("no.scsi.slots.available.on.vm.description",
                          "No SCSI slots to add new disk is available"
                          "Please add new SCSI controller and retry."))
        raise UserError(cause=cause, resolution=resolution)
    except UserError as ue:
        raise ue
    except Exception:
        cause = _(_T('get.scsi.controller.error',
                     'Getting available SCSI controller failed due to %s'),
                  traceback.format_exc())
        raise UserError(cause=cause)


def create_disk_spec(unit_number, new_disk_kb, controllerKey):
    """
    This method creates Disk Spec based on the values extracted from the
    target layout.json.
    :param unit_number: new disk no in the SCSI driver
    :type  unit_number: int

    :param new_disk_kb: new disk size  to be added, value in KB
    :type  new_disk_kb: int

    :param controller: SCSI controller object on which disk is added
    :type  controller: SCSI controller object

    :return: disk spec
    :type:   dict
    """
    try:
        disk_spec = vim.vm.device.VirtualDeviceSpec()
        disk_spec.fileOperation = FILE_OPERATION
        disk_spec.operation = vim.vm.device.VirtualDeviceSpec.Operation.add
        disk_spec.device = vim.vm.device.VirtualDisk()
        disk_spec.device.backing = \
            vim.vm.device.VirtualDisk.FlatVer2BackingInfo()
        #By default disk is created with thin provision
        disk_spec.device.backing.thinProvisioned = True
        disk_spec.device.backing.diskMode = DISK_MODE
        disk_spec.device.unitNumber = unit_number
        disk_spec.device.capacityInKB = new_disk_kb
        disk_spec.device.controllerKey = controllerKey
        return disk_spec
    except Exception:
        cause = _(_T('disk.spec.creation.error',
                     'Disk specification creation failed due to %s.'),
        traceback.format_exc())
        raise UserError(cause=cause)


def check_disk_exists(disk_name):
    """
    This method checks the disk exists or not.If disks exists it return true
    else return false
    :param disk_name: name of the disk to check whether it exists or not
    :return: True if disk already exists on the appliance else False
    """
    try:
        logger.info("Checking if %s disk already exists",disk_name)
        out = utils.get_disk_info()
        out_json = json.loads(out)
        for item in out_json["blockdevices"]:
            if item["type"] == "lvm":
                if item["name"].split('-')[1] == disk_name:
                    return True
        logger.info("Disk with name %s doesnt exists", disk_name)
        return False
    except Exception:
        cause = _(_T('check.disk.exists.error',
                     'Disk existence check failed due to %s.'),
                      traceback.format_exc())
        raise UserError(cause=cause)


def add_disk(vm_obj, disk_size, disk_name):
    """
    This method adds the disk of specified size on the specified VM
    :param vm_obj: VM Obj on which Disk needs to be added
    :type vm_obj: VM Obj

    :param disk_size: Disk Size in GB
    :type disk_size: str

    :param disk_name: Name of the disk to be added
    :type disk_name: str

    :return: None
    """
    try:
        if not(isinstance(vm_obj, vim.VirtualMachine)):
            raise Exception("VM object was not found in the inventory.")

        if check_disk_exists(disk_name):
            logger.info("Disk with the Name:%s already exists, "
                        "so skipping disk addition", disk_name)
            return

        if disk_size <= 0 :
            raise Exception("Disk size is {} The disk size should be greater"
                            "than zero.".format(disk_size))

        spec = vim.vm.ConfigSpec()
        controller=get_available_scsi_controller(vm_obj)
        unit_number = get_device_unit_number(vm_obj, controller.key)
        dev_changes = []
        new_disk_kb = int(disk_size) * 1024 * 1024
        disk_spec = create_disk_spec(unit_number, new_disk_kb, controller.key)
        dev_changes.append(disk_spec)
        spec.deviceChange = dev_changes
        state = WaitForTask(vm_obj.ReconfigVM_Task(spec=spec))
        if state == "success":
            logger.info("%s GB disk added to VM: %s on controller %s with"
                        " unit_number:%s", disk_size, vm_obj.config.name,
                                             controller.key, unit_number)
        else:
            cause = _(_T('disk.addition.task.failed',
                         'Disk addition onto controller %s with unit number %s '
                         'failed due to %s.'), [controller.key, unit_number,
                                                state])
            raise UserError(cause=cause)

        configure_added_disk(disk_name)

    except UserError as ue:
        raise ue
    except Exception:
        cause = _(_T('disk.addition.failed',
                     'Disk addition failed due to %s.'), traceback.format_exc())
        raise UserError(cause=cause)
