# 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 ast
import collections

from neutron import context as n_ctx
from oslo_config import cfg
from oslo_serialization import jsonutils

from gbpservice.neutron.services.servicechain.plugins.msc import context
from gbpservice.neutron.tests.unit.db.grouppolicy import (
    test_servicechain_db as test_servicechain_db)

cfg.CONF.import_opt(
    'servicechain_drivers',
    'gbpservice.neutron.services.servicechain.plugins.msc.config',
    group='servicechain')
SC_PLUGIN_KLASS = (
    "gbpservice.neutron.services.servicechain.plugins.msc.plugin."
    "ServiceChainPlugin")


class ServiceChainPluginTestCase(test_servicechain_db.ServiceChainDbTestCase):

    def setUp(self, core_plugin=None, sc_plugin=None, gp_plugin=None):
        if not sc_plugin:
            sc_plugin = SC_PLUGIN_KLASS
        super(ServiceChainPluginTestCase, self).setUp(core_plugin=core_plugin,
                                                      sc_plugin=sc_plugin,
                                                      gp_plugin=gp_plugin)


class TestGroupPolicyPluginGroupResources(
        ServiceChainPluginTestCase,
        test_servicechain_db.TestServiceChainResources):

    def test_spec_shared(self):
        # Shared spec can only point shared nodes
        node = self._create_profiled_servicechain_node(
            'LOADBALANCER', shared=True, shared_profile=True,
            profile_tenant_id='admin', tenant_id='admin')['servicechain_node']
        self.create_servicechain_spec(nodes=[node['id']], shared=True,
                                      expected_res_status=201)
        self.create_servicechain_spec(nodes=[node['id']], shared=False,
                                      tenant_id='admin',
                                      expected_res_status=201)

        node = self._create_profiled_servicechain_node(
            'LOADBALANCER', shared=False, profile_tenant_id='nonadmin',
            tenant_id='nonadmin')['servicechain_node']
        self.create_servicechain_spec(nodes=[node['id']], shared=True,
                                      expected_res_status=404)
        self.create_servicechain_spec(nodes=[node['id']], shared=True,
                                      tenant_id='nonadmin',
                                      expected_res_status=400)
        self.create_servicechain_spec(nodes=[node['id']], shared=False,
                                      tenant_id='nonadmin',
                                      expected_res_status=201)

    def test_node_shared(self):
        # Shared node can only point shared profile
        prof = self.create_service_profile(
            service_type='LOADBALANCER', shared=True,
            tenant_id='admin')['service_profile']
        to_update = self.create_servicechain_node(
            service_profile_id=prof['id'], shared=True,
            expected_res_status=201)['servicechain_node']
        self.create_servicechain_node(
            service_profile_id=prof['id'], shared=False, tenant_id='admin',
            expected_res_status=201)

        prof = self.create_service_profile(
            service_type='LOADBALANCER', shared=False,
            tenant_id='admin')['service_profile']
        self.create_servicechain_node(
            service_profile_id=prof['id'], shared=True,
            expected_res_status=404)
        self.create_servicechain_node(
            service_profile_id=prof['id'], shared=True,
            tenant_id='admin', expected_res_status=400)
        self.create_servicechain_node(
            service_profile_id=prof['id'], shared=False,
            tenant_id='admin', expected_res_status=201)

        self.create_servicechain_spec(nodes=[to_update['id']], shared=True,
                                      tenant_id='nonadmin',
                                      expected_res_status=201)

        data = {'servicechain_node': {'shared': False}}
        req = self.new_update_request('servicechain_nodes', data,
                                      to_update['id'])
        res = req.get_response(self.ext_api)
        self.assertEqual(400, res.status_int)
        res = self.deserialize(self.fmt, res)
        self.assertEqual('InvalidSharedAttributeUpdate',
                         res['NeutronError']['type'])

    def test_profile_shared(self):
        prof = self.create_service_profile(
            service_type='LOADBALANCER', shared=True,
            tenant_id='admin')['service_profile']
        self.create_servicechain_node(
            service_profile_id=prof['id'], shared=True,
            expected_res_status=201)

        data = {'service_profile': {'shared': False}}
        req = self.new_update_request('service_profiles', data,
                                      prof['id'])
        res = req.get_response(self.ext_api)
        self.assertEqual(400, res.status_int)
        res = self.deserialize(self.fmt, res)
        self.assertEqual('InvalidSharedAttributeUpdate',
                         res['NeutronError']['type'])

        prof = self.create_service_profile(
            service_type='LOADBALANCER', shared=False)['service_profile']
        self.create_servicechain_node(
            service_profile_id=prof['id'], shared=False,
            expected_res_status=201)

        data = {'service_profile': {'shared': True}}
        req = self.new_update_request('service_profiles', data,
                                      prof['id'])
        res = req.get_response(self.ext_api)
        self.assertEqual(200, res.status_int)
        res = self.deserialize(self.fmt, res)
        self.assertTrue(res['service_profile']['shared'])

    def test_node_context_profile(self):

        # Current node with profile
        plugin_context = n_ctx.get_admin_context()
        plugin_context.is_admin = plugin_context.is_advsvc = False
        plugin_context.tenant_id = 'test-tenant'

        prof = self.create_service_profile(
            service_type='LOADBALANCER')['service_profile']
        current = self.create_servicechain_node(
            service_profile_id=prof['id'],
            expected_res_status=201)['servicechain_node']
        ctx = context.ServiceChainNodeContext(self.plugin, plugin_context,
                                              current)

        self.assertIsNone(ctx.original)
        self.assertIsNone(ctx.original_profile)
        self.assertEqual(ctx.current['id'], current['id'])
        self.assertEqual(ctx.current_profile['id'], prof['id'])

        # Original node with profile

        prof2 = self.create_service_profile(
            service_type='LOADBALANCER')['service_profile']
        original = self.create_servicechain_node(
            service_profile_id=prof2['id'],
            expected_res_status=201)['servicechain_node']
        ctx = context.ServiceChainNodeContext(self.plugin, plugin_context,
                                              current, original)

        self.assertEqual(ctx.original['id'], original['id'])
        self.assertEqual(ctx.original_profile['id'], prof2['id'])
        self.assertEqual(ctx.current['id'], current['id'])
        self.assertEqual(ctx.current_profile['id'], prof['id'])

    def test_node_context_no_profile(self):

        plugin_context = n_ctx.get_admin_context()
        plugin_context.is_admin = plugin_context.is_advsvc = False
        plugin_context.tenant_id = 'test_tenant'

        current = self.create_servicechain_node(
            service_type='TEST',
            expected_res_status=201)['servicechain_node']
        ctx = context.ServiceChainNodeContext(self.plugin, plugin_context,
                                              current)

        self.assertIsNone(ctx.original)
        self.assertIsNone(ctx.original_profile)
        self.assertEqual(ctx.current['id'], current['id'])
        self.assertIsNone(ctx.current_profile)

        original = self.create_servicechain_node(
            service_type='TEST',
            expected_res_status=201)['servicechain_node']
        ctx = context.ServiceChainNodeContext(self.plugin, plugin_context,
                                              current, original)

        self.assertEqual(ctx.original['id'], original['id'])
        self.assertIsNone(ctx.original_profile)
        self.assertEqual(ctx.current['id'], current['id'])
        self.assertIsNone(ctx.current_profile)

    def test_spec_parameters(self):
        params_node_1 = ['p1', 'p2', 'p3']
        params_node_2 = ['p4', 'p5', 'p6']
        params_node_3 = ['p7', 'p8', 'p9']

        def params_dict(params):
            return jsonutils.dumps({'Parameters':
                                    dict((x, {}) for x in params)})

        prof = self.create_service_profile(
            service_type='LOADBALANCER', shared=True,
            tenant_id='admin')['service_profile']

        # Create 2 nodes with different parameters
        node1 = self.create_servicechain_node(
            service_profile_id=prof['id'], shared=True,
            config=params_dict(params_node_1),
            expected_res_status=201)['servicechain_node']
        node2 = self.create_servicechain_node(
            service_profile_id=prof['id'], shared=True,
            config=params_dict(params_node_2),
            expected_res_status=201)['servicechain_node']

        # Create SC spec with the nodes assigned
        spec = self.create_servicechain_spec(
            nodes=[node1['id'], node2['id']], shared=True,
            expected_res_status=201)['servicechain_spec']

        # Verify param names correspondence
        self.assertEqual(
            collections.Counter(params_node_1 + params_node_2),
            collections.Counter(ast.literal_eval(spec['config_param_names'])))

        # Update the spec removing one node
        self.update_servicechain_spec(spec['id'], nodes=[node1['id']],
                                      expected_res_status=200)

        spec = self.show_servicechain_spec(spec['id'])['servicechain_spec']
        # Verify param names correspondence
        self.assertEqual(
            collections.Counter(params_node_1),
            collections.Counter(ast.literal_eval(spec['config_param_names'])))

        # Update the spec without modifying the node list
        self.update_servicechain_spec(spec['id'],
                                      name='new_name',
                                      expected_res_status=200)

        spec = self.show_servicechain_spec(spec['id'])['servicechain_spec']
        # Verify param names correspondence
        self.assertEqual(
            collections.Counter(params_node_1),
            collections.Counter(ast.literal_eval(spec['config_param_names'])))

        # Update a node with new config params
        self.update_servicechain_node(node1['id'],
                                      config=params_dict(params_node_3),
                                      expected_res_status=200)

        spec = self.show_servicechain_spec(spec['id'])['servicechain_spec']
        # Verify param names correspondence
        self.assertEqual(
            collections.Counter(params_node_3),
            collections.Counter(ast.literal_eval(spec['config_param_names'])))
