#    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 os

from toscaparser.common import exception
from toscaparser.substitution_mappings import SubstitutionMappings
from toscaparser.tests.base import TestCase
from toscaparser.tests import utils
from toscaparser.topology_template import TopologyTemplate
from toscaparser.tosca_template import ToscaTemplate
from toscaparser.utils.gettextutils import _
import toscaparser.utils.yamlparser

YAML_LOADER = toscaparser.utils.yamlparser.load_yaml


class TopologyTemplateTest(TestCase):

    def setUp(self):
        TestCase.setUp(self)
        '''TOSCA template.'''
        self.tosca_tpl_path = utils.get_sample_test_path(
            "data/topology_template/transactionsubsystem.yaml")
        self.tpl = YAML_LOADER(self.tosca_tpl_path)
        self.topo_tpl = self.tpl.get('topology_template')
        self.imports = self.tpl.get('imports')
        self.topo = TopologyTemplate(self.topo_tpl,
                                     self._get_all_custom_def())

    def _get_custom_def(self, type_definition):
        custom_defs = {}
        for definition in self.imports:
            if os.path.isabs(definition):
                def_file = definition
            else:
                tpl_dir = os.path.dirname(os.path.abspath(self.tosca_tpl_path))
                def_file = os.path.join(tpl_dir, definition)
            custom_type = YAML_LOADER(def_file)
            custom_defs.update(custom_type.get(type_definition))
        return custom_defs

    def _get_all_custom_def(self):
        custom_defs = {}
        custom_defs.update(self._get_custom_def('node_types'))
        custom_defs.update(self._get_custom_def('capability_types'))
        return custom_defs

    def _get_custom_types(self):
        custom_types = {}
        def_file = utils.get_sample_test_path(
            "data/topology_template/definitions.yaml")
        custom_type = YAML_LOADER(def_file)
        node_types = custom_type['node_types']
        for name in node_types:
            defintion = node_types[name]
            custom_types[name] = defintion
        return custom_types

    def test_description(self):
        expected_desc = 'Template of a database including its hosting stack.'
        self.assertEqual(expected_desc, self.topo.description)

    def test_inputs(self):
        self.assertEqual(
            ['mq_server_ip', 'my_cpus', 'receiver_port'],
            sorted([input.name for input in self.topo.inputs]))

        input_name = "receiver_port"
        expected_description = "Port to be used for receiving messages."
        for input in self.topo.inputs:
            if input.name == input_name:
                self.assertEqual(expected_description, input.description)

    def test_node_tpls(self):
        '''Test nodetemplate names.'''
        self.assertEqual(
            ['app', 'db', 'server', 'websrv'],
            sorted([tpl.name for tpl in self.topo.nodetemplates]))

        tpl_name = "app"
        expected_type = "example.SomeApp"
        expected_properties = ['admin_user', 'pool_size']
        expected_capabilities = ['app_endpoint', 'feature', 'message_receiver']
        expected_requirements = [{'host': {'node': 'websrv'}},
                                 {'database': {'node': 'db'}}]
        expected_relationshp = ['tosca.relationships.HostedOn',
                                'tosca.relationships.ConnectsTo']
        expected_host = ['websrv', 'db']
        for tpl in self.topo.nodetemplates:
            if tpl_name == tpl.name:
                '''Test node type.'''
                self.assertEqual(tpl.type, expected_type)

                '''Test properties.'''
                self.assertEqual(
                    expected_properties,
                    sorted(tpl.get_properties().keys()))

                '''Test capabilities.'''
                self.assertEqual(
                    expected_capabilities,
                    sorted(tpl.get_capabilities().keys()))

                '''Test requirements.'''
                self.assertEqual(
                    expected_requirements, tpl.requirements)

                '''Test relationship.'''
                ''' TODO : skip tempororily. need to fix it
                '''
                self.assertEqual(
                    expected_relationshp,
                    [x.type for x in tpl.relationships.keys()])
                self.assertEqual(
                    expected_host,
                    [y.name for y in tpl.relationships.values()])
                '''Test interfaces.'''
                # TODO(hurf) add interface test when new template is available

            if tpl.name == 'server':
                '''Test property value'''
                props = tpl.get_properties()
                if props and 'mem_size' in props.keys():
                    self.assertEqual(props['mem_size'].value, '4096 MB')
                '''Test capability'''
                caps = tpl.get_capabilities()
                self.assertIn('os', caps.keys())
                os_props_objs = None
                os_props = None
                os_type_prop = None
                if caps and 'os' in caps.keys():
                    capability = caps['os']
                    os_props_objs = capability.get_properties_objects()
                    os_props = capability.get_properties()
                    os_type_prop = capability.get_property_value('type')
                    break
                self.assertEqual(
                    ['Linux'],
                    [p.value for p in os_props_objs if p.name == 'type'])
                self.assertEqual(
                    'Linux',
                    os_props['type'].value if 'type' in os_props else '')
                self.assertEqual('Linux', os_props['type'].value)
                self.assertEqual('Linux', os_type_prop)

    def test_outputs(self):
        self.assertEqual(
            sorted(['receiver_ip', 'receiver_port']),
            sorted([output.name for output in self.topo.outputs]))

    def test_groups(self):
        group = self.topo.groups[0]
        self.assertEqual('webserver_group', group.name)
        self.assertEqual(['websrv', 'server'], group.members)
        for node in group.get_member_nodes():
            if node.name == 'server':
                '''Test property value'''
                props = node.get_properties()
                if props and 'mem_size' in props.keys():
                    self.assertEqual(props['mem_size'].value, '4096 MB')

    def test_system_template(self):
        tpl_path = utils.get_sample_test_path(
            "data/topology_template/system.yaml")
        system_tosca_template = ToscaTemplate(tpl_path)
        self.assertIsNotNone(system_tosca_template)
        self.assertEqual(
            len(system_tosca_template.
                nested_tosca_templates_with_topology), 4)
        self.assertTrue(system_tosca_template.has_nested_templates())

    def test_invalid_keyname(self):
        tpl_snippet = '''
        substitution_mappings:
          node_type: example.DatabaseSubsystem
          capabilities:
            database_endpoint: [ db_app, database_endpoint ]
          requirements:
            receiver1: [ tran_app, receiver1 ]
          invalid_key: 123
        '''
        sub_mappings = (toscaparser.utils.yamlparser.
                        simple_parse(tpl_snippet))['substitution_mappings']
        expected_message = _(
            'SubstitutionMappings contains unknown field '
            '"invalid_key". Refer to the definition '
            'to verify valid values.')
        err = self.assertRaises(
            exception.UnknownFieldError,
            lambda: SubstitutionMappings(sub_mappings, None, None,
                                         None, None, None))
        self.assertEqual(expected_message, err.__str__())

    def test_missing_required_keyname(self):
        tpl_snippet = '''
        substitution_mappings:
          capabilities:
            database_endpoint: [ db_app, database_endpoint ]
          requirements:
            receiver1: [ tran_app, receiver1 ]
        '''
        sub_mappings = (toscaparser.utils.yamlparser.
                        simple_parse(tpl_snippet))['substitution_mappings']
        expected_message = _('SubstitutionMappings used in topology_template '
                             'is missing required field "node_type".')
        err = self.assertRaises(
            exception.MissingRequiredFieldError,
            lambda: SubstitutionMappings(sub_mappings, None, None,
                                         None, None, None))
        self.assertEqual(expected_message, err.__str__())

    def test_invalid_nodetype(self):
        tpl_snippet = '''
        substitution_mappings:
          node_type: example.DatabaseSubsystem1
          capabilities:
            database_endpoint: [ db_app, database_endpoint ]
          requirements:
            receiver1: [ tran_app, receiver1 ]
        '''
        sub_mappings = (toscaparser.utils.yamlparser.
                        simple_parse(tpl_snippet))['substitution_mappings']
        custom_defs = self._get_custom_types()
        expected_message = _('Node type "example.DatabaseSubsystem1" '
                             'is not a valid type.')
        err = self.assertRaises(
            exception.InvalidNodeTypeError,
            lambda: SubstitutionMappings(sub_mappings, None, None,
                                         None, None, custom_defs))
        self.assertEqual(expected_message, err.__str__())

    def test_system_with_input_validation(self):
        tpl_path0 = utils.get_sample_test_path(
            "data/topology_template/validate/system_invalid_input.yaml")
        tpl_path1 = utils.get_sample_test_path(
            "data/topology_template/validate/"
            "queuingsubsystem_invalid_input.yaml")
        errormsg = _('SubstitutionMappings with node_type '
                     'example.QueuingSubsystem is missing '
                     'required input definition of input "server_port".')

        # It's invalid in nested template.
        self.assertRaises(exception.ValidationError,
                          lambda: ToscaTemplate(tpl_path0))
        exception.ExceptionCollector.assertExceptionMessage(
            exception.MissingRequiredInputError, errormsg)

        # Subtemplate deploy standaolone is also invalid.
        self.assertRaises(exception.ValidationError,
                          lambda: ToscaTemplate(tpl_path1))
        exception.ExceptionCollector.assertExceptionMessage(
            exception.MissingRequiredInputError, errormsg)

    def test_substitution_mappings_valid_output(self):
        tpl_path = utils.get_sample_test_path(
            "data/topology_template/validate/"
            "test_substitution_mappings_valid_output.yaml")
        self.assertIsNotNone(ToscaTemplate(tpl_path))

    def test_system_with_unknown_output_validation(self):
        tpl_path = utils.get_sample_test_path(
            "data/topology_template/validate/"
            "test_substitution_mappings_invalid_output.yaml")
        errormsg = _('\'Attribute "my_cpu_output" was not found in node '
                     'template "substitute_app".\'')
        self.assertRaises(exception.ValidationError,
                          lambda: ToscaTemplate(tpl_path))
        exception.ExceptionCollector.assertExceptionMessage(
            KeyError, errormsg)

    def test_substitution_mappings_with_substitution_filter(self):
        tpl_path = utils.get_sample_test_path(
            "data/topology_template/validate/"
            "test_substitution_mappings_section.yaml")
        tosca = ToscaTemplate(tpl_path)
        filter_name = tosca.tpl['topology_template'][
            'substitution_mappings']['substitution_filter'].keys()
        self.assertEqual('node_filter', list(filter_name)[0])
        n_f = tosca.topology_template.substitution_mappings.sub_mapping_def[
            'substitution_filter'].keys()
        self.assertEqual('node_filter', list(n_f)[0])
        p_v = tosca.topology_template.substitution_mappings.sub_mapping_def[
            'substitution_filter'].values()
        self.assertEqual('property_value',
                         list(p_v)[0]['properties'][0]['my_property'])

    def test_substitution_mappings_error_substitution_filter(self):
        tpl_path = utils.get_sample_test_path(
            "data/topology_template/validate/"
            "test_substitution_mappings_error_node_filter.yaml")
        errormsg = _('SubstitutionMappings contains unknown field '
                     '"substitution_filter". Refer to the definition '
                     'to verify valid values.')
        err = self.assertRaises(
            exception.ValidationError, ToscaTemplate, tpl_path)
        self.assertIn(errormsg, str(err))

    def test_substitution_mappings_error_node_filter(self):
        tpl_path = utils.get_sample_test_path(
            "data/topology_template/validate/"
            "test_substitution_mappings_error_node_filter_key.yaml")
        errormsg = _('SubstitutionMappings contains unknown field '
                     '"node_filter". Refer to the definition '
                     'to verify valid values.')
        err = self.assertRaises(
            exception.ValidationError, ToscaTemplate, tpl_path)
        self.assertIn(errormsg, str(err))

    def test_substitution_mappings_error_properties(self):
        tpl_path = utils.get_sample_test_path(
            "data/topology_template/validate/"
            "test_substitution_mappings_error_property_type.yaml")
        errormsg = _('properties of the Node Filter definition '
                     'Keyname must be of type "list".')
        err = self.assertRaises(
            exception.ValidationError, ToscaTemplate, tpl_path)
        self.assertIn(errormsg, str(err))

    def test_substitution_mappings_error_capabilities(self):
        tpl_path = utils.get_sample_test_path(
            "data/topology_template/validate/"
            "test_substitution_mappings_error_capability_type.yaml")
        errormsg = _('capabilities of the Node Filter definition '
                     'Keyname must be of type "list".')
        err = self.assertRaises(
            exception.ValidationError, ToscaTemplate, tpl_path)
        self.assertIn(errormsg, str(err))

    def test_substitution_mappings_with_attributes(self):
        tpl_path = utils.get_sample_test_path(
            "data/topology_template/validate/"
            "test_substitution_mappings_section.yaml")
        tosca = ToscaTemplate(tpl_path)
        attribute_name = tosca.tpl['topology_template'][
            'substitution_mappings']['attributes'].keys()
        self.assertEqual('receiver_ip', list(attribute_name)[0])
        a_n = tosca.topology_template.substitution_mappings.sub_mapping_def[
            'attributes'].keys()
        self.assertEqual('receiver_ip', list(a_n)[0])
        a_v = tosca.topology_template.substitution_mappings.sub_mapping_def[
            'attributes'].values()
        self.assertEqual('my_property', list(a_v)[0])

    def test_substitution_mappings_with_interfaces(self):
        tpl_path = utils.get_sample_test_path(
            "data/topology_template/validate/"
            "test_substitution_mappings_section.yaml")
        tosca = ToscaTemplate(tpl_path)
        interface_name = tosca.tpl['topology_template'][
            'substitution_mappings']['interfaces'].keys()
        self.assertEqual('Standard', list(interface_name)[0])
        i_n = tosca.topology_template.substitution_mappings.sub_mapping_def[
            'interfaces'].keys()
        self.assertEqual('Standard', list(i_n)[0])
        i_v = tosca.topology_template.substitution_mappings.sub_mapping_def[
            'interfaces'].values()
        self.assertEqual('my_start_node.start', list(i_v)[0]['start'])

    def test_invalid_type_policies(self):
        tpl_snippet = '''
        policies:
           some_policy:
              type: tosca.policies.Placement
        '''
        policies = (toscaparser.utils.yamlparser.simple_parse(tpl_snippet))
        custom_defs = self._get_custom_types()
        err = self.assertRaises(exception.TypeMismatchError,
                                lambda: TopologyTemplate(policies,
                                                         custom_defs))
        errormsg = _('policies must be of type "list".')
        self.assertEqual(errormsg, err.__str__())

    def test_invalid_type_groups(self):
        tpl_snippet = '''
        groups:
           - some_group:
              type: tosca.groups.Root
        '''
        policies = (toscaparser.utils.yamlparser.simple_parse(tpl_snippet))
        custom_defs = self._get_custom_types()
        err = self.assertRaises(exception.TypeMismatchError,
                                lambda: TopologyTemplate(policies,
                                                         custom_defs))
        errormsg = _('groups must be of type "dict".')
        self.assertEqual(errormsg, err.__str__())

    def test_invalid_type_substitution_mappings(self):
        tpl_snippet = '''
        substitution_mappings:
           - node_type: MyService
             properties:
               num_cpus: cpus
        '''
        policies = (toscaparser.utils.yamlparser.simple_parse(tpl_snippet))
        custom_defs = self._get_custom_types()
        err = self.assertRaises(exception.TypeMismatchError,
                                lambda: TopologyTemplate(policies,
                                                         custom_defs))
        errormsg = _('substitution_mappings must be of type "dict".')
        self.assertEqual(errormsg, err.__str__())

    def test_invalid_type_outputs(self):
        tpl_snippet = '''
        outputs:
           - some_output:
               value: some_value
        '''
        policies = (toscaparser.utils.yamlparser.simple_parse(tpl_snippet))
        custom_defs = self._get_custom_types()
        err = self.assertRaises(exception.TypeMismatchError,
                                lambda: TopologyTemplate(policies,
                                                         custom_defs))
        errormsg = _('outputs must be of type "dict".')
        self.assertEqual(errormsg, err.__str__())

    def test_invalid_type_relationship_templates(self):
        tpl_snippet = '''
        relationship_templates:
           - my_connection:
                type: ConnectsTo
        '''
        policies = (toscaparser.utils.yamlparser.simple_parse(tpl_snippet))
        custom_defs = self._get_custom_types()
        err = self.assertRaises(exception.TypeMismatchError,
                                lambda: TopologyTemplate(policies,
                                                         custom_defs))
        errormsg = _('relationship_templates must be of type "dict".')
        self.assertEqual(errormsg, err.__str__())

    def test_invalid_type_nodetemplates(self):
        tpl_snippet = '''
        node_templates:
           - some_node:
               type: tosca.nodes.Compute
        '''
        policies = (toscaparser.utils.yamlparser.simple_parse(tpl_snippet))
        custom_defs = self._get_custom_types()
        err = self.assertRaises(exception.TypeMismatchError,
                                lambda: TopologyTemplate(policies,
                                                         custom_defs))
        errormsg = _('node_templates must be of type "dict".')
        self.assertEqual(errormsg, err.__str__())

    def test_invalid_type_inputs(self):
        tpl_snippet = '''
        inputs:
           - some_input:
               type: integer
               value: 1
        '''
        policies = (toscaparser.utils.yamlparser.simple_parse(tpl_snippet))
        custom_defs = self._get_custom_types()
        err = self.assertRaises(exception.TypeMismatchError,
                                lambda: TopologyTemplate(policies,
                                                         custom_defs))
        errormsg = _('inputs must be of type "dict".')
        self.assertEqual(errormsg, err.__str__())
