# Copyright [2015] Hewlett-Packard Development Company, L.P.
#
# 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.
from unittest import mock
from unittest.mock import MagicMock
from unittest.mock import Mock
from unittest.mock import patch

from glanceclient import exc as glance_exceptions
import jsonschema

from trove.common import clients
from trove.common import exception
from trove.configuration import models as config_models
from trove.datastore import models
from trove.extensions.mgmt.datastores.service import DatastoreVersionController
from trove.tests.unittests import trove_testtools
from trove.tests.unittests.util import util


class TestDatastoreVersionController(trove_testtools.TestCase):
    @classmethod
    def setUpClass(cls):
        cls.ds_name = cls.random_name('datastore')
        cls.ds_version_number = '5.7.30'
        models.update_datastore(name=cls.ds_name, default_version=None)

        models.update_datastore_version(
            cls.ds_name, 'test_vr1', 'mysql', cls.random_uuid(), '', 'pkg-1',
            1)
        models.update_datastore_version(
            cls.ds_name, 'test_vr2', 'mysql', cls.random_uuid(), '', 'pkg-1',
            1, version=cls.ds_version_number)

        cls.ds = models.Datastore.load(cls.ds_name)
        cls.ds_version1 = models.DatastoreVersion.load(cls.ds, 'test_vr1')
        cls.ds_version2 = models.DatastoreVersion.load(
            cls.ds, 'test_vr2', version=cls.ds_version_number)
        cls.version_controller = DatastoreVersionController()

        super(TestDatastoreVersionController, cls).setUpClass()

    @classmethod
    def tearDownClass(cls):
        util.cleanup_db()

        super(TestDatastoreVersionController, cls).tearDownClass()

    def test_create_schema(self):
        image_id = self.random_uuid()
        ver_name = self.random_name('dsversion')
        body = {
            "version": {
                "datastore_name": self.ds_name,
                "name": ver_name,
                "datastore_manager": "mysql",
                "image": image_id,
                "image_tags": [],
                "registry_ext": "registry_ext",
                "repl_strategy": "repl_strategy",
                "active": True,
                "default": False
            }
        }

        schema = self.version_controller.get_schema('create', body)
        validator = jsonschema.Draft4Validator(schema)

        self.assertTrue(validator.is_valid(body))

    def test_create_schema_too_many_image_tags(self):
        ver_name = self.random_name('dsversion')
        body = {
            "version": {
                "datastore_name": self.ds_name,
                "name": ver_name,
                "datastore_manager": "mysql",
                "image_tags": ['a', 'b', 'c', 'd', 'e', 'f'],
                "registry_ext": "registry_ext",
                "repl_strategy": "repl_strategy",
                "active": True,
                "default": False
            }
        }

        schema = self.version_controller.get_schema('create', body)
        validator = jsonschema.Draft4Validator(schema)

        self.assertFalse(validator.is_valid(body))

    def test_create_schema_emptyname(self):
        image_id = self.random_uuid()
        body = {
            "version": {
                "datastore_name": self.ds_name,
                "name": " ",
                "datastore_manager": "mysql",
                "image": image_id,
                "image_tags": [],
                "registry_ext": "registry_ext",
                "repl_strategy": "repl_strategy",
                "active": True,
                "default": False
            }
        }
        schema = self.version_controller.get_schema('create', body)
        validator = jsonschema.Draft4Validator(schema)

        self.assertFalse(validator.is_valid(body))

        errors = sorted(validator.iter_errors(body), key=lambda e: e.path)
        self.assertEqual(1, len(errors))
        self.assertEqual("' ' does not match '^.*[0-9a-zA-Z]+.*$'",
                         errors[0].message)

    @patch.object(clients, 'create_glance_client')
    def test_create(self, mock_glance_client):
        image_id = self.random_uuid()
        ver_name = self.random_name('dsversion')
        body = {
            "version": {
                "datastore_name": self.ds_name,
                "name": ver_name,
                "datastore_manager": "mysql",
                "image": image_id,
                "image_tags": [],
                "registry_ext": "registry_ext",
                "repl_strategy": "repl_strategy",
                "packages": "test-pkg",
                "active": True,
                "default": True
            }
        }
        output = self.version_controller.create(MagicMock(), body, mock.ANY)
        self.assertEqual(202, output.status)

        new_ver = models.DatastoreVersion.load(self.ds, ver_name)
        self.assertEqual(image_id, new_ver.image_id)
        self.assertEqual(ver_name, new_ver.version)

    @patch.object(clients, 'create_glance_client')
    def test_create_same_version_number(self, mock_glance_client):
        image_id = self.random_uuid()
        ver_name = self.random_name('dsversion')
        body = {
            "version": {
                "datastore_name": self.ds_name,
                "name": ver_name,
                "datastore_manager": "mysql",
                "image": image_id,
                "image_tags": [],
                "registry_ext": "registry_ext",
                "repl_strategy": "repl_strategy",
                "packages": "",
                "active": True,
                "default": False,
                "version": self.ds_version_number
            }
        }
        output = self.version_controller.create(MagicMock(), body, mock.ANY)
        self.assertEqual(202, output.status)

        new_ver = models.DatastoreVersion.load(self.ds, ver_name,
                                               version=self.ds_version_number)
        self.assertEqual(image_id, new_ver.image_id)
        self.assertEqual(ver_name, new_ver.name)
        self.assertEqual(self.ds_version_number, new_ver.version)
        self.assertNotEqual(self.ds_version2.id, new_ver.id)

    @patch.object(clients, 'create_glance_client')
    def test_create_by_image_tags(self, mock_create_client):
        ver_name = self.random_name('dsversion')
        body = {
            "version": {
                "datastore_name": self.ds_name,
                "name": ver_name,
                "datastore_manager": "mysql",
                "image_tags": ["trove", "mysql"],
                "registry_ext": "registry_ext",
                "repl_strategy": "repl_strategy",
                "active": True,
                "default": True
            }
        }
        mock_client = MagicMock()
        mock_client.images.list.return_value = [{"id": self.random_uuid()}]
        mock_create_client.return_value = mock_client

        output = self.version_controller.create(MagicMock(), body, mock.ANY)
        self.assertEqual(202, output.status)

        mock_client.images.list.assert_called_once_with(
            filters={'tag': ["trove", "mysql"], 'status': 'active'},
            sort='created_at:desc',
            limit=1
        )

        new_ver = models.DatastoreVersion.load(self.ds, ver_name)
        self.assertIsNone(new_ver.image_id)
        self.assertEqual('trove,mysql', new_ver.image_tags)

    @patch.object(clients, 'create_glance_client')
    def test_create_exist(self, mock_glance_client):
        image_id = self.random_uuid()
        ver_name = 'test_vr1'
        body = {
            "version": {
                "datastore_name": self.ds_name,
                "name": ver_name,
                "datastore_manager": "mysql",
                "image": image_id,
                "image_tags": [],
                "registry_ext": "registry_ext",
                "repl_strategy": "repl_strategy",
                "packages": "test-pkg",
                "active": True,
                "default": True
            }
        }
        self.assertRaises(
            exception.DatastoreVersionAlreadyExists,
            self.version_controller.create, MagicMock(), body, mock.ANY)

    def test_create_no_image(self):
        ver_name = self.random_name('dsversion')
        body = {
            "version": {
                "datastore_name": self.ds_name,
                "name": ver_name,
                "datastore_manager": "mysql",
                "active": True,
                "default": False
            }
        }
        self.assertRaises(
            exception.BadRequest,
            self.version_controller.create, MagicMock(), body, mock.ANY)

    @patch.object(clients, 'create_glance_client')
    def test_create_image_notfound(self, mock_create_client):
        image_id = self.random_uuid()
        ver_name = self.random_name('dsversion')
        body = {
            "version": {
                "datastore_name": self.ds_name,
                "name": ver_name,
                "datastore_manager": "mysql",
                "image": image_id,
                "active": True,
                "default": False
            }
        }
        mock_client = Mock()
        mock_client.images.get.side_effect = [glance_exceptions.HTTPNotFound()]
        mock_create_client.return_value = mock_client

        self.assertRaises(
            exception.ImageNotFound,
            self.version_controller.create, MagicMock(), body, mock.ANY)

    def test_update_name(self):
        new_name = self.random_name('ds-version-name')
        body = {
            "name": new_name
        }

        orig_ver = models.DatastoreVersion.load(self.ds, self.ds_version1.id)

        output = self.version_controller.edit(MagicMock(), body, mock.ANY,
                                              self.ds_version1.id)
        self.assertEqual(202, output.status)

        updated_ver = models.DatastoreVersion.load(self.ds,
                                                   self.ds_version1.id)

        self.assertEqual(new_name, updated_ver.name)
        self.assertEqual(orig_ver.image_id, updated_ver.image_id)
        self.assertEqual(orig_ver.image_tags, updated_ver.image_tags)
        self.assertEqual(orig_ver.version, updated_ver.version)

    @patch.object(clients, 'create_glance_client')
    def test_update_image(self, mock_create_client):
        new_image = self.random_uuid()
        body = {
            "image": new_image
        }

        output = self.version_controller.edit(MagicMock(), body, mock.ANY,
                                              self.ds_version1.id)
        self.assertEqual(202, output.status)

        updated_ver = models.DatastoreVersion.load(self.ds,
                                                   self.ds_version1.id)
        self.assertEqual(new_image, updated_ver.image_id)

    @patch.object(clients, 'create_glance_client')
    def test_update_image_tags(self, mock_create_client):
        name = self.random_name('dsversion')
        models.update_datastore_version(
            self.ds_name, name, 'mysql', self.random_uuid(), '', '', 1)
        ver = models.DatastoreVersion.load(self.ds, name)

        mock_client = MagicMock()
        mock_client.images.list.return_value = [{"id": self.random_uuid()}]
        mock_create_client.return_value = mock_client

        body = {
            "image_tags": ['trove', 'mysql']
        }

        output = self.version_controller.edit(MagicMock(), body, mock.ANY,
                                              ver.id)
        self.assertEqual(202, output.status)

        updated_ver = models.DatastoreVersion.load(self.ds, ver.id)
        self.assertEqual("", updated_ver.image_id)
        self.assertEqual("trove,mysql", updated_ver.image_tags)

    def test_delete(self):
        name = self.random_name('dsversion')
        models.update_datastore_version(
            self.ds_name, name, 'mysql', self.random_uuid(), '', '', 1)
        ver = models.DatastoreVersion.load(self.ds, name)

        # Add config param for the datastore version. Should be automatically
        # removed.
        param_name = self.random_name('param')
        config_models.create_or_update_datastore_configuration_parameter(
            param_name, ver.id, False, 'string', None, None)

        output = self.version_controller.delete(MagicMock(),
                                                mock.ANY,
                                                ver.id)
        self.assertEqual(202, output.status)

        self.assertRaises(
            exception.DatastoreVersionNotFound,
            models.DatastoreVersion.load_by_uuid, ver.id)

        config_params_cls = config_models.DatastoreConfigurationParameters
        self.assertRaises(
            exception.NotFound,
            config_params_cls.load_parameter_by_name,
            ver.id, param_name)

    def test_index(self):
        output = self.version_controller.index(MagicMock(), mock.ANY)
        self.assertEqual(200, output.status)

        data = output.data(None)
        self.assertGreater(len(data['versions']), 0)

    def test_show(self):
        output = self.version_controller.show(
            MagicMock(), mock.ANY, self.ds_version2.id)
        self.assertEqual(200, output.status)
        self.assertEqual(self.ds_version2.id,
                         output._data['version']['id'])
        self.assertEqual(self.ds_version2.name,
                         output._data['version']['name'])
        self.assertEqual(self.ds_version2.datastore_id,
                         output._data['version']['datastore_id'])
        self.assertEqual(self.ds_version2.datastore_name,
                         output._data['version']['datastore_name'])
        self.assertEqual(self.ds_version2.manager,
                         output._data['version']['datastore_manager'])
        self.assertEqual(self.ds_version2.image_id,
                         output._data['version']['image'])
        self.assertEqual(self.ds_version2.packages.split(','),
                         output._data['version']['packages'])
        self.assertEqual(self.ds_version2.active,
                         output._data['version']['active'])
        self.assertEqual(self.ds_version2.version,
                         output._data['version']['version'])

    def test_show_image_tags(self):
        ver_name = self.random_name('dsversion')
        tags = ['trove', 'mysql']
        models.update_datastore_version(self.ds_name, ver_name, 'mysql', '',
                                        tags, '', 1)
        ver = models.DatastoreVersion.load(self.ds, ver_name)

        output = self.version_controller.show(
            MagicMock(), mock.ANY, ver.id)
        self.assertEqual(200, output.status)

        data = output.data(None)
        self.assertEqual(tags, data['version']['image_tags'])
