# Copyright 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the 'License'). You
# may not use this file except in compliance with the License. A copy of
# the License is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the 'license' file accompanying this file. This file 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.

from boto3.exceptions import ResourceLoadException
from boto3.resources.base import ResourceMeta, ServiceResource
from boto3.resources.model import Request
from boto3.resources.params import create_request_parameters, \
                                   build_param_structure
from tests import BaseTestCase, mock

class TestServiceActionParams(BaseTestCase):
    def test_service_action_params_identifier(self):
        request_model = Request({
            'operation': 'GetFrobs',
            'params': [
                {
                    'target': 'WarehouseUrl',
                    'source': 'identifier',
                    'name': 'Url'
                }
            ]
        })

        parent = mock.Mock()
        parent.url = 'w-url'

        params = create_request_parameters(parent, request_model)

        self.assertEqual(params['WarehouseUrl'], 'w-url',
            'Parameter not set from resource identifier')

    def test_service_action_params_data_member(self):
        request_model = Request({
            'operation': 'GetFrobs',
            'params': [
                {
                    'target': 'WarehouseUrl',
                    'source': 'data',
                    'path': 'SomeMember'
                }
            ]
        })

        parent = mock.Mock()
        parent.meta = ResourceMeta('test', data={
            'SomeMember': 'w-url'
        })

        params = create_request_parameters(parent, request_model)

        self.assertEqual(params['WarehouseUrl'], 'w-url',
            'Parameter not set from resource property')

    def test_service_action_params_data_member_missing(self):
        request_model = Request({
            'operation': 'GetFrobs',
            'params': [
                {
                    'target': 'WarehouseUrl',
                    'source': 'data',
                    'path': 'SomeMember'
                }
            ]
        })

        parent = mock.Mock()

        def load_data():
            parent.meta.data = {
                'SomeMember': 'w-url'
            }

        parent.load.side_effect = load_data
        parent.meta = ResourceMeta('test')

        params = create_request_parameters(parent, request_model)

        parent.load.assert_called_with()
        self.assertEqual(params['WarehouseUrl'], 'w-url',
            'Parameter not set from resource property')

    def test_service_action_params_data_member_missing_no_load(self):
        request_model = Request({
            'operation': 'GetFrobs',
            'params': [
                {
                    'target': 'WarehouseUrl',
                    'source': 'data',
                    'path': 'SomeMember'
                }
            ]
        })

        # This mock has no ``load`` method.
        parent = mock.Mock(spec=ServiceResource)
        parent.meta = ResourceMeta('test', data=None)

        with self.assertRaises(ResourceLoadException):
            params = create_request_parameters(parent, request_model)

    def test_service_action_params_constants(self):
        request_model = Request({
            'operation': 'GetFrobs',
            'params': [
                {
                    'target': 'Param1',
                    'source': 'string',
                    'value': 'param1'
                },
                {
                    'target': 'Param2',
                    'source': 'integer',
                    'value': 123
                },
                {
                    'target': 'Param3',
                    'source': 'boolean',
                    'value': True
                }
            ]
        })

        params = create_request_parameters(None, request_model)

        self.assertEqual(params['Param1'], 'param1',
            'Parameter not set from string constant')
        self.assertEqual(params['Param2'], 123,
            'Parameter not set from integer constant')
        self.assertEqual(params['Param3'], True,
            'Parameter not set from boolean constant')

    def test_service_action_params_input(self):
        request_model = Request({
            'operation': 'GetFrobs',
            'params': [
                {'target': 'Param1', 'source': 'input'}
            ]
        })

        params = create_request_parameters(None, request_model)
        self.assertEqual(params, {})

        params['param1'] = 'myinput'
        params = create_request_parameters(None, request_model, params=params)
        self.assertEqual(params, {'param1': 'myinput'})

    def test_service_action_params_invalid(self):
        request_model = Request({
            'operation': 'GetFrobs',
            'params': [
                {
                    'target': 'Param1',
                    'source': 'invalid'
                }
            ]
        })

        with self.assertRaises(NotImplementedError):
            create_request_parameters(None, request_model)

    def test_service_action_params_list(self):
        request_model = Request({
            'operation': 'GetFrobs',
            'params': [
                {
                    'target': 'WarehouseUrls[0]',
                    'source': 'string',
                    'value': 'w-url'
                }
            ]
        })

        params = create_request_parameters(None, request_model)

        self.assertIsInstance(params['WarehouseUrls'], list,
            'Parameter did not create a list')
        self.assertEqual(len(params['WarehouseUrls']), 1,
            'Parameter list should only have a single item')
        self.assertIn('w-url', params['WarehouseUrls'],
            'Parameter not in expected list')

    def test_service_action_params_reuse(self):
        request_model = Request({
            'operation': 'GetFrobs',
            'params': [
                {
                    'target': 'Delete.Objects[].Key',
                    'source': 'data',
                    'path': 'Key'
                }
            ]
        })

        item1 = mock.Mock()
        item1.meta = ResourceMeta('test', data={
            'Key': 'item1'
        })

        item2 = mock.Mock()
        item2.meta = ResourceMeta('test', data={
            'Key': 'item2'
        })

        # Here we create params and then re-use it to build up a more
        # complex structure over multiple calls.
        params = create_request_parameters(item1, request_model)
        create_request_parameters(item2, request_model, params=params)

        self.assertEqual(params, {
            'Delete': {
                'Objects': [
                    {'Key': 'item1'},
                    {'Key': 'item2'}
                ]
            }
        })


class TestStructBuilder(BaseTestCase):
    def test_simple_value(self):
        params = {}
        build_param_structure(params, 'foo', 'bar')
        self.assertEqual(params['foo'], 'bar')

    def test_nested_dict(self):
        params = {}
        build_param_structure(params, 'foo.bar.baz', 123)
        self.assertEqual(params['foo']['bar']['baz'], 123)

    def test_nested_list(self):
        params = {}
        build_param_structure(params, 'foo.bar[0]', 'test')
        self.assertEqual(params['foo']['bar'][0], 'test')

    def test_strange_offset(self):
        params = {}
        build_param_structure(params, 'foo[2]', 'test')
        self.assertEqual(params['foo'], [{}, {}, 'test'])

    def test_nested_list_dict(self):
        params = {}
        build_param_structure(params, 'foo.bar[0].baz', 123)
        self.assertEqual(params['foo']['bar'][0]['baz'], 123)

    def test_modify_existing(self):
        params = {
            'foo': [
                {'key': 'abc'}
            ]
        }
        build_param_structure(params, 'foo[0].secret', 123)
        self.assertEqual(params['foo'][0]['key'], 'abc')
        self.assertEqual(params['foo'][0]['secret'], 123)

    def test_append_no_index(self):
        params = {}
        build_param_structure(params, 'foo[]', 123)
        self.assertEqual(params['foo'], [123])

        build_param_structure(params, 'foo[]', 456)
        self.assertEqual(params['foo'], [123, 456])

    def test_provided_index_with_wildcard(self):
        params = {}
        index = 0
        build_param_structure(params, 'foo[*].bar', 123, index)
        build_param_structure(params, 'foo[*].baz', 456, index)
        self.assertEqual(params['foo'][index], {'bar': 123, 'baz': 456})

        index = 1
        build_param_structure(params, 'foo[*].bar', 789, index)
        build_param_structure(params, 'foo[*].baz', 123, index)
        self.assertEqual(params['foo'], [
            {'bar': 123, 'baz': 456},
            {'bar': 789, 'baz': 123}
        ])
