# Copyright 2020 VMware, Inc.
# All rights reserved. -- VMware Confidential
"""
This module add new resource to the appliance during B2B if required
"""
import logging
import os
import sys

sys.path.append(os.environ['VMWARE_PYTHON_PATH'])
sys.path.append(os.path.dirname(__file__))
import utils
from patch_specs import DiscoveryResult, ValidationResult, \
    Question, Mismatch, Requirements, PatchInfo, RequirementsResult, PatchContext
from extensions import extend, Hook
from l10n import msgMetadata as _T, localizedString as _
from patch_errors import UserError
from add_disk import add_disk
from add_memory import add_memory
from extend_disk import resize_root_disk
from check_disk_availability import disk_addition_precheck
from vcsa_utils import isDisruptiveUpgrade

logger = logging.getLogger(__name__)

@extend(Hook.Discovery)
def discover(ctx):  # pylint: disable=W0613
    '''DiscoveryResult discover(PatchContext sharedCtx)
    throw UserUpgradeError'''

    patchable = isDisruptiveUpgrade(ctx) and \
                utils.is_hardware_addition_needed()
    return DiscoveryResult(componentId="hardware-patching-component",
                           displayName=_(_T("hardware-component.displayName",
                                            "VMware vCenter Server Patcher")),
                           patchable=patchable)


@extend(Hook.Requirements)
def collectRequirements(ctx):  # pylint: disable=W0613
    '''
    Collects the requirements needed for patching of hardware component
    :param ctx: PatchPhaseContext
    :return RequirementsResult: Object of RequirementsResult with
    requirements needed.
    '''
    mismatches = []
    requirements = Requirements()
    patchInfo = PatchInfo()
    if not utils.get_local_vm():
        managingVCIPQuestion = Question(
            userDataId=utils.MANAGING_VC_ADDRESS,
            text=_(_T("managing.vc.address.text",
                      "IP address or hostname of the vCenter Server where the "
                      "vCenter Server Appliance resides.")),
            description=_(_T("managing.vc.address.desc",
                             "IP address or hostname of the vCenter Server"
                             "where the vCenter Server Appliance resides.")),
            kind=Question.PLAIN_KIND)

        managingVCSSOUserQuestion = Question(
            userDataId=utils.MANAGING_VC_SSO_USER,
            text=_(_T("managing.vc.sso.user.text",
                      "vCenter Single Sign-On user id for the vCenter Server "
                      "where the vCenter Server Appliance resides.")),
            description=_(_T("managing.vc.sso.user.desc",
                             "vCenter Single Sign-On user for the vCenter "
                             "Server where the vCenter Server Appliance "
                             "resides. vCenter Server uses this vCenter Single "
                             "Sign-On User to connect to the managing vCenter "
                             "Server and add the additional hardware "
                             "resources")),
            kind=Question.PLAIN_KIND)

        managingVCSSOPasswordQuestion = Question(
            userDataId=utils.MANAGING_VC_SSO_PASSWORD,
            text=_(_T("managing.vc.sso.password.text",
                      "vCenter Single Sign-On password for the vCenter Server"
                      " where the vCenter Server Appliance resides.")),
            description=_(_T("managing.vc.sso.password.desc",
                             "vCenter Single Sign-On password for the vCenter "
                             "Server where the vCenter Server Appliance resides"
                             ".vCenter Server uses this vCenter Single Sign-On"
                             "password to connect to the managing vCenter "
                             "Server and add the additional hardware "
                             "resources.")),
            kind=Question.PASSWORD_KIND)

        managingVCSSOPortQuestion = Question(
            userDataId=utils.MANAGING_VC_SSO_PORT,
            text=_(_T("managing.vc.sso.port.text",
                      "vCenter Single Sign-On port for the vCenter Server where"
                      " the vCenter Server Appliance resides.")),
            description=_(_T("managing.vc.sso.port.desc",
                             "vCenter Single Sign-On port for the vCenter "
                             "Server where the vCenter Server Appliance "
                             "resides. vCenter Server uses this port to connect"
                             "to the managing vCenter Server and add "
                             "additional hardware resources.")),
            kind=Question.PLAIN_KIND)

        requirements = Requirements(questions=[managingVCIPQuestion,
                                               managingVCSSOPortQuestion,
                                               managingVCSSOUserQuestion,
                                               managingVCSSOPasswordQuestion
                                               ],
                                    rebootRequired=False)

    return RequirementsResult(requirements, patchInfo, mismatches)


@extend(Hook.Validation)
def validate(ctx):
    '''
    Validates hardware component required question answers provided by the user
    :param ctx: PatchPhaseContext
    :return mismatches: list of validation mismatches
    '''
    mismatches = []
    vm_obj = utils.get_local_vm()
    if not vm_obj:
        if ctx.userData.get(utils.MANAGING_VC_ADDRESS) is None:
            mismatches.append(Mismatch(
                text=_(_T("managing.vc.ip.error.text",
                          "The vCenter Single Sign-on user id for the managing"
                          " vCenter Server is missing.")),
                description=_(_T("managing.vc.ip.error.description",
                                 "You need the vCenter Single Sign-On user id "
                                 "to add hardware resources.")),
                resolution=_(_T("managing.vc.ip.error.resolution",
                                "Please provide a valid vCenter Single Sign-On"
                                " user id for the managing vCenter Server.")),
                severity=Mismatch.ERROR,
                relatedUserDataId=utils.MANAGING_VC_ADDRESS))

        if ctx.userData.get(utils.MANAGING_VC_SSO_USER) is None:
            mismatches.append(
                Mismatch(
                    text=_(_T("managing.vc.sso.user.error.text",
                              "The vCenter Single Sign-on password for the "
                              "managing vCenter Server is missing.")),
                    description=_(_T("managing.vc.sso.user.error.description",
                                     "You need the vCenter Single Sign-On password"
                                     " to add hardware resources.")),
                    resolution=_(_T("managing.vc.sso.password.user.resolution",
                                    "Please provide a valid vCenter Single Sign-On"
                                    " password for the managing vCenter Server.")),
                    severity=Mismatch.ERROR,
                    relatedUserDataId=utils.MANAGING_VC_SSO_USER))

        if ctx.userData.get(utils.MANAGING_VC_SSO_PASSWORD) is None:
            mismatches.append(
                Mismatch(
                    text=_(_T("managing.vc.sso.password.error.text",
                              "The vCenter Single Sign-on port for the managing "
                              "vCenter Server is missing.")),
                    description=_(
                        _T("managing.vc.sso.password.error.description",
                           "You need the vCenter Single Sign-On port"
                           " to add hardware resources.")),
                    resolution=_(_T("managing.vc.sso.password.error.resolution",
                                    "Please provide a valid vCenter Single "
                                    "Sign-On password for the managing "
                                    "vCenter Server.")),
                    severity=Mismatch.ERROR,
                    relatedUserDataId=utils.MANAGING_VC_SSO_PASSWORD))

        if ctx.userData.get(utils.MANAGING_VC_SSO_PORT) is None:
            mismatches.append(Mismatch(
                text=_(_T("managing.vc.sso.port.error.text",
                          "The vCenter Single Sign-on port for the "
                          "managing vCenter Server is missing.")),
                description=_(_T("managing.vc.sso.port.error.description",
                                 "You need the vCenter Single Sign-On port to "
                                 "add hardware resources.")),
                resolution=_(_T("managing.vc.sso.port.error.resolution",
                                "Please provide a valid vCenter Single Sign-On"
                                " port for the managing vCenter Server.")),
                severity=Mismatch.ERROR,
                relatedUserDataId=utils.MANAGING_VC_SSO_PORT))
        # Return mismatches asking user to provide required answers
        if mismatches:
            return ValidationResult(mismatches)
        try:
            vm_obj = utils.get_local_vc_vm(ctx)
        except UserError as e:
            mismatches.append(Mismatch(
                text=e.cause,
                description=e.cause,
                resolution=e.resolution,
                severity=Mismatch.ERROR))
            return ValidationResult(mismatches)
        except Exception:  # pylint: disable=W0703
            mismatches.append(Mismatch(
                text=_(_T("cannot.find.vm.error.text",
                          "Cannot find the VM on the managing vCenter Server "
                          "")),
                description=_(_T("cannot.find.vm.error.description",
                                 "Cannot find the VM on the Managing vCenter "
                                 "Server.")),
                resolution=_(_T("cannot.find.vm.error.resolution",
                                "Provide the correct Managing vCenter Server "
                                " which is Managing the vCenter VM.")),
                severity=Mismatch.ERROR))
            return ValidationResult(mismatches)
    # Below code block performs the precheck related to disk addition if disk
    # addition is needed and throw the error if the required SCSI slots are
    # not available

    if not vm_obj:
        logger.error("Failed to find the vc vm in the vc inventory.")
        cause = _(_T("Failed.to.find.vc.vm.error",
                     "Failed to find the vc vm in the given vc inventory"))
        resolution = _(_T("Failed.to.find.vc.vm.resolution",
                          "Provide the correct Details Managing vCenter Server "
                          "which is Managing the vCenter VM."))
        mismatches.append(Mismatch(text=cause, resolution=resolution,
                                   severity=Mismatch.ERROR))
        return ValidationResult(mismatches)

    hardware_difference_dict = utils.wrapper_get_hardware_difference_dict()

    if utils.need_disk_addition(hardware_difference_dict):
        try:
            no_of_disks_to_add = \
                utils.get_disk_addition_info(hardware_difference_dict)
            disk_addition_precheck(vm_obj, no_of_disks_to_add)
        except UserError as e:
            mismatches.append(Mismatch(text=e.cause,
                                       description=e.cause,
                                       resolution=e.resolution,
                                       severity=Mismatch.ERROR))

    if utils.need_root_resize(hardware_difference_dict):
        # Check for vm snapshots
        if vm_obj.snapshot:
            logger.info("The vCenter Server has snapshots. Cannot perform "
                        "root resizing of the vCenter Server Appliance.")
            mismatches.append(Mismatch(
                text=_(_T("vm.snapshots.exist.error.text",
                          "vCenter Server contains snapshots.  Cannot resize "
                          "root disk.")),
                description=_(_T("vm.snapshots.exist.error.description",
                                 "vCenter Server contains snapshots.  Cannot resize"
                                 " root disk.")),
                resolution=_(_T("vm.snapshots.exist.error.resolution",
                                "Delete all snapshots from vCenter Server virtual "
                                "machine. To take a backup, perform a "
                                "file-based backup.")),
                severity=Mismatch.ERROR))
        else:
            logger.info("The vCenter server has no snapshots. Proceeding "
                        "with root resize operation.")

    return ValidationResult(mismatches)


def _adjust_disk(vm_obj, layout_disk_name, layout_disk_size_in_gb):
    """
    This method assumes if the disk_name  is 'disk-root' then its root resize.
    If not check if disk_name starts with 'disk' then assume disk addition
    is required, call appropriate methods to do the operation.

    NOTE: This logic needs ot be revisited. If value for 'disk-*' changes
    in target layout.json it will be assumed to be a disk addition.
    :param vm_obj: VM object
    :param layout_disk_name: disk name from layout.json to be adjusted
    :param layout_disk_size_in_gb: new size of disk in GBs
    :return:
    """

    if layout_disk_name == "disk-root":
        new_root_size = layout_disk_size_in_gb.strip("GB")
        new_root_size_in_kb = int(new_root_size) * 1024 * 1024
        resize_root_disk(vm_obj, new_root_size_in_kb)
    else:
        disk_name = layout_disk_name[layout_disk_name.index('-') + 1:]
        add_disk(vm_obj, int(layout_disk_size_in_gb.strip("GB")), disk_name)


@extend(Hook.SystemPrepare)
def prepare(ctx):
    '''
    This hook adds the hardware needed based on the hardware_difference_dict
    generated
    :param ctx:PatchPhaseContext
    :raise UserError: error occurred during hardware modification of the
    local vc vm
    '''
    try:
        vm_obj = utils.get_local_vm() or utils.get_local_vc_vm(ctx)
        if vm_obj is None:
            cause = _(_T("get.vc.vm.error.txt",
                         "Could not find the vCenter VM."))
            resolution = _(_T("get.vc.vm.error.resolution",
                              "Check the vCenter VM is available in the "
                              "inventory or not"))
            raise UserError(cause=cause, resolution=resolution)

        hardware_difference_dict = utils.wrapper_get_hardware_difference_dict()

        for key in hardware_difference_dict:
            if key.startswith("disk"):
                _adjust_disk(vm_obj, key, hardware_difference_dict[key])
            elif key.startswith("cpu"):
                pass
                # add_cpu(hardware_difference_dict[key])
            elif key.startswith("memory"):
                add_memory(vm_obj, hardware_difference_dict[key])

    except Exception as e:
        cause = _(_T("add.hardware.resource.failed.txt",
                     "Adding hardware resource failed due to %s"), e)
        raise UserError(cause=cause)
