# coding: utf-8
"""Backend management system classes

Used to communicate with providers without using CFME facilities
"""
from __future__ import absolute_import
try:
    # In Fedora 22, we see SSL errors when connecting to vSphere, this prevents the error.
    import ssl
    ssl._create_default_https_context = ssl._create_unverified_context
except AttributeError:
    pass

import operator
import re
import time
from datetime import datetime
from distutils.version import LooseVersion
from functools import partial

import six
import pytz
from wait_for import wait_for, TimedOutError
from pyVmomi import vim, vmodl
from pyVim.connect import SmartConnect, Disconnect

from .base import WrapanapiAPIBaseVM, VMInfo
from .exceptions import (VMInstanceNotCloned, VMInstanceNotSuspended, VMNotFoundViaIP,
    HostNotRemoved, VMInstanceNotFound, VMCreationDateError)


SELECTION_SPECS = [
    'resource_pool_traversal_spec',
    'resource_pool_vm_traversal_spec',
    'folder_traversal_spec',
    'datacenter_host_traversal_spec',
    'datacenter_vm_traversal_spec',
    'compute_resource_rp_traversal_spec',
    'compute_resource_host_traversal_spec',
    'host_vm_traversal_spec',
    'datacenter_datastore_traversal_spec'
]
TRAVERSAL_SPECS = [
    {
        'name': 'resource_pool_traversal_spec',
        'type': vim.ResourcePool,
        'path': 'resourcePool',
        'select_indices': [0, 1]
    },
    {
        'name': 'resource_pool_vm_traversal_spec',
        'type': vim.ResourcePool,
        'path': 'vm',
        'select_indices': []
    },
    {
        'name': 'compute_resource_rp_traversal_spec',
        'type': vim.ComputeResource,
        'path': 'resourcePool',
        'select_indices': [0, 1]
    },
    {
        'name': 'compute_resource_host_traversal_spec',
        'type': vim.ComputeResource,
        'path': 'host',
        'select_indices': []
    },
    {
        'name': 'datacenter_host_traversal_spec',
        'type': vim.Datacenter,
        'path': 'hostFolder',
        'select_indices': [2]
    },
    {
        'name': 'datacenter_datastore_traversal_spec',
        'type': vim.Datacenter,
        'path': 'datastoreFolder',
        'select_indices': [2]
    },
    {
        'name': 'datacenter_vm_traversal_spec',
        'type': vim.Datacenter,
        'path': 'vmFolder',
        'select_indices': [2]
    },
    {
        'name': 'host_vm_traversal_spec',
        'type': vim.HostSystem,
        'path': 'vm',
        'select_indices': [2]
    },
    {
        'name': 'folder_traversal_spec',
        'type': vim.Folder,
        'path': 'childEntity',
        'select_indices': [2, 3, 4, 5, 6, 7, 1, 8]
    }
]


def get_task_error_message(task):
    """Depending on the error type, a different attribute may contain the error message. This
    function will figure out the error message.
    """
    if hasattr(task.info.error, 'message'):
        message = str(task.info.error.message)
    elif hasattr(task.info.error, 'localizedMessage'):
        message = str(task.info.error.localizedMessage)
    elif hasattr(task.info.error, 'msg'):
        message = str(task.info.error.msg)
    else:
        message = 'Unknown error type: {}'.format(task.info.error)
    return message


class VMWareSystem(WrapanapiAPIBaseVM):
    """Client to Vsphere API

    Args:
        hostname: The hostname of the system.
        username: The username to connect with.
        password: The password to connect with.

    See also:

        vSphere Management SDK API docs
        https://developercenter.vmware.com/web/dp/doc/preview?id=155

    """
    _api = None

    _stats_available = {
        'num_vm': lambda self: len(self.list_vm()),
        'num_host': lambda self: len(self.list_host()),
        'num_cluster': lambda self: len(self.list_cluster()),
        'num_template': lambda self: len(self.list_template()),
        'num_datastore': lambda self: len(self.list_datastore()),
    }
    POWERED_ON = 'poweredOn'
    POWERED_OFF = 'poweredOff'
    SUSPENDED = 'suspended'

    def __init__(self, hostname, username, password, **kwargs):
        super(VMWareSystem, self).__init__(kwargs)
        self.hostname = hostname
        self.username = username
        self.password = password
        self._service_instance = None
        self._content = None
        self._vm_cache = {}
        self.kwargs = kwargs

    def __del__(self):
        """Disconnect from the API when the object is deleted"""
        # This isn't the best place for this, but this class doesn't know when it is no longer in
        # use, and we need to do some sort of disconnect based on the pyVmomi documentation.
        if self._service_instance:
            Disconnect(self._service_instance)

    @property
    def service_instance(self):
        """An instance of the service"""
        if not self._service_instance:
            self._service_instance = SmartConnect(host=self.hostname, user=self.username,
                                                  pwd=self.password)
        return self._service_instance

    @property
    def content(self):
        """The content node"""
        if not self._content:
            self._content = self.service_instance.RetrieveContent()
        return self._content

    @property
    def version(self):
        """The product version"""
        return LooseVersion(self.content.about.version)

    @property
    def default_resource_pool(self):
        return self.kwargs.get("default_resource_pool")

    def _get_obj_list(self, vimtype, folder=None):
        """Get a list of objects of type ``vimtype``"""
        folder = folder or self.content.rootFolder
        container = self.content.viewManager.CreateContainerView(folder, [vimtype], True)
        return container.view

    def _get_obj(self, vimtype, name, folder=None):
        """Get an object of type ``vimtype`` with name ``name`` from Vsphere"""
        obj = None
        for item in self._get_obj_list(vimtype, folder):
            if item.name == name:
                obj = item
                break
        return obj

    def _build_filter_spec(self, begin_entity, property_spec):
        """Build a search spec for full inventory traversal, adapted from psphere"""
        # Create selection specs
        selection_specs = [vmodl.query.PropertyCollector.SelectionSpec(name=ss)
                           for ss in SELECTION_SPECS]
        # Create traversal specs
        traversal_specs = []
        for spec_values in TRAVERSAL_SPECS:
            spec = vmodl.query.PropertyCollector.TraversalSpec()
            spec.name = spec_values['name']
            spec.type = spec_values['type']
            spec.path = spec_values['path']
            if spec_values.get('select_indices'):
                spec.selectSet = [selection_specs[i] for i in spec_values['select_indices']]
            traversal_specs.append(spec)
        # Create an object spec
        obj_spec = vmodl.query.PropertyCollector.ObjectSpec()
        obj_spec.obj = begin_entity
        obj_spec.selectSet = traversal_specs
        # Create a filter spec
        filter_spec = vmodl.query.PropertyCollector.FilterSpec()
        filter_spec.propSet = [property_spec]
        filter_spec.objectSet = [obj_spec]
        return filter_spec

    def _get_updated_obj(self, obj):
        """
        Build a filter spec based on ``obj`` and return the updated object.

        Args:
             obj (pyVmomi.ManagedObject): The managed object to update, will be a specific subclass
        """
        # Set up the filter specs
        property_spec = vmodl.query.PropertyCollector.PropertySpec(type=type(obj), all=True)
        object_spec = vmodl.query.PropertyCollector.ObjectSpec(obj=obj)
        filter_spec = vmodl.query.PropertyCollector.FilterSpec()