##############################################################################
# Copyright (c) 2017 Ericsson AB and others.
# Author: Jose Lausuch (jose.lausuch@ericsson.com)
# All rights reserved. This program and the accompanying materials
# are made available under the terms of the Apache License, Version 2.0
# which accompanies this distribution, and is available at
# http://www.apache.org/licenses/LICENSE-2.0
##############################################################################

from abc import abstractmethod
import os


from opnfv.utils import opnfv_logger as logger
from opnfv.utils import ssh_utils

logger = logger.Logger(__name__).getLogger()


class Deployment(object):

    def __init__(self,
                 installer,
                 installer_ip,
                 scenario,
                 pod,
                 status,
                 openstack_version,
                 sdn_controller,
                 nodes=None):

        self.deployment_info = {
            'installer': installer,
            'installer_ip': installer_ip,
            'scenario': scenario,
            'pod': pod,
            'status': status,
            'openstack_version': openstack_version,
            'sdn_controller': sdn_controller,
            'nodes': nodes
        }

    def _get_openstack_release(self):
        '''
        Translates an openstack version into the release name
        '''
        os_versions = {
            '12': 'Liberty',
            '13': 'Mitaka',
            '14': 'Newton',
            '15': 'Ocata',
            '16': 'Pike',
            '17': 'Queens'
        }
        try:
            version = self.deployment_info['openstack_version'].split('.')[0]
            name = os_versions[version]
            return name
        except Exception:
            return 'Unknown release'

    def get_dict(self):
        '''
        Returns a dictionary will all the attributes
        '''
        return self.deployment_info

    def __str__(self):
        '''
        Override of the str method
        '''
        s = '''
        INSTALLER:    {installer}
        SCENARIO:     {scenario}
        INSTALLER IP: {installer_ip}
        POD:          {pod}
        STATUS:       {status}
        OPENSTACK:    {openstack_version} ({openstack_release})
        SDN:          {sdn_controller}
        NODES:
    '''.format(installer=self.deployment_info['installer'],
               scenario=self.deployment_info['scenario'],
               installer_ip=self.deployment_info['installer_ip'],
               pod=self.deployment_info['pod'],
               status=self.deployment_info['status'],
               openstack_version=self.deployment_info[
            'openstack_version'],
            openstack_release=self._get_openstack_release(),
            sdn_controller=self.deployment_info['sdn_controller'])

        for node in self.deployment_info['nodes']:
            s += '{node_object}\n'.format(node_object=node)

        return s


class Role():
    INSTALLER = 'installer'
    CONTROLLER = 'controller'
    COMPUTE = 'compute'
    ODL = 'opendaylight'
    ONOS = 'onos'


class NodeStatus():
    STATUS_OK = 'active'
    STATUS_INACTIVE = 'inactive'
    STATUS_OFFLINE = 'offline'
    STATUS_ERROR = 'error'
    STATUS_UNUSED = 'unused'
    STATUS_UNKNOWN = 'unknown'


class Node(object):

    def __init__(self,
                 id,
                 ip,
                 name,
                 status,
                 roles=None,
                 ssh_client=None,
                 info=None):
        self.id = id
        self.ip = ip
        self.name = name
        self.status = status
        self.ssh_client = ssh_client
        self.roles = roles
        self.info = info

        self.cpu_info = 'unknown'
        self.memory = 'unknown'
        self.ovs = 'unknown'

        if ssh_client and Role.INSTALLER not in self.roles:
            sys_info = self.get_system_info()
            self.cpu_info = sys_info['cpu_info']
            self.memory = sys_info['memory']
            self.ovs = self.get_ovs_info()

    def get_file(self, src, dest):
        '''
        SCP file from a node
        '''
        if self.status is not NodeStatus.STATUS_OK:
            logger.info("The node %s is not active" % self.ip)
            return 1
        logger.info("Fetching %s from %s" % (src, self.ip))
        get_file_result = ssh_utils.get_file(self.ssh_client, src, dest)
        if get_file_result is None:
            logger.error("SFTP failed to retrieve the file.")
        else:
            logger.info("Successfully copied %s:%s to %s" %
                        (self.ip, src, dest))
        return get_file_result

    def put_file(self, src, dest):
        '''
        SCP file to a node
        '''
        if self.status is not NodeStatus.STATUS_OK:
            logger.info("The node %s is not active" % self.ip)
            return 1
        logger.info("Copying %s to %s" % (src, self.ip))
        put_file_result = ssh_utils.put_file(self.ssh_client, src, dest)
        if put_file_result is None:
            logger.error("SFTP failed to retrieve the file.")
        else:
            logger.info("Successfully copied %s to %s:%s" %
                        (src, dest, self.ip))
        return put_file_result

    def run_cmd(self, cmd):
        '''
        Run command remotely on a node
        '''
        if self.status is not NodeStatus.STATUS_OK:
            logger.error(
                "Error running command %s. The node %s is not active"
                % (cmd, self.ip))
            return None
        _, stdout, stderr = (self.ssh_client.exec_command(cmd))
        error = stderr.readlines()
        if len(error) > 0:
            logger.error("error %s" % ''.join(error))
            return None
        output = ''.join(stdout.readlines()).rstrip()
        return output

    def get_dict(self):
        '''
        Returns a dictionary with all the attributes
        '''
        return {
            'id': self.id,
            'ip': self.ip,
            'name': self.name,
            'status': self.status,
            'roles': self.roles,
            'cpu_info': self.cpu_info,
            'memory': self.memory,
            'ovs': self.ovs,
            'info': self.info
        }

    def is_active(self):
        '''
        Returns if the node is active
        '''
        if self.status == NodeStatus.STATUS_OK:
            return True
        return False

    def is_controller(self):
        '''
        Returns if the node is a controller
        '''
        return Role.CONTROLLER in self.roles

    def is_compute(self):
        '''
        Returns if the node is a compute
        '''
        return Role.COMPUTE in self.roles

    def is_odl(self):
        '''
        Returns if the node is an opendaylight
        '''
        return Role.ODL in self.roles

    def is_onos(self):
        '''
        Returns if the node is an ONOS
        '''
        return Role.ONOS in self.roles

    def get_ovs_info(self):
        '''
        Returns the ovs version installed
        '''
        if self.is_active():
            cmd = "ovs-vsctl --version|head -1| sed 's/^.*) //'"
            return self.run_cmd(cmd)
        return None

    def get_system_info(self):
        '''
        Returns the ovs version installed
        '''
        cmd = 'grep MemTotal /proc/meminfo'
        memory = self.run_cmd(cmd).partition('MemTotal:')[-1].strip().encode()

        cpu_info = {}
        cmd = 'lscpu'
        result = self.run_cmd(cmd)
        for line in result.splitlines():
            if line.startswith('CPU(s)'):
                cpu_info['num_cpus'] = line.split(' ')[-1].encode()
            elif line.startswith('Thread(s) per core'):
                cpu_info['threads/core'] = line.split(' ')[-1].encode()
            elif line.startswith('Core(s) per socket'):
                cpu_info['cores/socket'] = line.split(' ')[-1].encode()
            elif line.startswith('Model name'):
                cpu_info['model'] = line.partition(
                    'Model name:')[-1].strip().encode()
            elif line.startswith('Architecture'):
                cpu_info['arch'] = line.split(' ')[-1].encode()

        return {'memory': memory, 'cpu_info': cpu_info}

    def __str__(self):
        return '''
            name:    {name}
            id:      {id}
            ip:      {ip}
            status:  {status}
            roles:   {roles}
            cpu:     {cpu_info}
            memory:  {memory}
            ovs:     {ovs}
            info:    {info}'''.format(name=self.name,
                                      id=self.id,
                                      ip=self.ip,
                                      status=self.status,
                                      roles=self.roles,
                                      cpu_info=self.cpu_info,
                                      memory=self.memory,
                                      ovs=self.ovs,
                                      info=self.info)


class DeploymentHandler(object):

    EX_OK = os.EX_OK
    EX_ERROR = os.EX_SOFTWARE
    FUNCTION_NOT_IMPLEMENTED = "Function not implemented by adapter!"

    def __init__(self,
                 installer,
                 installer_ip,
                 installer_user,
                 installer_pwd=None,
                 pkey_file=None):

        self.installer = installer.lower()
        self.installer_ip = installer_ip
        self.installer_user = installer_user
        self.installer_pwd = installer_pwd
        self.pkey_file = pkey_file

        if pkey_file is not None and not os.path.isfile(pkey_file):
            raise Exception(
                'The private key file %s does not exist!' % pkey_file)

        self.installer_connection = ssh_utils.get_ssh_client(
            hostname=self.installer_ip,
            username=self.installer_user,
            password=self.installer_pwd,
            pkey_file=self.pkey_file)

        if self.installer_connection:
            self.installer_node = Node(id='',
                                       ip=installer_ip,
                                       name=installer,
                                       status=NodeStatus.STATUS_OK,
                                       ssh_client=self.installer_connection,
                                       roles=Role.INSTALLER)
        else:
            raise Exception(
                'Cannot establish connection to the installer node!')

        self.nodes = self.get_nodes()

    @abstractmethod
    def get_openstack_version(self):
        '''
        Returns a string of the openstack version (nova-compute)
        '''
        raise Exception(DeploymentHandler.FUNCTION_NOT_IMPLEMENTED)

    @abstractmethod
    def get_sdn_version(self):
        '''
        Returns a string of the sdn controller and its version, if exists
        '''
        raise Exception(DeploymentHandler.FUNCTION_NOT_IMPLEMENTED)

    @abstractmethod
    def get_deployment_status(self):
        '''
        Returns a string of the status of the deployment
        '''
        raise Exception(DeploymentHandler.FUNCTION_NOT_IMPLEMENTED)

    @abstractmethod
    def get_nodes(self, options=None):
        '''
            Generates a list of all the nodes in the deployment
        '''
        raise Exception(DeploymentHandler.FUNCTION_NOT_IMPLEMENTED)

    def get_installer_node(self):
        '''
            Returns the installer node object
        '''
        return self.installer_node

    def get_arch(self):
        '''
            Returns the architecture of the first compute node found
        '''
        arch = None
        for node in self.nodes:
            if node.is_compute():
                arch = node.cpu_info.get('arch', None)
                if arch:
                    break
        return arch

    def get_deployment_info(self):
        '''
            Returns an object of type Deployment
        '''
        return Deployment(installer=self.installer,
                          installer_ip=self.installer_ip,
                          scenario=os.getenv('DEPLOY_SCENARIO', 'Unknown'),
                          status=self.get_deployment_status(),
                          pod=os.getenv('NODE_NAME', 'Unknown'),
                          openstack_version=self.get_openstack_version(),
                          sdn_controller=self.get_sdn_version(),
                          nodes=self.nodes)