"""
#############################################################################
#Copyright 2017 Parker Berberian and others                                 #
#                                                                           #
#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.                                             #
#############################################################################
"""

import requests
import time
import sys


class Fuel_api:

    def __init__(self, url, logger, user="admin", password="admin"):
        """
        url is the url of the fog api in the form
        http://ip.or.host.name:8000/
        logger is a reference to the logger
        the default creds for fuel is admin/admin
        """
        self.logger = logger
        self.base = url
        self.user = user
        self.password = password
        self.header = {"Content-Type": "application/json"}

    def getKey(self):
        """
        authenticates with the user and password
        to get a keystone key, used in the headers
        from here on to talk to fuel.
        """
        url = self.base + 'keystone/v2.0/tokens/'
        reqData = {"auth": {
            "tenantName": self.user,
            "passwordCredentials": {
                "username": self.user,
                "password": self.password
                }
            }}
        self.logger.info("Retreiving keystone token from %s", url)
        token = requests.post(url, headers=self.header, json=reqData)
        self.logger.info("Received response code %d", token.status_code)
        self.token = token.json()['access']['token']['id']
        self.header['X-Auth-Token'] = self.token

    def getNotifications(self):
        """
        returns the fuel notifications
        """
        url = self.base+'/api/notifications'
        try:
            req = requests.get(url, headers=self.header)
            return req.json()

        except Exception:
            self.logger.exception('%s', "Failed to talk to the Fuel api!")
            sys.exit(1)

    def waitForBootstrap(self):
        """
        Waits for the bootstrap image to build.
        """
        while True:
            time.sleep(30)
            notes = self.getNotifications()
            for note in notes:
                if "bootstrap image building done" in note['message']:
                    return

    def getNodes(self):
        """
        returns a list of all nodes booted into fuel
        """
        url = self.base+'api/nodes'
        try:
            req = requests.get(url, headers=self.header)
            return req.json()
        except Exception:
            self.logger.exception('%s', "Failed to talk to the Fuel api!")
            sys.exit(1)

    def getID(self, mac):
        """
        gets the fuel id of node with given mac
        """
        for node in self.getNodes():
            if node['mac'] == mac:
                return node['id']

    def getNetID(self, name, osid):
        """
        gets the id of the network with name
        """
        url = self.base+'api/clusters/'
        url += str(osid)+'/network_configuration/neutron'
        try:
            req = requests.get(url, headers=self.header)
            nets = req.json()['networks']
            for net in nets:
                if net['name'] == name:
                    return net['id']
            return -1

        except Exception:
            self.logger.exception('%s', "Failed to talk to the Fuel api!")
            sys.exit(1)

    def createOpenstack(self):
        """
        defines a new openstack environment in fuel.
        """
        url = self.base+'api/clusters'
        data = {
                "nodes": [],
                "tasks": [],
                "name": "OpenStack",
                "release_id": 2,
                "net_segment_type": "vlan"
                }
        try:
            req = requests.post(url, json=data, headers=self.header)
            return req.json()['id']
        except Exception:
            self.logger.exception('%s', "Failed to talk to the Fuel api!")
            sys.exit(1)

    def simpleNetDict(self, osID):
        """
        returns a simple dict of network names and id numbers
        """
        nets = self.getNetworks(osID)
        netDict = {}
        targetNets = ['admin', 'public', 'storage', 'management']
        for net in nets['networks']:
            for tarNet in targetNets:
                if tarNet in net['name']:
                    netDict[tarNet] = net['id']
        return netDict

    def getNetworks(self, osID):
        """
        Returns the pythonizezd json of the openstack networks
        """
        url = self.base + 'api/clusters/'
        url += str(osID)+'/network_configuration/neutron/'
        try:
            req = requests.get(url, headers=self.header)
            return req.json()
        except Exception:
            self.logger.exception('%s', "Failed to talk to the Fuel api!")
            sys.exit(1)

    def uploadNetworks(self, netJson, osID):
        """
        configures the networks of the openstack
        environment with id osID based on netJson
        """
        url = self.base+'api/clusters/'
        url += str(osID)+'/network_configuration/neutron'
        try:
            req = requests.put(url, headers=self.header, json=netJson)
            return req.json()
        except Exception:
            self.logger.exception('%s', "Failed to talk to the Fuel api!")
            sys.exit(1)

    def addNodes(self, clusterID, nodes):
        """
        Adds the nodes into this openstack environment.
        nodes is valid  json
        """
        url = self.base + 'api/clusters/'+str(clusterID)+'/assignment'
        try:
            req = requests.post(url, headers=self.header, json=nodes)
            return req.json()

        except Exception:
            self.logger.exception('%s', "Failed to talk to the Fuel api!")
            sys.exit(1)

    def getIfaces(self, nodeID):
        """
        returns the pythonized json describing the
        interfaces of given node
        """
        url = self.base + 'api/nodes/'+str(nodeID)+'/interfaces'
        try:
            req = requests.get(url, headers=self.header)
            return req.json()

        except Exception:
            self.logger.exception('%s', "Failed to talk to the Fuel api!")
            sys.exit(1)

    def setIfaces(self, nodeID, ifaceJson):
        """
        configures the interfaces of node with id nodeID
        with ifaceJson
        ifaceJson is valid json that fits fuel's schema for ifaces
        """
        url = self.base+'/api/nodes/'+str(nodeID)+'/interfaces'
        try:
            req = requests.put(url, headers=self.header, json=ifaceJson)
            return req.json()

        except Exception:
            self.logger.exception('%s', "Failed to talk to the Fuel api!")
            sys.exit(1)

    def getTasks(self):
        """
        returns a list of all tasks
        """
        url = self.base+"/api/tasks/"
        try:
            req = requests.get(url, headers=self.header)
            return req.json()
        except Exception:
            self.logger.exception('%s', "Failed to talk to the Fuel api!")
            sys.exit(1)

    def waitForTask(self, uuid):
        """
        Tracks the progress of task with uuid and
        returns once the task finishes
        """
        progress = 0
        while progress < 100:
            for task in self.getTasks():
                if task['uuid'] == uuid:
                    progress = task['progress']
            self.logger.info("Task is %s percent done", str(progress))
            time.sleep(20)
        # Task may hang a minute at 100% without finishing
        while True:
            for task in self.getTasks():
                if task['uuid'] == uuid and not task['status'] == "ready":
                    time.sleep(10)
                elif task['uuid'] == uuid and task['status'] == "ready":
                    return

    def getHorizonIP(self, osid):
        """
        returns the ip address of the horizon dashboard.
        Horizon always takes the first ip after the public router's
        """
        url = self.base+'api/clusters/'
        url += str(osid)+'/network_configuration/neutron/'
        try:
            req = requests.get(url, headers=self.header)
            routerIP = req.json()['vips']['vrouter_pub']['ipaddr'].split('.')
            routerIP[-1] = str(int(routerIP[-1])+1)
            return '.'.join(routerIP)
        except Exception:
            self.logger.exception('%s', "Failed to talk to the Fuel api!")
            sys.exit(1)

    def deployOpenstack(self, clusterID):
        """
        Once openstack and the nodes are configured,
        this method actually deploys openstack.
        It takes a while.
        """
        # First, we need to provision the cluster
        url = self.base+'/api/clusters/'+str(clusterID)+'/provision'
        req = requests.put(url, headers=self.header)
        if req.status_code < 300:
            self.logger.info('%s', "Sent provisioning task")
        else:
            err = "failed to provision Openstack Environment"
            self.logger.error('%s', err)
            sys.exit(1)

        taskUID = ''
        tasks = self.getTasks()
        for task in tasks:
            if task['name'] == "provision" and task['cluster'] == clusterID:
                taskUID = task['uuid']

        self.waitForTask(taskUID)

        # Then, we deploy cluster
        url = self.base + '/api/clusters/'+str(clusterID)+'/deploy'
        req = requests.put(url, headers=self.header)
        if req.status_code < 300:
            self.logger.info('%s', "Sent deployment task")
        taskUID = ''
        tasks = self.getTasks()
        for task in tasks:
            if 'deploy' in task['name'] and task['cluster'] == clusterID:
                taskUID = task['uuid']
        if len(taskUID) > 0:
            self.waitForTask(taskUID)