#! /usr/bin/env python3
# Copyright 2020-2022 VMware, Inc.  All rights reserved. -- VMware Confidential
# Description:  Provides class VcConnection, and also modifies SSL behavior
# in our python session so that it can tolerate self-signed SSL certs
# Disabled: True
#
#  vc_connection.py
#

from vcf_utils.vcf_creds import VcfCreds
from vcf_utils.tasks import wait_for_tasks

from pyVim import connect
from pyVmomi import vim

import atexit
import logging
import pprint
import sys

# following imports for SSL fix hack (see below)
# import requests # BUGBUG - is this safe to disable??
import ssl


VCENTER_PORT = 443


# ===  SSL fix hack (https://stackoverflow.com/a/39779918/3274214 & others)

# requests.packages.urllib3.disable_warnings()  # is this safe to disable?

try:
    _create_unverified_https_context = ssl._create_unverified_context
except AttributeError:
    # Legacy Python that doesn't verify HTTPS certificates by default
    pass
else:
    # Handle target environment that doesn't support HTTPS verification
    ssl._create_default_https_context = _create_unverified_https_context

# ===  end of SSL fix hack


logger = logging.getLogger(__name__)


class VcConnection(object):
    """
    Represents a single connect session to an IS vCenter which is described
    in the data provided by VcfAuthCache.

    NB: In simple IS deployments there is only one vCenter, but in larger
    ones there may be multiple vCenters with potentially multiple
    workloads / DVSes per vCenter.

    When successfully connected to our current target vCenter, we have a
    serviceInstance describing what the vCenter contains. Because a lot of the
    returned vCenter values are "managed objects", it is not safe to assume
    their lifetime may exceed that of our connect session. Though at present
    we only explicitly terminate the session at program exit, it is worth
    being aware of this.

    We provide methods to return various elements from the vCenter.

    Attributes
    ----------
    vcfAuthCache : VcfAuthCache
        Reference to caller-supplied image of VCF's auth info
        (lookup-passwords output).
    vcfAuthLookup : VcfAuthLookup
        Helper for finding entries in vcfAuthCache.
    vCenterInfo : VcfAuthInfo
        Describes the vCenter we are connected to.
    serviceInstance : vim.ServiceInstance
        Managed object providing access to all (reachable) vCenter config info.
    """

    def __init__(self, vCenterAddr, vCenterUser, vCenterPassword):
        """
        Construct an instance of VcConnection which is already connected to
        the the specified vCenter.

        Parameters
        ----------
        """

        self.vCenterAddr = vCenterAddr
        self.vCenterUser = vCenterUser
        self.vCenterPassword = vCenterPassword

        self.serviceInstance = None
        try:
            self.serviceInstance = connect.SmartConnect(host=vCenterAddr,
                                                        user=vCenterUser,
                                                        pwd=vCenterPassword,
                                                        port=VCENTER_PORT)
            atexit.register(connect.Disconnect, self.serviceInstance)
        except IOError:
            pass

        if not self.serviceInstance:
            raise Exception("Unable to connect to vCenter %s" % vCenterAddr)

        self.serviceContent = self.serviceInstance.RetrieveServiceContent()

    def disconnect(self):
        """
        Disconnect our current VC connection.
        """
        if self.serviceInstance:
            connect.Disconnect(self.serviceInstance)
            self.serviceInstance = None
            atexit.unregister(connect.Disconnect)

    def get_portgroups(self):
        """
        Return an array of portgroups known to our vCenter.

        Returns
        -------
        An iterable collection of portgroups known to our vCenter.
        These are of vmOMI type vim.dvs.DistributedVirtualPortgroup.
        """

        portgroups = self.get_vc_objects([vim.dvs.DistributedVirtualPortgroup])

        return portgroups

    def get_distributed_virtual_switch_by_name(self, name):
        """
        Return an array containing a VDS object

        Returns
        -------
        An array containing the VDS in vCenter which has name
        equivalent to input name. These are of vmOMI type
        vim.DistributedVirtualSwitch.
        """

        vds = self.get_vc_objects([vim.DistributedVirtualSwitch], name=name)

        return vds

    def get_resource_pools(self):
        """
        Return an array of resource pools known to our vCenter.

        Returns
        -------
        An iterable collection of resource pools known to our vCenter.
        These are of vmOMI type vim.ResourcePool.
        """

        portgroups = self.get_vc_objects([vim.ResourcePool])

        return portgroups

    def get_vc_objects(self, vimType, name=None, root=None):
        """
        Return a view of vCenter objects of some type. The objects
        are returned as an iterable collection.

        If the optional name parameter is supplied, then only the matching
        object (in both name and type) is returned.

        Based on pyvmomi community sample list_dc_datastore_info.py.

        Parameters
        ----------
        vimType Vector of pyvmomi VIM types which should appear in the view.
                Any type not listed here is excluded.
        name Optional name string to further refine the returned result.
                If this value is given, we return only the first object
                which matches both this and the vimType value. Otherwise
                we return all objects which match vimType.
        root Root folder to start searching from. If not given, use
                connection's rootFolder.
        """
        obj = None
        if not root:
            root = self.serviceContent.rootFolder
        container = self.serviceContent.viewManager.CreateContainerView(
            root, vimType, True)
        view = container.view
        container.Destroy()     # no need for live-update overhead

        if name:
            for c in view:
                if c.name == name:
                    obj = c
                    break
            return [obj]
        else:
            return view

    def delete_object(self, deletable_object, dryRun=True, itemTypeName=None):
        """Given some VC object which has an inherited Delete_Task,,
        initiate deletion of the object and wait for the deletion
        task to complete.
        """

        if itemTypeName:
            typeName = itemTypeName
        else:
            typeName = type(deletable_object)

        if 'Destroy_Task' not in dir(deletable_object):
            raise Exception("Object type %s cannot be deleted here" %
                            type(deletable_object))

        if dryRun:
            logger.warning("Would delete %s %s" %
                           (typeName, deletable_object.name))
        else:
            logger.warning("Deleting %s %s" %
                           (typeName, deletable_object.name))
            task = deletable_object.Destroy_Task()
            wait_for_tasks(self.serviceInstance, [task])
            logger.warning("Deletion completed.")

        return

    def getVcObjFromMoref(self, instanceMoref, expectedType):
        """
        Relies on trickery from
        http://www.errr-online.com/index.php/2014/10/12/using-the-managed-object-id-to-create-objects-with-pyvmomi/
        (have to use the wayback machine to read this now).

        The above pointer is found here:
        https://github.com/vmware/pyvmomi/issues/183#issuecomment-61742824
        """

        logger.debug(f"type(instanceMoref): {type(instanceMoref)}")

        pp = pprint.PrettyPrinter()
        logger.debug(f"instanceMoref: {pp.pformat(instanceMoref)}")
        logger.debug(f"dir(instanceMoref): {pp.pformat(dir(instanceMoref))}")

        instanceMorefStr = str(instanceMoref).strip("'")
        morefParts = instanceMorefStr.split(':')

        morefType = morefParts[0]
        morefId = morefParts[1]

        """
        Could also obtain class from morefType, using a full version of
          pvm = importlib.import_module("pyVmomi")
          _vim = getattr(pvm, "vim")
          _rp = getattr(_vim, "ResourcePool")

        (splitting on '.' and chaining as many getattr() calls as needed)
        But giving a json "config info" cache file this much control is
        more than a little scary, so we don't and instead rely on caller-
        provided expectedType class reference.
        """

        expectedTypeName = expectedType.__name__

        if expectedTypeName != morefType:
            return None

        # all seems to line up, so make up a usable reference to the instance
        inst = expectedType(morefId)
        inst._stub = self.serviceInstance._stub

        return inst

    def getVmByIp(self, ipAddr):
        """
        Find VM which is using the given IP address.

        We return a live vim.VirtualMachine moref. If there is no match,
        we return None.
        """

        match = None

        searchIndex = self.serviceContent.searchIndex

        pp = pprint.PrettyPrinter()

        try:
            matches = searchIndex.FindAllByIp(datacenter=None,
                                              ip=ipAddr, vmSearch=True)
            logger.debug(f"FindAllByIP() matches: {pp.pformat(matches)}")
            match = matches[0]
            logger.debug(f"FindAllByIP() matches[0]: {pp.pformat(match)}")

        except Exception as e:
            # not fatal, so confine to debug log to avoid alarming user
            logger.debug(f"Lookup of Edge VM by IP {ipAddr} failed: {e}")

        return match


def test_vc_connection(*args):
    """
    Helper to do some basic sanity-testing of class VcConnection.
    """
    import argparse
    import os

    print("Testing class VcConnection..")

    # be sure to set these env vars before running
    default_user = os.getenv("SDDC_SSO_USERNAME")
    default_pass = os.getenv("SDDC_SSO_PASSWORD")
    default_mgr_addr = os.getenv("SDDC_MANAGER_ADDR", default="10.0.0.4")

    parser = argparse.ArgumentParser()
    parser.add_argument(
        '--workload', '-w', required=False,
        help='Name of VCF workload in which we resize edge cluster(s).')
    parser.add_argument(
        '--user', '-u', required=False, default=default_user,
        help='Name of single-signon admin user to authenticate as.')
    parser.add_argument(
        '--password', '-p', required=False, default=default_pass,
        help='Password for specified user.')

    args = parser.parse_args()

    creds = VcfCreds(args.user, args.password, workload=args.workload,
                     sddcMgrAddr=default_mgr_addr)

    # def __init__(self, vCenterAddr, vCenterUser, vCenterPassword):

    inst = VcConnection(creds.vcCred.ipAddr,
                        creds.vcCred.username,
                        creds.vcCred.password)

    about = inst.serviceContent.about
    print("About. IP %s, Name: %s" % (inst.vCenterAddr, about.name))
    print("       Full name: %s" % about.fullName)
    print("       Vendor: %s, Version: %s, Build: %s" % (about.vendor,
                                                         about.version,
                                                         about.build))
    print("       osType: %s" % about.osType)
    print("       API type: %s, API version: %s" % (about.apiType,
                                                    about.apiVersion))

    dcs = inst.get_vc_objects([vim.Datacenter])
    print("%s datacenters found." % len(dcs))
    if dcs:
        for dc in dcs:
            print("    %s" % dc.name)
            portgroups = inst.get_portgroups()
            print("    %s portgroups found:" % len(portgroups))

            for pg in portgroups:
                print("      %s" % pg.name)

                # See
                #   https://github.com/vmware/pyvmomi-tools/blob/master/pyvmomi_tools/extensions/managed_object.py  # noqa
                # complete with comment from Shawn Hartsock saying "don't"
                print("        vim.dvs.DistributedVirtualPortgroup's _moId"
                      " = '%s'" % pg._moId)


if __name__ == '__main__':
    test_vc_connection(*sys.argv[1:])
