# Copyright 2016 OpenStack Foundation
# 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 io
import os
import tempfile

import mock
from oslo_serialization import jsonutils
import testtools

from glareclient.v1 import artifacts


class TestController(testtools.TestCase):

    def setUp(self):
        self.mock_resp = mock.MagicMock()
        self.mock_body = mock.MagicMock()
        self.mock_http_client = mock.MagicMock()
        for method in ('get', 'post', 'patch', 'delete'):
            method = getattr(self.mock_http_client, method)
            method.return_value = (self.mock_resp, self.mock_body)
        self.c = artifacts.Controller(self.mock_http_client, 'test_name')
        self.c._check_type_name = mock.Mock(return_value='checked_name')
        super(TestController, self).setUp()

    def test_create(self):
        body = self.c.create('name', version='0.1.2', type_name='ok')
        self.assertEqual(self.mock_body, body)
        self.mock_http_client.post.assert_called_once_with(
            '/artifacts/checked_name',
            json={'version': '0.1.2', 'name': 'name'})
        self.c._check_type_name.assert_called_once_with('ok')

    def test_update(self):
        remove_props = ['remove1', 'remove2']
        body = self.c.update('test-id', type_name='test_name',
                             remove_props=remove_props, update1=1, update2=2)
        self.assertEqual(self.mock_body, body)
        patch_kwargs = {
            'headers': {'Content-Type': 'application/json-patch+json'},
            'json': [
                {'path': '/remove1', 'value': None, 'op': 'replace'},
                {'path': '/remove2', 'value': None, 'op': 'replace'},
                {'path': '/update2', 'value': 2, 'op': 'add'},
                {'path': '/update1', 'value': 1, 'op': 'add'}
            ]
        }
        self.mock_http_client.patch.assert_called_once_with(
            '/artifacts/checked_name/test-id', **patch_kwargs)
        self.c._check_type_name.assert_called_once_with('test_name')

    def test_get(self):
        body = self.c.get('test-id', type_name='test_name')
        self.assertEqual(self.mock_body, body)
        self.mock_http_client.get.assert_called_once_with(
            '/artifacts/checked_name/test-id')
        self.c._check_type_name.assert_called_once_with('test_name')

    def test_list_legacy(self):
        self.mock_http_client.get.side_effect = [
            (None, {'checked_name': [10, 11, 12], "next": "next1"}),
            (None, {'checked_name': [13, 14, 15], "next": "next2"}),
            (None, {'checked_name': [16, 17, 18], "next": "next3"}),
            (None, {'checked_name': [19, 20, 21]}),
        ]
        data = list(self.c.list(type_name='test-type', limit=10, page_size=3))
        expected = [10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
        self.assertEqual(expected, data)
        expected_calls = [
            mock.call.get('/artifacts/checked_name?&limit=3'),
            mock.call.get('next1'),
            mock.call.get('next2'),
            mock.call.get('next3'),
        ]
        self.assertEqual(expected_calls, self.mock_http_client.mock_calls)

    def test_list(self):
        self.mock_http_client.get.side_effect = [
            (None, {'artifacts': [10, 11, 12], "next": "next1"}),
            (None, {'artifacts': [13, 14, 15], "next": "next2"}),
            (None, {'artifacts': [16, 17, 18], "next": "next3"}),
            (None, {'artifacts': [19, 20, 21]}),
        ]
        data = list(self.c.list(type_name='test-type', limit=10, page_size=3))
        expected = [10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
        self.assertEqual(expected, data)
        expected_calls = [
            mock.call.get('/artifacts/checked_name?&limit=3'),
            mock.call.get('next1'),
            mock.call.get('next2'),
            mock.call.get('next3'),
        ]
        self.assertEqual(expected_calls, self.mock_http_client.mock_calls)

    def test_activate(self):
        self.c.update = mock.Mock()
        self.assertEqual(self.c.update.return_value,
                         self.c.activate('test-id', type_name='test-type'))
        self.c.update.assert_called_once_with('test-id', 'test-type',
                                              status='active')

    def test_deactivate(self):
        self.c.update = mock.Mock()
        self.assertEqual(self.c.update.return_value,
                         self.c.deactivate('test-id', type_name='test-type'))
        self.c.update.assert_called_once_with('test-id', 'test-type',
                                              status='deactivated')

    def test_reactivate(self):
        self.c.update = mock.Mock()
        self.assertEqual(self.c.update.return_value,
                         self.c.reactivate('test-id', type_name='test-type'))
        self.c.update.assert_called_once_with('test-id', 'test-type',
                                              status='active')

    def test_delete(self):
        self.assertIsNone(self.c.delete('test-id', type_name='test-name'))
        self.mock_http_client.delete.assert_called_once_with(
            '/artifacts/checked_name/test-id')

    def test_upload_blob_str(self):
        self.c.upload_blob('test-id', 'blob-prop', 'data',
                           type_name='test-type',
                           content_type='application/test')
        self.mock_http_client.put.assert_called_once_with(
            '/artifacts/checked_name/test-id/blob-prop',
            data='data', headers={'Content-Length': '4',
                                  'Content-Type': 'application/test'})

    def test_upload_blob_file(self):
        tfd, path = tempfile.mkstemp()
        try:
            os.write(tfd, b'data')
            tfile = open(path, "rb")
            self.c.upload_blob('test-id', 'blob-prop', tfile,
                               type_name='test-type',
                               content_type='application/test')
            self.mock_http_client.put.assert_called_once_with(
                '/artifacts/checked_name/test-id/blob-prop',
                data=tfile, headers={'Content-Length': '4',
                                     'Content-Type': 'application/test'})
        finally:
            os.remove(path)

    def test_upload_blob_stream(self):
        data = io.BytesIO(b'data')
        self.c.upload_blob('test-id', 'blob-prop', data,
                           type_name='test-type',
                           content_type='application/test')
        self.mock_http_client.put.assert_called_once_with(
            '/artifacts/checked_name/test-id/blob-prop',
            data=data, headers={'Content-Type': 'application/test'})

    def test_get_type_list(self):
        schemas = {'schemas': {'a': {'version': 1}, 'b': {'version': 2}}}
        self.mock_http_client.get.return_value = (None, schemas)
        expected_types = [('a', 1), ('b', 2)]
        self.assertEqual(expected_types, self.c.get_type_list())

    def test_get_type_schema(self):
        test_schema = {'schemas': {'checked_name': 'test-schema'}}
        self.mock_http_client.get.return_value = (None, test_schema)
        self.assertEqual('test-schema',
                         self.c.get_type_schema(type_name='test-type'))
        self.mock_http_client.get.assert_called_once_with(
            '/schemas/checked_name')

    def test_add_external_location(self):
        art_id = '3a4560a1-e585-443e-9b39-553b46ec92a8'
        data = {
            'url': 'http://fake_url',
            'md5': '7CA772EE98D5CAF99F3674085D5E4124',
            'sha1': None,
            'sha256': None},
        resp = self.c.add_external_location(
            art_id, 'image',
            data=data,
            type_name='images')
        self.c.http_client.put.assert_called_once_with(
            '/artifacts/checked_name/'
            '3a4560a1-e585-443e-9b39-553b46ec92a8/image',
            data=jsonutils.dumps(data),
            headers={'Content-Type':
                     'application/vnd+openstack.glare-custom-location+json'})
        self.assertIsNone(resp)

    def test_remove_external_location(self):
        art_id = '3a4560a1-e585-443e-9b39-553b46ec92a8'
        resp = self.c.remove_external_location(
            art_id, 'image',
            type_name='images')
        self.c.http_client.delete.assert_called_once_with(
            '/artifacts/checked_name/'
            '3a4560a1-e585-443e-9b39-553b46ec92a8/image')
        self.assertIsNone(resp)

    def test_add_tag(self):
        art_id = '07a679d8-d0a8-45ff-8d6e-2f32f2097b7c'
        d = {'tags': ['a', 'b', 'c']}
        self.mock_body.__getitem__.side_effect = d.__getitem__
        data = self.c.add_tag(
            art_id, tag_value="123", type_name='images')
        self.c.http_client.get.assert_called_once_with(
            '/artifacts/checked_name/07a679d8-d0a8-45ff-8d6e-2f32f2097b7c')
        self.c.http_client.patch.assert_called_once_with(
            '/artifacts/checked_name/07a679d8-d0a8-45ff-8d6e-2f32f2097b7c',
            headers={'Content-Type': 'application/json-patch+json'},
            json=[{'path': '/tags',
                   'value': ['a', 'b', 'c', '123'],
                   'op': 'add'}])
        self.assertIsNotNone(data)

    def test_add_existing_tag(self):
        art_id = '07a679d8-d0a8-45ff-8d6e-2f32f2097b7c'
        d = {'tags': ['a', 'b', 'c']}
        self.mock_body.__getitem__.side_effect = d.__getitem__
        data = self.c.add_tag(
            art_id, tag_value="a", type_name='images')
        self.c.http_client.get.assert_called_once_with(
            '/artifacts/checked_name/07a679d8-d0a8-45ff-8d6e-2f32f2097b7c')
        self.assertEqual(0, self.c.http_client.patch.call_count)
        self.assertIsNotNone(data)

    def test_remove_tag(self):
        art_id = '07a679d8-d0a8-45ff-8d6e-2f32f2097b7c'
        d = {'tags': ['a', 'b', 'c']}
        self.mock_body.__getitem__.side_effect = d.__getitem__
        data = self.c.remove_tag(
            art_id, tag_value="a", type_name='images')
        self.c.http_client.get.assert_called_once_with(
            '/artifacts/checked_name/07a679d8-d0a8-45ff-8d6e-2f32f2097b7c')
        self.c.http_client.patch.assert_called_once_with(
            '/artifacts/checked_name/07a679d8-d0a8-45ff-8d6e-2f32f2097b7c',
            headers={'Content-Type': 'application/json-patch+json'},
            json=[{'path': '/tags',
                   'value': ['b', 'c'],
                   'op': 'add'}])
        self.assertIsNotNone(data)

    def test_remove_nonexisting_tag(self):
        art_id = '07a679d8-d0a8-45ff-8d6e-2f32f2097b7c'
        d = {'tags': ['a', 'b', 'c']}
        self.mock_body.__getitem__.side_effect = d.__getitem__
        data = self.c.remove_tag(
            art_id, tag_value="123", type_name='images')
        self.c.http_client.get.assert_called_once_with(
            '/artifacts/checked_name/07a679d8-d0a8-45ff-8d6e-2f32f2097b7c')
        self.assertEqual(0, self.c.http_client.patch.call_count)
        self.assertIsNotNone(data)
