# -*- coding: utf-8 -*-
try:
    from unittest import mock
except ImportError:
    import mock

import six
from django.db import models
from django.test import TestCase
from tests.testapp.models import (
    DummyRelationModel, InheritedFromPostWithUniqField, PostWithUniqField,
    ReverseModel, SecondDummyRelationModel, ThirdDummyRelationModel,
)

from django_extensions.db.fields import UniqueFieldMixin


class UniqFieldMixinTestCase(TestCase):
    def setUp(self):

        class MockField(UniqueFieldMixin):
            def __init__(self, **kwargs):
                for key, value in six.iteritems(kwargs):
                    setattr(self, key, value)

        self.uniq_field = MockField(
            attname='uniq_field',
            max_length=255,
            boolean_attr=True,
            non_boolean_attr='non_boolean_attr'
        )

        f_dummy = DummyRelationModel.objects.create()
        s_dummy = SecondDummyRelationModel.objects.create()
        t_dummy = ThirdDummyRelationModel.objects.create()

        post = PostWithUniqField.objects.create(
            uniq_field='test_uniq',
            common_field='first',
            another_common_field='second',
            many_to_one_field=f_dummy,
            one_to_one_field=s_dummy,
        )
        post.many_to_many_field.add(t_dummy)
        post.save()

        ReverseModel.objects.create(post_field=post)

        self.post = post

    def tearDown(self):
        PostWithUniqField.objects.all().delete()
        DummyRelationModel.objects.all().delete()
        SecondDummyRelationModel.objects.all().delete()
        ThirdDummyRelationModel.objects.all().delete()
        ReverseModel.objects.all().delete()

    def test_check_is_bool_boolean_attr(self):

        self.assertIsNone(self.uniq_field.check_is_bool('boolean_attr'))

    def test_check_is_bool_non_boolean_attr(self):
        with self.assertRaisesMessage(
            ValueError,
            "'non_boolean_attr' argument must be True or False",
        ):
            self.uniq_field.check_is_bool('non_boolean_attr')

    def test__get_fields_returns_list_of_tulpes(self):
        uniq_mixin_fields = UniqueFieldMixin._get_fields(PostWithUniqField)
        self.assertIsInstance(uniq_mixin_fields, list)
        for field in uniq_mixin_fields:
            self.assertIsInstance(field, tuple)

    def test__get_fields_returns_correct_fields(self):
        option_fields = PostWithUniqField._meta.get_fields()
        uniq_mixin_fields = [i[0] for i in UniqueFieldMixin._get_fields(PostWithUniqField)]

        self.assertEqual(len(option_fields), 9)
        self.assertEqual(len(uniq_mixin_fields), 7)

        fields_to_be_excluded_from_uniq_mixin_fields = [
            f for f in option_fields
            if f.is_relation and not f.one_to_one and not (f.many_to_one and f.related_model)
        ]

        for field in fields_to_be_excluded_from_uniq_mixin_fields:
            self.assertNotIn(field, uniq_mixin_fields)

    def test__get_fields_returns_correct_model(self):
        post_models = [i[1] for i in UniqueFieldMixin._get_fields(PostWithUniqField)]
        self.assertTrue(all(model is None for model in post_models))

        inherited_post_models = [
            i[1] for i
            in UniqueFieldMixin._get_fields(InheritedFromPostWithUniqField)
            if i[1]
        ]

        self.assertEqual(len(inherited_post_models), 6)
        self.assertTrue(all(model is PostWithUniqField) for model in inherited_post_models)

    def test_get_queryset(self):
        mocked_get_fields = (
            (models.CharField, PostWithUniqField),
        )
        with mock.patch(
            'django_extensions.db.fields.UniqueFieldMixin._get_fields',
            return_value=mocked_get_fields
        ), mock.patch(
            'tests.testapp.models.PostWithUniqField._default_manager.all'
        ) as mocked_qs_all:

            self.uniq_field.get_queryset(PostWithUniqField, models.CharField)

        mocked_qs_all.assert_called_with()

        mocked_get_fields = (
            (models.CharField, None),
        )

        with mock.patch(
            'django_extensions.db.fields.UniqueFieldMixin._get_fields',
            return_value=mocked_get_fields
        ), mock.patch(
            'tests.testapp.models.InheritedFromPostWithUniqField._default_manager.all'
        ) as mocked_qs_all:

            self.uniq_field.get_queryset(InheritedFromPostWithUniqField, models.CharField)

        mocked_qs_all.assert_called_with()

    def test_find_unique(self):
        def filter_func(*args, **kwargs):
            uniq_field = kwargs.get('uniq_field')
            if uniq_field == 'a':
                return mocked_qs
            return None

        mocked_qs = mock.Mock(spec=PostWithUniqField.objects)
        mocked_qs.filter.side_effect = filter_func
        mocked_qs.exclude.return_value = mocked_qs

        field = models.CharField
        with mock.patch(
            'django_extensions.db.fields.UniqueFieldMixin.get_queryset',
            return_value=mocked_qs
        ) as get_qs:

            res = self.uniq_field.find_unique(self.post, field, iter('abcde'))

        get_qs.assert_called_with(PostWithUniqField, field)
        mocked_qs.exclude.assert_called_with(pk=self.post.pk)
        self.assertEqual(res, 'b')
        self.assertTrue(hasattr(self.post, 'uniq_field'))
        self.assertEqual(self.post.uniq_field, 'b')
