# 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. 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 copy
from unittest import mock

from nova import test
from nova.tests import json_ref


class TestJsonRef(test.NoDBTestCase):

    def test_update_dict_recursively(self):
        input = {'foo': 1,
                 'bar': 13,
                 'a list': [],
                 'nesting': {
                     'baz': 42,
                     'foobar': 121
                 }}

        d = copy.deepcopy(input)
        json_ref._update_dict_recursively(d, {})
        self.assertDictEqual(input, d)

        d = copy.deepcopy(input)
        json_ref._update_dict_recursively(d, {'foo': 111,
                                              'new_key': 1,
                                              'nesting': {
                                                  'baz': 142,
                                                  'new_nested': 1
                                              }})
        expected = copy.deepcopy(input)
        expected['foo'] = 111
        expected['new_key'] = 1
        expected['nesting']['baz'] = 142
        expected['nesting']['new_nested'] = 1
        self.assertDictEqual(expected, d)

        d = copy.deepcopy(input)
        json_ref._update_dict_recursively(d, {'nesting': 1})
        expected = copy.deepcopy(input)
        expected['nesting'] = 1
        self.assertDictEqual(expected, d)
        d = copy.deepcopy(input)

    @mock.patch('oslo_serialization.jsonutils.load')
    @mock.patch('builtins.open', new_callable=mock.mock_open())
    def test_resolve_ref(self, mock_open, mock_json_load):
        mock_json_load.return_value = {'baz': 13}

        actual = json_ref.resolve_refs(
            {'foo': 1,
             'bar': {'$ref': 'another.json#'}},
            'some/base/path/')

        self.assertDictEqual({'foo': 1,
                              'bar': {'baz': 13}},
                             actual)
        mock_open.assert_called_once_with('some/base/path/another.json', 'r+b')

    @mock.patch('oslo_serialization.jsonutils.load')
    @mock.patch('builtins.open', new_callable=mock.mock_open())
    def test_resolve_ref_recursively(self, mock_open, mock_json_load):
        mock_json_load.side_effect = [
            # this is the content of direct_ref.json
            {'baz': 13,
             'nesting': {'$ref': 'subdir/nested_ref.json#'}},
            # this is the content of subdir/nested_ref.json
            {'a deep key': 'happiness'}]

        actual = json_ref.resolve_refs(
            {'foo': 1,
             'bar': {'$ref': 'direct_ref.json#'}},
            'some/base/path/')

        self.assertDictEqual({'foo': 1,
                              'bar': {'baz': 13,
                                      'nesting':
                                          {'a deep key': 'happiness'}}},
                             actual)
        mock_open.assert_any_call('some/base/path/direct_ref.json', 'r+b')
        mock_open.assert_any_call('some/base/path/subdir/nested_ref.json',
                                  'r+b')

    @mock.patch('oslo_serialization.jsonutils.load')
    @mock.patch('builtins.open', new_callable=mock.mock_open())
    def test_resolve_ref_with_override(self, mock_open, mock_json_load):
        mock_json_load.return_value = {'baz': 13,
                                       'boo': 42}

        actual = json_ref.resolve_refs(
            {'foo': 1,
             'bar': {'$ref': 'another.json#',
                     'boo': 0}},
            'some/base/path/')

        self.assertDictEqual({'foo': 1,
                              'bar': {'baz': 13,
                                      'boo': 0}},
                             actual)
        mock_open.assert_called_once_with('some/base/path/another.json', 'r+b')

    @mock.patch('oslo_serialization.jsonutils.load')
    @mock.patch('builtins.open', new_callable=mock.mock_open())
    def test_resolve_ref_with_nested_override(self, mock_open, mock_json_load):
        mock_json_load.return_value = {'baz': 13,
                                       'boo': {'a': 1,
                                               'b': 2}}

        actual = json_ref.resolve_refs(
            {'foo': 1,
             'bar': {'$ref': 'another.json#',
                     'boo': {'b': 3,
                             'c': 13}}},
            'some/base/path/')

        self.assertDictEqual({'foo': 1,
                              'bar': {'baz': 13,
                                      'boo': {'a': 1,
                                              'b': 3,
                                              'c': 13}}},
                             actual)
        mock_open.assert_called_once_with('some/base/path/another.json', 'r+b')

    @mock.patch('oslo_serialization.jsonutils.load')
    @mock.patch('builtins.open', new_callable=mock.mock_open())
    def test_resolve_ref_with_override_having_refs(self, mock_open,
                                                   mock_json_load):
        mock_json_load.side_effect = [
            {'baz': 13,
             'boo': 42},
            {'something': 0}]

        actual = json_ref.resolve_refs(
            {'foo': 1,
             'bar': {'$ref': 'another.json#',
                     'boo': {'$ref': 'override_ref.json#'}}},
            'some/base/path/')

        self.assertDictEqual({'foo': 1,
                              'bar': {'baz': 13,
                                      'boo': {'something': 0}}},
                             actual)
        self.assertEqual(2, mock_open.call_count)
        # any_order=True is needed as context manager calls also done on open
        mock_open.assert_has_calls(
            [mock.call('some/base/path/another.json', 'r+b'),
             mock.call('some/base/path/override_ref.json', 'r+b')],
            any_order=True)

    def test_ref_with_json_path_not_supported(self):

        self.assertRaises(
            NotImplementedError, json_ref.resolve_refs,
            {'foo': 1,
             'bar': {'$ref': 'another.json#/key-in-another',
                     'boo': {'b': 3,
                             'c': 13}}},
            'some/base/path/')
