# -*- coding: utf-8 -*-
import unittest

from mongoengine import *
from mongoengine.pymongo_support import list_collection_names

from mongoengine.queryset import NULLIFY, PULL
from mongoengine.connection import get_db

__all__ = ("ClassMethodsTest", )


class ClassMethodsTest(unittest.TestCase):

    def setUp(self):
        connect(db='mongoenginetest', is_mock=True)
        self.db = get_db()

        class Person(Document):
            name = StringField()
            age = IntField()

            non_field = True

            meta = {"allow_inheritance": True}

        self.Person = Person

    def tearDown(self):
        for collection in list_collection_names(self.db):
            self.db.drop_collection(collection)

    def test_definition(self):
        """Ensure that document may be defined using fields.
        """
        self.assertEqual(['_cls', 'age', 'id', 'name'],
                         sorted(self.Person._fields.keys()))
        self.assertEqual(["IntField", "ObjectIdField", "StringField", "StringField"],
                        sorted([x.__class__.__name__ for x in
                                list(self.Person._fields.values())]))

    def test_get_db(self):
        """Ensure that get_db returns the expected db.
        """
        db = self.Person._get_db()
        self.assertEqual(self.db, db)

    def test_get_collection_name(self):
        """Ensure that get_collection_name returns the expected collection
        name.
        """
        collection_name = 'person'
        self.assertEqual(collection_name, self.Person._get_collection_name())

    def test_get_collection(self):
        """Ensure that get_collection returns the expected collection.
        """
        collection_name = 'person'
        collection = self.Person._get_collection()
        self.assertEqual(self.db[collection_name], collection)

    def test_drop_collection(self):
        """Ensure that the collection may be dropped from the database.
        """
        collection_name = 'person'
        self.Person(name='Test').save()
        self.assertIn(collection_name, list_collection_names(self.db))

        self.Person.drop_collection()
        self.assertNotIn(collection_name, list_collection_names(self.db))

    def test_register_delete_rule(self):
        """Ensure that register delete rule adds a delete rule to the document
        meta.
        """
        class Job(Document):
            employee = ReferenceField(self.Person)

        self.assertEqual(self.Person._meta.get('delete_rules'), None)

        self.Person.register_delete_rule(Job, 'employee', NULLIFY)
        self.assertEqual(self.Person._meta['delete_rules'],
                         {(Job, 'employee'): NULLIFY})

    def test_compare_indexes(self):
        """ Ensure that the indexes are properly created and that
        compare_indexes identifies the missing/extra indexes
        """

        class BlogPost(Document):
            author = StringField()
            title = StringField()
            description = StringField()
            tags = StringField()

            meta = {
                'indexes': [('author', 'title')]
            }

        BlogPost.drop_collection()

        BlogPost.ensure_indexes()
        self.assertEqual(BlogPost.compare_indexes(), {'missing': [], 'extra': []})

        BlogPost.ensure_index(['author', 'description'])
        self.assertEqual(BlogPost.compare_indexes(), {'missing': [], 'extra': [[('author', 1), ('description', 1)]]})

        BlogPost._get_collection().drop_index('author_1_description_1')
        self.assertEqual(BlogPost.compare_indexes(), {'missing': [], 'extra': []})

        BlogPost._get_collection().drop_index('author_1_title_1')
        self.assertEqual(BlogPost.compare_indexes(), {'missing': [[('author', 1), ('title', 1)]], 'extra': []})

    def test_compare_indexes_inheritance(self):
        """ Ensure that the indexes are properly created and that
        compare_indexes identifies the missing/extra indexes for subclassed
        documents (_cls included)
        """

        class BlogPost(Document):
            author = StringField()
            title = StringField()
            description = StringField()

            meta = {
                'allow_inheritance': True
            }

        class BlogPostWithTags(BlogPost):
            tags = StringField()
            tag_list = ListField(StringField())

            meta = {
                'indexes': [('author', 'tags')]
            }

        BlogPost.drop_collection()

        BlogPost.ensure_indexes()
        BlogPostWithTags.ensure_indexes()
        self.assertEqual(BlogPost.compare_indexes(), {'missing': [], 'extra': []})

        BlogPostWithTags.ensure_index(['author', 'tag_list'])
        self.assertEqual(BlogPost.compare_indexes(), {'missing': [], 'extra': [[('_cls', 1), ('author', 1), ('tag_list', 1)]]})

        BlogPostWithTags._get_collection().drop_index('_cls_1_author_1_tag_list_1')
        self.assertEqual(BlogPost.compare_indexes(), {'missing': [], 'extra': []})

        BlogPostWithTags._get_collection().drop_index('_cls_1_author_1_tags_1')
        self.assertEqual(BlogPost.compare_indexes(), {'missing': [[('_cls', 1), ('author', 1), ('tags', 1)]], 'extra': []})

    def test_compare_indexes_multiple_subclasses(self):
        """ Ensure that compare_indexes behaves correctly if called from a
        class, which base class has multiple subclasses
        """

        class BlogPost(Document):
            author = StringField()
            title = StringField()
            description = StringField()

            meta = {
                'allow_inheritance': True
            }

        class BlogPostWithTags(BlogPost):
            tags = StringField()
            tag_list = ListField(StringField())

            meta = {
                'indexes': [('author', 'tags')]
            }

        class BlogPostWithCustomField(BlogPost):
            custom = DictField()

            meta = {
                'indexes': [('author', 'custom')]
            }

        BlogPost.ensure_indexes()
        BlogPostWithTags.ensure_indexes()
        BlogPostWithCustomField.ensure_indexes()

        self.assertEqual(BlogPost.compare_indexes(), {'missing': [], 'extra': []})
        self.assertEqual(BlogPostWithTags.compare_indexes(), {'missing': [], 'extra': []})
        self.assertEqual(BlogPostWithCustomField.compare_indexes(), {'missing': [], 'extra': []})

    def test_compare_indexes_for_text_indexes(self):
        """ Ensure that compare_indexes behaves correctly for text indexes """

        class Doc(Document):
            a = StringField()
            b = StringField()
            meta = {'indexes': [
                {'fields': ['$a', "$b"],
                 'default_language': 'english',
                 'weights': {'a': 10, 'b': 2}
                }
            ]}

        Doc.drop_collection()
        Doc.ensure_indexes()
        actual = Doc.compare_indexes()
        expected = {'missing': [], 'extra': []}
        self.assertEqual(actual, expected)

    def test_list_indexes_inheritance(self):
        """ ensure that all of the indexes are listed regardless of the super-
        or sub-class that we call it from
        """

        class BlogPost(Document):
            author = StringField()
            title = StringField()
            description = StringField()

            meta = {
                'allow_inheritance': True
            }

        class BlogPostWithTags(BlogPost):
            tags = StringField()

            meta = {
                'indexes': [('author', 'tags')]
            }

        class BlogPostWithTagsAndExtraText(BlogPostWithTags):
            extra_text = StringField()

            meta = {
                'indexes': [('author', 'tags', 'extra_text')]
            }

        BlogPost.drop_collection()

        BlogPost.ensure_indexes()
        BlogPostWithTags.ensure_indexes()
        BlogPostWithTagsAndExtraText.ensure_indexes()

        self.assertEqual(BlogPost.list_indexes(),
                         BlogPostWithTags.list_indexes())
        self.assertEqual(BlogPost.list_indexes(),
                         BlogPostWithTagsAndExtraText.list_indexes())
        self.assertEqual(BlogPost.list_indexes(),
                         [[('_cls', 1), ('author', 1), ('tags', 1)],
                         [('_cls', 1), ('author', 1), ('tags', 1), ('extra_text', 1)],
                         [('_id', 1)], [('_cls', 1)]])

    def test_register_delete_rule_inherited(self):

        class Vaccine(Document):
            name = StringField(required=True)

            meta = {"indexes": ["name"]}

        class Animal(Document):
            family = StringField(required=True)
            vaccine_made = ListField(ReferenceField("Vaccine", reverse_delete_rule=PULL))

            meta = {"allow_inheritance": True, "indexes": ["family"]}

        class Cat(Animal):
            name = StringField(required=True)

        self.assertEqual(Vaccine._meta['delete_rules'][(Animal, 'vaccine_made')], PULL)
        self.assertEqual(Vaccine._meta['delete_rules'][(Cat, 'vaccine_made')], PULL)

    def test_collection_naming(self):
        """Ensure that a collection with a specified name may be used.
        """

        class DefaultNamingTest(Document):
            pass
        self.assertEqual('default_naming_test',
                         DefaultNamingTest._get_collection_name())

        class CustomNamingTest(Document):
            meta = {'collection': 'pimp_my_collection'}

        self.assertEqual('pimp_my_collection',
                         CustomNamingTest._get_collection_name())

        class DynamicNamingTest(Document):
            meta = {'collection': lambda c: "DYNAMO"}
        self.assertEqual('DYNAMO', DynamicNamingTest._get_collection_name())

        # Use Abstract class to handle backwards compatibility
        class BaseDocument(Document):
            meta = {
                'abstract': True,
                'collection': lambda c: c.__name__.lower()
            }

        class OldNamingConvention(BaseDocument):
            pass
        self.assertEqual('oldnamingconvention',
                         OldNamingConvention._get_collection_name())

        class InheritedAbstractNamingTest(BaseDocument):
            meta = {'collection': 'wibble'}
        self.assertEqual('wibble',
                         InheritedAbstractNamingTest._get_collection_name())

        # Mixin tests
        class BaseMixin(object):
            meta = {
                'collection': lambda c: c.__name__.lower()
            }

        class OldMixinNamingConvention(Document, BaseMixin):
            pass
        self.assertEqual('oldmixinnamingconvention',
                          OldMixinNamingConvention._get_collection_name())

        class BaseMixin(object):
            meta = {
                'collection': lambda c: c.__name__.lower()
            }

        class BaseDocument(Document, BaseMixin):
            meta = {'allow_inheritance': True}

        class MyDocument(BaseDocument):
            pass

        self.assertEqual('basedocument', MyDocument._get_collection_name())

    def test_custom_collection_name_operations(self):
        """Ensure that a collection with a specified name is used as expected.
        """
        collection_name = 'personCollTest'

        class Person(Document):
            name = StringField()
            meta = {'collection': collection_name}

        Person(name="Test User").save()
        self.assertIn(collection_name, list_collection_names(self.db))

        user_obj = self.db[collection_name].find_one()
        self.assertEqual(user_obj['name'], "Test User")

        user_obj = Person.objects[0]
        self.assertEqual(user_obj.name, "Test User")

        Person.drop_collection()
        self.assertNotIn(collection_name, list_collection_names(self.db))

    def test_collection_name_and_primary(self):
        """Ensure that a collection with a specified name may be used.
        """

        class Person(Document):
            name = StringField(primary_key=True)
            meta = {'collection': 'app'}

        Person(name="Test User").save()

        user_obj = Person.objects.first()
        self.assertEqual(user_obj.name, "Test User")

        Person.drop_collection()


if __name__ == '__main__':
    unittest.main()
