# 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 uuid

from keystoneauth1.exceptions import http
from keystoneclient import exceptions
from keystoneclient.tests.functional import base
from keystoneclient.tests.functional.v3 import client_fixtures as fixtures


class ProjectsTestMixin(object):

    def check_project(self, project, project_ref=None):
        self.assertIsNotNone(project.id)
        self.assertIn('self', project.links)
        self.assertIn('/projects/' + project.id, project.links['self'])

        if project_ref:
            self.assertEqual(project_ref['name'], project.name)
            self.assertEqual(project_ref['domain'], project.domain_id)
            self.assertEqual(project_ref['enabled'], project.enabled)

            # There is no guarantee the attributes below are present in project
            if hasattr(project_ref, 'description'):
                self.assertEqual(project_ref['description'],
                                 project.description)
            if hasattr(project_ref, 'parent'):
                self.assertEqual(project_ref['parent'], project.parent)

        else:
            # Only check remaining mandatory attributes
            self.assertIsNotNone(project.name)
            self.assertIsNotNone(project.domain_id)
            self.assertIsNotNone(project.enabled)


class ProjectsTestCase(base.V3ClientTestCase, ProjectsTestMixin):

    def setUp(self):
        super(ProjectsTestCase, self).setUp()
        self.test_domain = fixtures.Domain(self.client)
        self.useFixture(self.test_domain)

        self.test_project = fixtures.Project(self.client, self.test_domain.id)
        self.useFixture(self.test_project)
        self.special_tag = '~`!@#$%^&*()-_+=<>.? \'"'

    def test_create_subproject(self):
        project_ref = {
            'name': fixtures.RESOURCE_NAME_PREFIX + uuid.uuid4().hex,
            'domain': self.test_domain.id,
            'enabled': True,
            'description': uuid.uuid4().hex,
            'parent': self.test_project.id}

        project = self.client.projects.create(**project_ref)
        self.addCleanup(self.client.projects.delete, project)
        self.check_project(project, project_ref)

    def test_create_project(self):
        project_ref = {
            'name': fixtures.RESOURCE_NAME_PREFIX + uuid.uuid4().hex,
            'domain': self.test_domain.id,
            'enabled': True,
            'description': uuid.uuid4().hex}

        project = self.client.projects.create(**project_ref)
        self.addCleanup(self.client.projects.delete, project)
        self.check_project(project, project_ref)

    def test_get_project(self):
        project_ret = self.client.projects.get(self.test_project.id)
        self.check_project(project_ret, self.test_project.ref)

    def test_get_project_invalid_params(self):
        self.assertRaises(exceptions.ValidationError,
                          self.client.projects.get,
                          self.test_project.id,
                          subtree_as_list=True, subtree_as_ids=True)
        self.assertRaises(exceptions.ValidationError,
                          self.client.projects.get,
                          self.test_project.id,
                          parents_as_list=True, parents_as_ids=True)

    def test_get_hierarchy_as_list(self):
        project = fixtures.Project(self.client, self.test_domain.id,
                                   parent=self.test_project.id)
        self.useFixture(project)

        child_project = fixtures.Project(self.client, self.test_domain.id,
                                         parent=project.id)
        self.useFixture(child_project)

        # Only parents and subprojects that the current user has role
        # assingments on are returned when asked for subtree_as_list and
        # parents_as_list.
        role = fixtures.Role(self.client)
        self.useFixture(role)
        self.client.roles.grant(role.id, user=self.user_id,
                                project=self.test_project.id)
        self.client.roles.grant(role.id, user=self.user_id,
                                project=project.id)
        self.client.roles.grant(role.id, user=self.user_id,
                                project=child_project.id)

        project_ret = self.client.projects.get(project.id,
                                               subtree_as_list=True,
                                               parents_as_list=True)

        self.check_project(project_ret, project.ref)
        self.assertCountEqual(
            [{'project': self.test_project.entity.to_dict()}],
            project_ret.parents)
        self.assertCountEqual(
            [{'project': child_project.entity.to_dict()}],
            project_ret.subtree)

    def test_get_hierarchy_as_ids(self):
        project = fixtures.Project(self.client, self.test_domain.id,
                                   parent=self.test_project.id)
        self.useFixture(project)

        child_project = fixtures.Project(self.client, self.test_domain.id,
                                         parent=project.id)
        self.useFixture(child_project)

        project_ret = self.client.projects.get(project.id,
                                               subtree_as_ids=True,
                                               parents_as_ids=True)

        self.assertCountEqual([self.test_project.id], project_ret.parents)
        self.assertCountEqual([child_project.id], project_ret.subtree)

    def test_list_projects(self):
        project_one = fixtures.Project(self.client, self.test_domain.id)
        self.useFixture(project_one)

        project_two = fixtures.Project(self.client, self.test_domain.id)
        self.useFixture(project_two)

        projects = self.client.projects.list()

        # All projects are valid
        for project in projects:
            self.check_project(project)

        self.assertIn(project_one.entity, projects)
        self.assertIn(project_two.entity, projects)

    def test_list_subprojects(self):
        parent_project = fixtures.Project(self.client, self.test_domain.id)
        self.useFixture(parent_project)

        child_project_one = fixtures.Project(self.client, self.test_domain.id,
                                             parent=parent_project.id)
        self.useFixture(child_project_one)

        child_project_two = fixtures.Project(self.client, self.test_domain.id,
                                             parent=parent_project.id)
        self.useFixture(child_project_two)

        projects = self.client.projects.list(parent=parent_project.id)

        # All projects are valid
        for project in projects:
            self.check_project(project)

        self.assertIn(child_project_one.entity, projects)
        self.assertIn(child_project_two.entity, projects)

        # Parent project should not be included in the result
        self.assertNotIn(parent_project.entity, projects)

    def test_update_project(self):
        project = fixtures.Project(self.client, self.test_domain.id)
        self.useFixture(project)

        new_name = fixtures.RESOURCE_NAME_PREFIX + uuid.uuid4().hex
        new_description = uuid.uuid4().hex
        project_ret = self.client.projects.update(project.id,
                                                  name=new_name,
                                                  enabled=False,
                                                  description=new_description)

        project.ref.update({'name': new_name, 'enabled': False,
                            'description': new_description})
        self.check_project(project_ret, project.ref)

    def test_update_project_domain_not_allowed(self):
        domain = fixtures.Domain(self.client)
        self.useFixture(domain)
        # Cannot update domain after project is created.
        self.assertRaises(http.BadRequest,
                          self.client.projects.update,
                          self.test_project.id, domain=domain.id)

    def test_delete_project(self):
        project = self.client.projects.create(name=uuid.uuid4().hex,
                                              domain=self.test_domain.id,
                                              enabled=True)

        self.client.projects.delete(project.id)
        self.assertRaises(http.NotFound,
                          self.client.projects.get,
                          project.id)

    def test_list_projects_with_tag_filters(self):
        project_one = fixtures.Project(
            self.client, self.test_domain.id,
            tags=['tag1'])
        project_two = fixtures.Project(
            self.client, self.test_domain.id,
            tags=['tag1', 'tag2'])
        project_three = fixtures.Project(
            self.client, self.test_domain.id,
            tags=['tag2', 'tag3'])

        self.useFixture(project_one)
        self.useFixture(project_two)
        self.useFixture(project_three)

        projects = self.client.projects.list(tags='tag1')
        project_ids = []
        for project in projects:
            project_ids.append(project.id)
        self.assertIn(project_one.id, project_ids)

        projects = self.client.projects.list(tags_any='tag1')
        project_ids = []
        for project in projects:
            project_ids.append(project.id)
        self.assertIn(project_one.id, project_ids)
        self.assertIn(project_two.id, project_ids)

        projects = self.client.projects.list(not_tags='tag1')
        project_ids = []
        for project in projects:
            project_ids.append(project.id)
        self.assertNotIn(project_one.id, project_ids)

        projects = self.client.projects.list(not_tags_any='tag1,tag2')
        project_ids = []
        for project in projects:
            project_ids.append(project.id)
        self.assertNotIn(project_one.id, project_ids)
        self.assertNotIn(project_two.id, project_ids)
        self.assertNotIn(project_three.id, project_ids)

        projects = self.client.projects.list(tags='tag1,tag2')
        project_ids = []
        for project in projects:
            project_ids.append(project.id)
        self.assertNotIn(project_one.id, project_ids)
        self.assertIn(project_two.id, project_ids)
        self.assertNotIn(project_three.id, project_ids)

    def test_add_tag(self):
        project = fixtures.Project(self.client, self.test_domain.id)
        self.useFixture(project)

        tags = self.client.projects.get(project.id).tags
        self.assertEqual([], tags)

        project.add_tag('tag1')
        tags = self.client.projects.get(project.id).tags
        self.assertEqual(['tag1'], tags)

        # verify there is an error when you try to add the same tag
        self.assertRaises(http.BadRequest,
                          project.add_tag,
                          'tag1')

    def test_update_tags(self):
        project = fixtures.Project(self.client, self.test_domain.id)
        self.useFixture(project)

        tags = self.client.projects.get(project.id).tags
        self.assertEqual([], tags)

        project.update_tags(['tag1', 'tag2', self.special_tag])
        tags = self.client.projects.get(project.id).tags
        self.assertIn('tag1', tags)
        self.assertIn('tag2', tags)
        self.assertIn(self.special_tag, tags)
        self.assertEqual(3, len(tags))

        project.update_tags([])
        tags = self.client.projects.get(project.id).tags
        self.assertEqual([], tags)

        # cannot have duplicate tags in update
        self.assertRaises(http.BadRequest,
                          project.update_tags,
                          ['tag1', 'tag1'])

    def test_delete_tag(self):
        project = fixtures.Project(
            self.client, self.test_domain.id,
            tags=['tag1', self.special_tag])
        self.useFixture(project)

        project.delete_tag('tag1')
        tags = self.client.projects.get(project.id).tags
        self.assertEqual([self.special_tag], tags)

        project.delete_tag(self.special_tag)
        tags = self.client.projects.get(project.id).tags
        self.assertEqual([], tags)

    def test_delete_all_tags(self):
        project_one = fixtures.Project(
            self.client, self.test_domain.id,
            tags=['tag1'])

        project_two = fixtures.Project(
            self.client, self.test_domain.id,
            tags=['tag1', 'tag2', self.special_tag])

        project_three = fixtures.Project(
            self.client, self.test_domain.id,
            tags=[])

        self.useFixture(project_one)
        self.useFixture(project_two)
        self.useFixture(project_three)

        result_one = project_one.delete_all_tags()
        tags_one = self.client.projects.get(project_one.id).tags
        tags_two = self.client.projects.get(project_two.id).tags
        self.assertEqual([], result_one)
        self.assertEqual([], tags_one)
        self.assertIn('tag1', tags_two)

        result_two = project_two.delete_all_tags()
        tags_two = self.client.projects.get(project_two.id).tags
        self.assertEqual([], result_two)
        self.assertEqual([], tags_two)

        result_three = project_three.delete_all_tags()
        tags_three = self.client.projects.get(project_three.id).tags
        self.assertEqual([], result_three)
        self.assertEqual([], tags_three)

    def test_list_tags(self):
        tags_one = ['tag1']
        project_one = fixtures.Project(
            self.client, self.test_domain.id,
            tags=tags_one)

        tags_two = ['tag1', 'tag2']
        project_two = fixtures.Project(
            self.client, self.test_domain.id,
            tags=tags_two)

        tags_three = []
        project_three = fixtures.Project(
            self.client, self.test_domain.id,
            tags=tags_three)

        self.useFixture(project_one)
        self.useFixture(project_two)
        self.useFixture(project_three)

        result_one = project_one.list_tags()
        result_two = project_two.list_tags()
        result_three = project_three.list_tags()

        for tag in tags_one:
            self.assertIn(tag, result_one)
        self.assertEqual(1, len(result_one))

        for tag in tags_two:
            self.assertIn(tag, result_two)
        self.assertEqual(2, len(result_two))

        for tag in tags_three:
            self.assertIn(tag, result_three)
        self.assertEqual(0, len(result_three))

    def test_check_tag(self):
        project = fixtures.Project(
            self.client, self.test_domain.id,
            tags=['tag1'])
        self.useFixture(project)

        tags = self.client.projects.get(project.id).tags
        self.assertEqual(['tag1'], tags)
        self.assertTrue(project.check_tag('tag1'))
        self.assertFalse(project.check_tag('tag2'))
        self.assertFalse(project.check_tag(self.special_tag))

    def test_add_invalid_tags(self):
        project_one = fixtures.Project(
            self.client, self.test_domain.id)

        self.useFixture(project_one)

        self.assertRaises(exceptions.BadRequest,
                          project_one.add_tag,
                          ',')
        self.assertRaises(exceptions.BadRequest,
                          project_one.add_tag,
                          '/')
        self.assertRaises(exceptions.BadRequest,
                          project_one.add_tag,
                          '')

    def test_update_invalid_tags(self):
        tags_comma = ['tag1', ',']
        tags_slash = ['tag1', '/']
        tags_blank = ['tag1', '']
        project_one = fixtures.Project(
            self.client, self.test_domain.id)

        self.useFixture(project_one)

        self.assertRaises(exceptions.BadRequest,
                          project_one.update_tags,
                          tags_comma)
        self.assertRaises(exceptions.BadRequest,
                          project_one.update_tags,
                          tags_slash)
        self.assertRaises(exceptions.BadRequest,
                          project_one.update_tags,
                          tags_blank)

    def test_create_project_invalid_tags(self):
        project_ref = {
            'name': fixtures.RESOURCE_NAME_PREFIX + uuid.uuid4().hex,
            'domain': self.test_domain.id,
            'enabled': True,
            'description': uuid.uuid4().hex,
            'tags': ','}

        self.assertRaises(exceptions.BadRequest,
                          self.client.projects.create,
                          **project_ref)

        project_ref = {
            'name': fixtures.RESOURCE_NAME_PREFIX + uuid.uuid4().hex,
            'domain': self.test_domain.id,
            'enabled': True,
            'description': uuid.uuid4().hex,
            'tags': '/'}

        self.assertRaises(exceptions.BadRequest,
                          self.client.projects.create,
                          **project_ref)

        project_ref = {
            'name': fixtures.RESOURCE_NAME_PREFIX + uuid.uuid4().hex,
            'domain': self.test_domain.id,
            'enabled': True,
            'description': uuid.uuid4().hex,
            'tags': ''}

        self.assertRaises(exceptions.BadRequest,
                          self.client.projects.create,
                          **project_ref)
