##############################################################################
# Copyright (c) 2016 Dell Inc, ZTE and others.
#
# 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
##############################################################################
import os
import sys
import yaml
import heatclient.client
import keystoneclient
import time
from env_setup import Env_setup
from create_zones import AvailabilityZone
import logger_utils

logger = logger_utils.QtipLogger('spawn_vm').get


class SpawnVM(Env_setup):

    def __init__(self, vm_info):
        logger.info('vm_info: %s' % vm_info)
        vm_role_ip_dict = vm_info.copy()
        self._keystone_client = None
        self._heat_client = None
        self._glance_client = None
        self._nova_client = None
        self.azone = AvailabilityZone()
        # TODO: it should clean up aggregates and stack after test case finished.
        self.azone.clean_all_aggregates()
        self.azone.create_aggs(vm_info['availability_zone'])
        self.heat_template = self.generate_heat_template(vm_info)
        self.create_stack(vm_role_ip_dict)

    @staticmethod
    def get_public_network():

        """
        TODO: GET THE NAMES OF THE PUBLIC NETWORKS for OTHER PROJECTS
        """
        installer = os.environ['INSTALLER_TYPE']

        if installer.lower() == 'fuel':
            return 'admin_floating_net'
        if installer.lower() == 'apex':
            return 'external'
        if installer.lower() == 'compass':
            return 'ext-net'
        if installer.lower() == 'joid':
            return 'ext-net'

    def generate_heat_template(self, vm_params):
        logger.info('Generating Heat Template')
        heat_dict = {}
        try:
            with open('./config/SampleHeat.yaml', 'r+') as H_temp:
                heat_dict = yaml.safe_load(H_temp)
        except yaml.YAMLError as exc:
            if hasattr(exc, 'problem_mark'):
                mark = exc.problem_mark
                logger.error(
                    'Error in qtip/config/SampleHeat.yaml at: (%s,%s)' % (mark.line + 1,
                                                                          mark.column + 1))
                logger.error('EXITING PROGRAM. Correct File and restart')
                sys.exit(1)

        fopen = open('./config/QtipKey.pub', 'r')
        fopenstr = fopen.read()
        fopenstr = fopenstr.rstrip()
        scriptcmd = '#!/bin/bash \n echo {0} >>  foo.txt \n echo {1} >> /root/.ssh/authorized_keys'.format(
            fopenstr, fopenstr)

        netName = self.get_public_network()
        heat_dict['heat_template_version'] = '2015-04-30'

        heat_dict['parameters']['public_network'] = {
            'type': 'string',
            'default': netName
        }

        for x in range(1, len(vm_params['availability_zone']) + 1):
            avail_zone = vm_params['availability_zone'][x - 1]

            heat_dict['parameters']['availability_zone_' + str(x)] = \
                {'description': 'Availability Zone of the instance',
                 'default': avail_zone,
                 'type': 'string'}

            heat_dict['resources']['public_port_' + str(x)] = \
                {'type': 'OS::Neutron::Port',
                 'properties': {'network': {'get_resource': 'network'},
                                'security_groups': [{'get_resource': 'security_group'}],
                                'fixed_ips': [{'subnet_id': {'get_resource': 'subnet'}}]}}

            heat_dict['resources']['floating_ip_' + str(x)] = {
                'type': 'OS::Neutron::FloatingIP',
                'properties': {'floating_network': {'get_param': 'external_net_name'}}}

            heat_dict['resources']['floating_ip_assoc_' + str(x)] = {
                'type': 'OS::Neutron::FloatingIPAssociation',
                'properties': {
                    'floatingip_id': {'get_resource': 'floating_ip_' + str(x)},
                    'port_id': {'get_resource': 'public_port_' + str(x)}}}

            heat_dict['resources']['my_instance_' + str(x)] = \
                {'type': 'OS::Nova::Server',
                 'properties': {'image': {'get_param': 'image'},
                                'networks':
                                    [{'port': {'get_resource': 'public_port_' + str(x)}}],
                                'flavor': {'get_resource': 'flavor'},
                                'availability_zone': avail_zone,
                                'security_groups': [{'get_resource': 'security_group'}],
                                'name': 'instance' + str(x),
                                'user_data_format': 'RAW',
                                'user_data': scriptcmd}}

            heat_dict['outputs']['instance_PIP_' + str(x)] = {
                'description': 'IP address of the instance',
                'value': {'get_attr': ['my_instance_' + str(x), 'first_address']}}

            heat_dict['outputs']['instance_ip_' + str(x)] = {
                'description': 'IP address of the instance',
                'value': {'get_attr': ['floating_ip_' + str(x), 'floating_ip_address']}}

            heat_dict['outputs']['availability_instance_' + str(x)] = {
                'description': 'Availability Zone of the Instance',
                'value': {'get_param': 'availability_zone_' + str(x)}}

        del heat_dict['outputs']['description']
        logger.info(heat_dict)

        return heat_dict

    def _get_keystone_client(self):
        """returns a keystone client instance"""

        if self._keystone_client is None:
            self._keystone_client = keystoneclient.v2_0.client.Client(
                auth_url=os.environ.get('OS_AUTH_URL'),
                username=os.environ.get('OS_USERNAME'),
                password=os.environ.get('OS_PASSWORD'),
                tenant_name=os.environ.get('OS_TENANT_NAME'))
        return self._keystone_client

    def _get_heat_client(self):
        """returns a heat client instance"""
        if self._heat_client is None:
            keystone = self._get_keystone_client()
            heat_endpoint = keystone.service_catalog.url_for(
                service_type='orchestration')
            self._heat_client = heatclient.client.Client(
                '1', endpoint=heat_endpoint, token=keystone.auth_token)
        return self._heat_client

    def create_stack(self, vm_role_ip_dict):
        stackname = 'QTIP'
        heat = self._get_heat_client()

        self.delete_stack(stackname)

        logger.info('Start to create stack %s' % stackname)
        heat.stacks.create(stack_name=stackname, template=self.heat_template)

        stack_status = "IN_PROGRESS"
        while stack_status != 'COMPLETE':
            if stack_status == 'IN_PROGRESS':
                logger.debug('Create in Progress')
            if stack_status == 'CREATE_FAILED':
                raise RuntimeError("Stack %s created failed!" % stackname)
            stack_status = heat.stacks.get(stackname).status
            time.sleep(15)
        logger.info('Stack %s Created Complete!' % stackname)

        stack_outputs = heat.stacks.get(stackname).outputs

        for vm in range(len(vm_role_ip_dict['OS_image'])):
            for i in stack_outputs:
                instanceKey = "instance_ip_" + str(vm + 1)
                privateIPkey = 'instance_PIP_' + str(vm + 1)
                if i['output_key'] == instanceKey:
                    Env_setup.roles_dict[vm_role_ip_dict['role'][vm]] \
                        .append(str(i['output_value']))
                    Env_setup.ip_pw_list.append((str(i['output_value']), ''))

                if i['output_key'] == privateIPkey:
                    Env_setup.ip_pw_dict[vm_role_ip_dict['role'][vm]] = str(i['output_value'])

        logger.info('Getting Public IP(s): %s' % Env_setup.ip_pw_list)

    def delete_stack(self, stack_name):
        heat = self._get_heat_client()

        stacks = heat.stacks.list()
        exists = map(lambda x: x.stack_name, stacks)
        if stack_name in exists:
            logger.info("Delete stack %s" % stack_name)
            heat.stacks.delete(stack_name)
            while stack_name in exists:
                time.sleep(10)
                stacks = heat.stacks.list()
                exists = map(lambda x: x.stack_name, stacks)
                logger.debug("exists_stacks: %s" % exists)
        logger.info("%s doesn't exist" % stack_name)