# -*- coding: utf-8 -*-
##
# This file is standalone vmware vcloud director util
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
# For those usages not covered by the Apache License, Version 2.0 please
# contact with: mbayramov@vmware.com
##

"""

Standalone application that leverage openmano vmware connector work with vCloud director rest api.

 - Provides capability to create and delete VDC for specific organization.
 - Create, delete and manage network for specific VDC
 - List deployed VM's , VAPPs, VDSs, Organization
 - View detail information about VM / Vapp , Organization etc
 - Operate with images upload / boot / power on etc

 Usage example.

 List organization created in vCloud director
  vmwarecli.py -u admin -p qwerty123 -c 172.16.254.206 -U Administrator -P qwerty123 -o test -v TEF list org

 List VDC for particular organization
  vmwarecli.py -u admin -p qwerty123 -c 172.16.254.206 -U Administrator -P qwerty123 -o test -v TEF list vdc

 Upload image
  python vmwarerecli.py image upload /Users/spyroot/Developer/Openmano/Ro/vnfs/cirros/cirros.ovf

 Boot Image
    python vmwarerecli.py -u admin -p qwerty123 -c 172.16.254.206 -o test -v TEF image boot cirros cirros

 View vApp
    python vmwarerecli.py -u admin -p qwerty123 -c 172.16.254.206 -o test -v TEF view vapp 90bd2b4e-f782-46cf-b5e2-c3817dcf6633 -u

 List VMS
    python vmwarerecli.py -u admin -p qwerty123 -c 172.16.254.206 -o test -v TEF list vms

 List VDC in OSM format
  python vmwarerecli.py -u admin -p qwerty123 -c 172.16.254.206 -o test -v TEF list vdc -o

Mustaafa Bayramov
mbayramov@vmware.com
"""
import os
import argparse
import traceback
import uuid

from xml.etree import ElementTree as ET

import sys
from pyvcloud import Http

import logging
import vimconn
import time
import uuid
import urllib3
import requests

from vimconn_vmware import vimconnector
from requests.packages.urllib3.exceptions import InsecureRequestWarning
from prettytable import PrettyTable

requests.packages.urllib3.disable_warnings(InsecureRequestWarning)

__author__ = "Mustafa Bayramov"
__date__ = "$16-Sep-2016 11:09:29$"


# TODO move to main vim
def delete_network_action(vca=None, network_uuid=None):
    """
    Method leverages vCloud director and query network based on network uuid

    Args:
        vca - is active VCA connection.
        network_uuid - is a network uuid

        Returns:
            The return XML respond
    """

    if vca is None or network_uuid is None:
        return None

    url_list = [vca.host, '/api/admin/network/', network_uuid]
    vm_list_rest_call = ''.join(url_list)

    if not (not vca.vcloud_session or not vca.vcloud_session.organization):
        response = Http.get(url=vm_list_rest_call,
                            headers=vca.vcloud_session.get_vcloud_headers(),
                            verify=vca.verify,
                            logger=vca.logger)
        if response.status_code == requests.codes.ok:
            print response.content
            return response.content

    return None


def print_vapp(vapp_dict=None):
    """ Method takes vapp_dict and print in tabular format

    Args:
        vapp_dict: container vapp object.

        Returns:
            The return nothing
    """

    # following key available to print
    # {'status': 'POWERED_OFF', 'storageProfileName': '*', 'hardwareVersion': '7', 'vmToolsVersion': '0',
    #  'memoryMB': '384',
    #  'href': 'https://172.16.254.206/api/vAppTemplate/vm-129e22e8-08dc-4cb6-8358-25f635e65d3b',
    #  'isBusy': 'false', 'isDeployed': 'false', 'isInMaintenanceMode': 'false', 'isVAppTemplate': 'true',
    #  'networkName': 'nat', 'isDeleted': 'false', 'catalogName': 'Cirros',
    #  'containerName': 'Cirros Template', #  'container':
    #  'https://172.16.254.206/api/vAppTemplate/vappTemplate-b966453d-c361-4505-9e38-ccef45815e5d',
    #  'name': 'Cirros', 'pvdcHighestSupportedHardwareVersion': '11', 'isPublished': 'false',
    #  'numberOfCpus': '1', 'vdc': 'https://172.16.254.206/api/vdc/a5056f85-418c-4bfd-8041-adb0f48be9d9',
    #  'guestOs': 'Other (32-bit)', 'isVdcEnabled': 'true'}

    if vapp_dict is None:
        return

    vm_table = PrettyTable(['vm   uuid',
                            'vapp name',
                            'vapp uuid',
                            'network name',
                            'storage name',
                            'vcpu', 'memory', 'hw ver','deployed','status'])
    for k in vapp_dict:
        entry = []
        entry.append(k)
        entry.append(vapp_dict[k]['containerName'])
        # vm-b1f5cd4c-2239-4c89-8fdc-a41ff18e0d61
        entry.append(vapp_dict[k]['container'].split('/')[-1:][0][5:])
        entry.append(vapp_dict[k]['networkName'])
        entry.append(vapp_dict[k]['storageProfileName'])
        entry.append(vapp_dict[k]['numberOfCpus'])
        entry.append(vapp_dict[k]['memoryMB'])
        entry.append(vapp_dict[k]['pvdcHighestSupportedHardwareVersion'])
        entry.append(vapp_dict[k]['isDeployed'])
        entry.append(vapp_dict[k]['status'])

        vm_table.add_row(entry)

    print vm_table


def print_org(org_dict=None):
    """ Method takes vapp_dict and print in tabular format

    Args:
        org_dict:  dictionary of organization where key is org uuid.

        Returns:
            The return nothing
    """

    if org_dict is None:
        return

    org_table = PrettyTable(['org uuid', 'name'])
    for k in org_dict:
        entry = [k, org_dict[k]]
        org_table.add_row(entry)

    print org_table


def print_vm_list(vm_dict=None):
    """ Method takes vapp_dict and print in tabular format

    Args:
        vm_dict:  dictionary of organization where key is org uuid.

        Returns:
            The return nothing
    """
    if vm_dict is None:
        return

    vm_table = PrettyTable(
        ['vm uuid', 'vm name', 'vapp uuid', 'vdc uuid', 'network name', 'is deployed', 'vcpu', 'memory', 'status'])

    try:
        for k in vm_dict:
            entry = []
            entry.append(k)
            entry.append(vm_dict[k]['name'])
            entry.append(vm_dict[k]['container'].split('/')[-1:][0][5:])
            entry.append(vm_dict[k]['vdc'].split('/')[-1:][0])
            entry.append(vm_dict[k]['networkName'])
            entry.append(vm_dict[k]['isDeployed'])
            entry.append(vm_dict[k]['numberOfCpus'])
            entry.append(vm_dict[k]['memoryMB'])
            entry.append(vm_dict[k]['status'])
            vm_table.add_row(entry)
        print vm_table
    except KeyError:
        logger.error("wrong key {}".format(KeyError.message))
        pass


def print_vdc_list(org_dict=None):
    """ Method takes vapp_dict and print in tabular format

    Args:
        org_dict:  dictionary of organization where key is org uuid.

        Returns:
            The return nothing
    """
    if org_dict is None:
        return
    try:
        vdcs_dict = {}
        if org_dict.has_key('vdcs'):
            vdcs_dict = org_dict['vdcs']
        vdc_table = PrettyTable(['vdc uuid', 'vdc name'])
        for k in vdcs_dict:
            entry = [k, vdcs_dict[k]]
            vdc_table.add_row(entry)

        print vdc_table
    except KeyError:
        logger.error("wrong key {}".format(KeyError.message))
        logger.logger.debug(traceback.format_exc())


def print_network_list(org_dict=None):
    """ Method print network list.

    Args:
        org_dict:   dictionary of organization that contain key networks with a list of all
                    network for for specific VDC

        Returns:
            The return nothing
    """
    if org_dict is None:
        return
    try:
        network_dict = {}
        if org_dict.has_key('networks'):
            network_dict = org_dict['networks']
        network_table = PrettyTable(['network uuid', 'network name'])
        for k in network_dict:
            entry = [k, network_dict[k]]
            network_table.add_row(entry)

        print network_table

    except KeyError:
        logger.error("wrong key {}".format(KeyError.message))
        logger.logger.debug(traceback.format_exc())


def print_org_details(org_dict=None):
    """ Method takes vapp_dict and print in tabular format

    Args:
        org_dict:  dictionary of organization where key is org uuid.

        Returns:
            The return nothing
    """
    if org_dict is None:
        return
    try:
        catalogs_dict = {}

        print_vdc_list(org_dict=org_dict)
        print_network_list(org_dict=org_dict)

        if org_dict.has_key('catalogs'):
            catalogs_dict = org_dict['catalogs']

        catalog_table = PrettyTable(['catalog uuid', 'catalog name'])
        for k in catalogs_dict:
            entry = [k, catalogs_dict[k]]
            catalog_table.add_row(entry)

        print catalog_table

    except KeyError:
        logger.error("wrong key {}".format(KeyError.message))
        logger.logger.debug(traceback.format_exc())


def delete_actions(vim=None, action=None, namespace=None):
    if action == 'network' or namespace.action == 'network':
        logger.debug("Requesting delete for network {}".format(namespace.network_name))
        network_uuid = namespace.network_name
        # if request name based we need find UUID
        # TODO optimize it or move to external function
        if not namespace.uuid:
            org_dict = vim.get_org_list()
            for org in org_dict:
                org_net = vim.get_org(org)['networks']
                for network in org_net:
                    if org_net[network] == namespace.network_name:
                        network_uuid = network

        vim.delete_network_action(network_uuid=network_uuid)


def list_actions(vim=None, action=None, namespace=None):
    """ Method provide list object from VDC action

       Args:
           vim - is vcloud director vim connector.
           action - is action for list ( vdc / org etc)
           namespace -  must contain VDC / Org information.

           Returns:
               The return nothing
       """

    org_id = None
    myorgs = vim.get_org_list()
    for org in myorgs:
        if myorgs[org] == namespace.vcdorg:
            org_id = org
        break
    else:
        print(" Invalid organization.")
        return

    if action == 'vms' or namespace.action == 'vms':
        vm_dict = vim.get_vm_list(vdc_name=namespace.vcdvdc)
        print_vm_list(vm_dict=vm_dict)
    elif action == 'vapps' or namespace.action == 'vapps':
        vapp_dict = vim.get_vapp_list(vdc_name=namespace.vcdvdc)
        print_vapp(vapp_dict=vapp_dict)
    elif action == 'networks' or namespace.action == 'networks':
        if namespace.osm:
            osm_print(vim.get_network_list(filter_dict={}))
        else:
            print_network_list(vim.get_org(org_uuid=org_id))
    elif action == 'vdc' or namespace.action == 'vdc':
        if namespace.osm:
            osm_print(vim.get_tenant_list(filter_dict=None))
        else:
            print_vdc_list(vim.get_org(org_uuid=org_id))
    elif action == 'org' or namespace.action == 'org':
        print_org(org_dict=vim.get_org_list())
    else:
        return None


def print_network_details(network_dict=None):
    try:
        network_table = PrettyTable(network_dict.keys())
        entry = [network_dict.values()]
        network_table.add_row(entry[0])
        print network_table
    except KeyError:
        logger.error("wrong key {}".format(KeyError.message))
        logger.logger.debug(traceback.format_exc())


def osm_print(generic_dict=None):

    try:
        for element in generic_dict:
            table = PrettyTable(element.keys())
            entry = [element.values()]
            table.add_row(entry[0])
        print table
    except KeyError:
        logger.error("wrong key {}".format(KeyError.message))
        logger.logger.debug(traceback.format_exc())


def view_actions(vim=None, action=None, namespace=None):
    org_id = None
    orgs = vim.get_org_list()
    for org in orgs:
        if orgs[org] == namespace.vcdorg:
            org_id = org
        break
    else:
        print(" Invalid organization.")
        return

    myorg = vim.get_org(org_uuid=org_id)

    # view org
    if action == 'org' or namespace.action == 'org':
        org_id = None
        orgs = vim.get_org_list()
        if namespace.uuid:
            if namespace.org_name in orgs:
                org_id = namespace.org_name
        else:
            # we need find UUID based on name provided
            for org in orgs:
                if orgs[org] == namespace.org_name:
                    org_id = org
                    break

        logger.debug("Requesting view for orgs {}".format(org_id))
        print_org_details(vim.get_org(org_uuid=org_id))

    # view vapp action
    if action == 'vapp' or namespace.action == 'vapp':
        if namespace.vapp_name is not None and namespace.uuid:
            logger.debug("Requesting vapp {} for vdc {}".format(namespace.vapp_name, namespace.vcdvdc))
            vapp_dict = {}
            vapp_uuid = namespace.vapp_name
            # if request based on just name we need get UUID
            if not namespace.uuid:
                vapp_uuid = vim.get_vappid(vdc=namespace.vcdvdc, vapp_name=namespace.vapp_name)
                if vapp_uuid is None:
                    print("Can't find vapp by given name {}".format(namespace.vapp_name))
                    return

            print " namespace {}".format(namespace)
            if vapp_dict is not None and namespace.osm:
                vm_info_dict = vim.get_vminstance(vim_vm_uuid=vapp_uuid)
                print vm_info_dict
            if vapp_dict is not None and namespace.osm != True:
                vapp_dict = vim.get_vapp(vdc_name=namespace.vcdvdc, vapp_name=vapp_uuid, isuuid=True)
                print_vapp(vapp_dict=vapp_dict)

    # view network
    if action == 'network' or namespace.action == 'network':
        logger.debug("Requesting view for network {}".format(namespace.network_name))
        network_uuid = namespace.network_name
        # if request name based we need find UUID
        # TODO optimize it or move to external function
        if not namespace.uuid:
            if not myorg.has_key('networks'):
                print("Network {} is undefined in vcloud director for org {} vdc {}".format(namespace.network_name,
                                                                                            vim.name,
                                                                                            vim.tenant_name))
                return

            my_org_net = myorg['networks']
            for network in my_org_net:
                if my_org_net[network] == namespace.network_name:
                    network_uuid = network
                    break

        print print_network_details(network_dict=vim.get_vcd_network(network_uuid=network_uuid))


def create_actions(vim=None, action=None, namespace=None):
    """Method gets provider vdc view from vcloud director

        Args:
            vim - is Cloud director vim connector
            action - action for create ( network / vdc etc)

        Returns:
            The return xml content of respond or None
    """
    if action == 'network' or namespace.action == 'network':
        logger.debug("Creating a network in vcloud director".format(namespace.network_name))
        network_uuid = vim.create_network(namespace.network_name)
        if network_uuid is not None:
            print ("Crated new network {} and uuid: {}".format(namespace.network_name, network_uuid))
        else:
            print ("Failed create a new network {}".format(namespace.network_name))
    elif action == 'vdc' or namespace.action == 'vdc':
        logger.debug("Creating a new vdc in vcloud director.".format(namespace.vdc_name))
        vdc_uuid = vim.create_vdc(namespace.vdc_name)
        if vdc_uuid is not None:
            print ("Crated new vdc {} and uuid: {}".format(namespace.vdc_name, vdc_uuid))
        else:
            print ("Failed create a new vdc {}".format(namespace.vdc_name))
    else:
        return None


def validate_uuid4(uuid_string):
    """Function validate that string contain valid uuid4

        Args:
            uuid_string - valid UUID string

        Returns:
            The return true if string contain valid UUID format
    """
    try:
        val = uuid.UUID(uuid_string, version=4)
    except ValueError:
        return False
    return True


def upload_image(vim=None, image_file=None):
    """Function upload image to vcloud director

        Args:
            image_file - valid UUID string

        Returns:
            The return true if image uploaded correctly
    """
    try:
        catalog_uuid = vim.get_image_id_from_path(path=image_file, progress=True)
        if catalog_uuid is not None and validate_uuid4(catalog_uuid):
            print("Image uploaded and uuid {}".format(catalog_uuid))
            return True
    except vimconn.vimconnException as upload_exception:
        print("Failed uploaded {} image".format(image_file))
        print("Error Reason: {}".format(upload_exception.message))
    return False


def boot_image(vim=None, image_name=None, vm_name=None):
    """ Function boot image that resided in vcloud director.
        The image name can be UUID of name.

        Args:
            vim - vim connector
            image_name - image identified by UUID or text string.
            vm_name - vmname


         Returns:
             The return true if image uploaded correctly
     """

    vim_catalog = None
    try:
        catalogs = vim.vca.get_catalogs()
        if not validate_uuid4(image_name):
            vim_catalog = vim.get_catalogid(catalog_name=image_name, catalogs=catalogs)
            if vim_catalog is None:
                return None
        else:
            vim_catalog = vim.get_catalogid(catalog_name=image_name, catalogs=catalogs)
            if vim_catalog is None:
                return None

        print (" Booting {} image id {} ".format(vm_name, vim_catalog))
        vm_uuid, _ = vim.new_vminstance(name=vm_name, image_id=vim_catalog)
        if vm_uuid is not None and validate_uuid4(vm_uuid):
            print("Image booted and vm uuid {}".format(vm_uuid))
            vapp_dict = vim.get_vapp(vdc_name=namespace.vcdvdc, vapp_name=vm_uuid, isuuid=True)
            if vapp_dict is not None:
                print_vapp(vapp_dict=vapp_dict)
        return True
    except vimconn.vimconnNotFoundException as notFound:
        print("Failed boot {} image".format(image_name))
        print(notFound.message)
    except vimconn.vimconnException as vimconError:
        print("Failed boot {} image".format(image_name))
        print(vimconError.message)
    except:
        print("Failed boot {} image".format(image_name))


        return False


def image_action(vim=None, action=None, namespace=None):
    """ Function present set of action to manipulate with image.
          - upload image
          - boot image.
          - delete image ( not yet done )

        Args:
             vim - vcloud director connector
             action - string (upload/boot etc)
             namespace - contain other attributes image name etc

         Returns:
             The return nothing
     """

    if action == 'upload' or namespace.action == 'upload':
        upload_image(vim=vim, image_file=namespace.image)
    elif action == 'boot' or namespace.action == 'boot':
        boot_image(vim=vim, image_name=namespace.image, vm_name=namespace.vmname)
    else:
        return None


def vmwarecli(command=None, action=None, namespace=None):
    logger.debug("Namespace {}".format(namespace))
    urllib3.disable_warnings()

    vcduser = None
    vcdpasword = None
    vcdhost = None