"""
    SoftLayer.vs
    ~~~~~~~~~~~~
    VS Manager/helpers

    :license: MIT, see LICENSE for more details.
"""
import datetime
import logging
import socket
import time
import warnings

from SoftLayer.decoration import retry
from SoftLayer import exceptions
from SoftLayer.managers import ordering
from SoftLayer import utils

LOGGER = logging.getLogger(__name__)

# pylint: disable=no-self-use,too-many-lines


class VSManager(utils.IdentifierMixin, object):
    """Manages SoftLayer Virtual Servers.

    See product information here: http://www.softlayer.com/virtual-servers

    Example::

           # Initialize the VSManager.
           # env variables. These can also be specified in ~/.softlayer,
           # or passed directly to SoftLayer.Client()
           # SL_USERNAME = YOUR_USERNAME
           # SL_API_KEY = YOUR_API_KEY
           import SoftLayer
           client = SoftLayer.Client()
           mgr = SoftLayer.VSManager(client)


    :param SoftLayer.API.BaseClient client: the client instance
    :param SoftLayer.managers.OrderingManager ordering_manager: an optional
                                              manager to handle ordering.
                                              If none is provided, one will be
                                              auto initialized.

    """

    def __init__(self, client, ordering_manager=None):
        self.client = client
        self.account = client['Account']
        self.guest = client['Virtual_Guest']
        self.package_svc = client['Product_Package']
        self.resolvers = [self._get_ids_from_ip, self._get_ids_from_hostname]
        if ordering_manager is None:
            self.ordering_manager = ordering.OrderingManager(client)
        else:
            self.ordering_manager = ordering_manager

    @retry(logger=LOGGER)
    def list_instances(self, hourly=True, monthly=True, tags=None, cpus=None,
                       memory=None, hostname=None, domain=None,
                       local_disk=None, datacenter=None, nic_speed=None,
                       public_ip=None, private_ip=None, transient=None, **kwargs):
        """Retrieve a list of all virtual servers on the account.

        Example::

            # Print out a list of hourly instances in the DAL05 data center.

            for vsi in mgr.list_instances(hourly=True, datacenter='dal05'):
               print vsi['fullyQualifiedDomainName'], vsi['primaryIpAddress']

            # Using a custom object-mask. Will get ONLY what is specified
            object_mask = "mask[hostname,monitoringRobot[robotStatus]]"
            for vsi in mgr.list_instances(mask=object_mask,hourly=True):
                print vsi

        :param boolean hourly: include hourly instances
        :param boolean monthly: include monthly instances
        :param list tags: filter based on list of tags
        :param integer cpus: filter based on number of CPUS
        :param integer memory: filter based on amount of memory
        :param string hostname: filter based on hostname
        :param string domain: filter based on domain
        :param string local_disk: filter based on local_disk
        :param string datacenter: filter based on datacenter
        :param integer nic_speed: filter based on network speed (in MBPS)
        :param string public_ip: filter based on public ip address
        :param string private_ip: filter based on private ip address
        :param boolean transient: filter on transient or non-transient instances
        :param dict \\*\\*kwargs: response-level options (mask, limit, etc.)
        :returns: Returns a list of dictionaries representing the matching
                  virtual servers
        """
        if 'mask' not in kwargs:
            items = [
                'id',
                'globalIdentifier',
                'hostname',
                'domain',
                'fullyQualifiedDomainName',
                'primaryBackendIpAddress',
                'primaryIpAddress',
                'lastKnownPowerState.name',
                'powerState',
                'maxCpu',
                'maxMemory',
                'datacenter',
                'activeTransaction.transactionStatus[friendlyName,name]',
                'status',
            ]
            kwargs['mask'] = "mask[%s]" % ','.join(items)

        call = 'getVirtualGuests'
        if not all([hourly, monthly]):
            if hourly:
                call = 'getHourlyVirtualGuests'
            elif monthly:
                call = 'getMonthlyVirtualGuests'

        _filter = utils.NestedDict(kwargs.get('filter') or {})
        if tags:
            _filter['virtualGuests']['tagReferences']['tag']['name'] = {
                'operation': 'in',
                'options': [{'name': 'data', 'value': tags}],
            }

        if cpus:
            _filter['virtualGuests']['maxCpu'] = utils.query_filter(cpus)

        if memory:
            _filter['virtualGuests']['maxMemory'] = utils.query_filter(memory)

        if hostname:
            _filter['virtualGuests']['hostname'] = utils.query_filter(hostname)

        if domain:
            _filter['virtualGuests']['domain'] = utils.query_filter(domain)

        if local_disk is not None:
            _filter['virtualGuests']['localDiskFlag'] = (
                utils.query_filter(bool(local_disk)))

        if datacenter:
            _filter['virtualGuests']['datacenter']['name'] = (
                utils.query_filter(datacenter))

        if nic_speed:
            _filter['virtualGuests']['networkComponents']['maxSpeed'] = (
                utils.query_filter(nic_speed))

        if public_ip:
            _filter['virtualGuests']['primaryIpAddress'] = (
                utils.query_filter(public_ip))

        if private_ip:
            _filter['virtualGuests']['primaryBackendIpAddress'] = (
                utils.query_filter(private_ip))

        if transient is not None:
            _filter['virtualGuests']['transientGuestFlag'] = (
                utils.query_filter(bool(transient))
            )

        kwargs['filter'] = _filter.to_dict()
        kwargs['iter'] = True
        return self.client.call('Account', call, **kwargs)

    @retry(logger=LOGGER)
    def get_instance(self, instance_id, **kwargs):
        """Get details about a virtual server instance.

        :param integer instance_id: the instance ID
        :returns: A dictionary containing a large amount of information about
                  the specified instance.

        Example::

            # Print out instance ID 12345.
            vsi = mgr.get_instance(12345)
            print vsi

            # Print out only FQDN and primaryIP for instance 12345
            object_mask = "mask[fullyQualifiedDomainName,primaryIpAddress]"
            vsi = mgr.get_instance(12345, mask=mask)
            print vsi

        """

        if 'mask' not in kwargs:
            kwargs['mask'] = (
                'id,'
                'globalIdentifier,'
                'fullyQualifiedDomainName,'
                'hostname,'
                'domain,'
                'createDate,'
                'modifyDate,'
                'provisionDate,'
                'notes,'
                'dedicatedAccountHostOnlyFlag,'
                'transientGuestFlag,'
                'privateNetworkOnlyFlag,'
                'primaryBackendIpAddress,'
                'primaryIpAddress,'
                '''networkComponents[id, status, speed, maxSpeed, name,
                                     macAddress, primaryIpAddress, port,
                                     primarySubnet[addressSpace],
                                     securityGroupBindings[
                                        securityGroup[id, name]]],'''
                'lastKnownPowerState.name,'
                'powerState,'
                'status,'
                'maxCpu,'
                'maxMemory,'
                'datacenter,'
                'activeTransaction[id, transactionStatus[friendlyName,name]],'
                'lastTransaction[transactionStatus],'
                'lastOperatingSystemReload.id,'
                'blockDevices,'
                'blockDeviceTemplateGroup[id, name, globalIdentifier],'
                'postInstallScriptUri,'
                '''operatingSystem[passwords[username,password],
                                   softwareLicense.softwareDescription[
                                       manufacturer,name,version,
                                       referenceCode]],'''
                '''softwareComponents[
                    passwords[username,password,notes],
                    softwareLicense[softwareDescription[
                                        manufacturer,name,version,
                                        referenceCode]]],'''
                'hourlyBillingFlag,'
                'userData,'
                '''billingItem[id,nextInvoiceTotalRecurringAmount,
                               package[id,keyName],
                               children[categoryCode,nextInvoiceTotalRecurringAmount],
                               orderItem[id,
                                         order.userRecord[username],
                                         preset.keyName]],'''
                'tagReferences[id,tag[name,id]],'
                'networkVlans[id,vlanNumber,networkSpace],'
                'dedicatedHost.id,'
                'placementGroupId'
            )

        return self.guest.getObject(id=instance_id, **kwargs)

    @retry(logger=LOGGER)
    def get_create_options(self):
        """Retrieves the available options for creating a VS.

        :returns: A dictionary of creation options.

        Example::

            # Prints out the create option dictionary
            options = mgr.get_create_options()
            print(options)
        """
        return self.guest.getCreateObjectOptions()

    def cancel_instance(self, instance_id):
        """Cancel an instance immediately, deleting all its data.

        :param integer instance_id: the instance ID to cancel

        Example::

            # Cancels instance 12345
            mgr.cancel_instance(12345)

        """
        return self.guest.deleteObject(id=instance_id)

    def reload_instance(self, instance_id,
                        post_uri=None,
                        ssh_keys=None,
                        image_id=None):
        """Perform an OS reload of an instance.

        :param integer instance_id: the instance ID to reload
        :param string post_url: The URI of the post-install script to run
                                after reload
        :param list ssh_keys: The SSH keys to add to the root user
        :param int image_id: The GUID of the image to load onto the server

        .. warning::
            This will reformat the primary drive.
            Post-provision script MUST be HTTPS for it to be executed.

        Example::

           # Reload instance ID 12345 then run a custom post-provision script.
           # Post-provision script MUST be HTTPS for it to be executed.
           post_uri = 'https://somehost.com/bootstrap.sh'
           vsi = mgr.reload_instance(12345, post_uri=post_url)

        """
        config = {}

        if post_uri:
            config['customProvisionScriptUri'] = post_uri

        if ssh_keys:
            config['sshKeyIds'] = [key_id for key_id in ssh_keys]

        if image_id:
            config['imageTemplateId'] = image_id

        return self.client.call('Virtual_Guest', 'reloadOperatingSystem',
                                'FORCE', config, id=instance_id)

    def _generate_create_dict(
            self, cpus=None, memory=None, hourly=True,
            hostname=None, domain=None, local_disk=True,
            datacenter=None, os_code=None, image_id=None,
            dedicated=False, public_vlan=None, private_vlan=None,
            private_subnet=None, public_subnet=None,
            userdata=None, nic_speed=None, disks=None, post_uri=None,
            private=False, ssh_keys=None, public_security_groups=None,
            private_security_groups=None, boot_mode=None, transient=False, **kwargs):
        """Returns a dict appropriate to pass into Virtual_Guest::createObject

            See :func:`create_instance` for a list of available options.
        """
        required = [hostname, domain]

        flavor = kwargs.get('flavor', None)
        host_id = kwargs.get('host_id', None)

        mutually_exclusive = [
            {'os_code': os_code, 'image_id': image_id},
            {'cpu': cpus, 'flavor': flavor},
            {'memory': memory, 'flavor': flavor},
            {'flavor': flavor, 'dedicated': dedicated},
            {'flavor': flavor, 'host_id': host_id}
        ]

        if not all(required):
            raise ValueError("hostname, and domain are required")

        for mu_ex in mutually_exclusive:
            if all(mu_ex.values()):
                raise ValueError(
                    'Can only specify one of: %s' % (','.join(mu_ex.keys())))

        data = {
            "startCpus": cpus,
            "maxMemory": memory,
            "hostname": hostname,
            "domain": domain,
            "localDiskFlag": local_disk,
            "hourlyBillingFlag": hourly,
            "supplementalCreateObjectOptions": {
                "bootMode": boot_mode
            }
        }

        if flavor:
            data["supplementalCreateObjectOptions"]["flavorKeyName"] = flavor

        if dedicated and not host_id: