"""This module contains the test cases for the ``views_utils`` module"""

from django.core.cache import cache
from django.http import Http404, HttpResponse
from django.test import Client, RequestFactory, TestCase
from django.test.utils import modify_settings, override_settings
from django.views.generic.base import View

from mtp.apps.accounts.factories import MTPUserFactory
from mtp.apps.accounts.models import MTPUser

from ..views_utils import (
    CacheMixin,
    CrsfExemptMixin,
    NextSuccessUrlMixin,
    RequireMasterMixin,
    RequireSlaveMixin,
    SyncableUndeleteView,
)


@modify_settings(MIDDLEWARE_CLASSES={
    'remove': 'mtp.apps.accounts.middleware.PasswordExpiryMiddleware',
})
@override_settings(ROOT_URLCONF='mtp.apps.common.tests.fake_urls')
class CsrfExemptMixinTestCase(TestCase):
    """Test case for the ``CsrfExemptMixin`` view mixin"""

    @classmethod
    def setUpTestData(cls):
        """Setup a test client with the ``enforce_csrf_checks`` flag"""

        super().setUpTestData()

        cls.csrf_client = Client(enforce_csrf_checks=True)

    class SimpleView(View):
        """Simple view for the test needs"""

        # pylint:disable=unused-argument,missing-param-doc,missing-type-doc
        def post(self, request, *args, **kwargs):
            """Simply return a string for the test needs"""

            return HttpResponse('test')

    class ViewWithCsrfDecorator(CrsfExemptMixin, SimpleView):
        """Simple view with the crsf exempt decorator"""

        pass

    def test_simple_view_without_decorator(self):
        """Test that the view without the decorator should output an ``HttpResponseNotAllowed``"""

        response = self.csrf_client.post('/url-with-csrf-protection/')

        self.assertEqual(
            response.status_code,
            403
        )

    def test_view_with_decorator(self):
        """Test that the view with the decorator should output the correct response"""

        response = self.csrf_client.post('/url-without-csrf-protection/')

        self.assertEqual(
            response.status_code,
            200
        )


class RequireMasterMixinTestCase(TestCase):
    """Test case for the ``RequireMasterMixin`` view mixin"""

    class SimpleView(RequireMasterMixin, View):
        """Simple view for the tests"""

        # pylint:disable=unused-argument,missing-param-doc,missing-type-doc
        def get(self, request, *arg, **kwargs):
            """Simply returns a string for the test needs"""

            return 'test'

    def call_view(self):
        """Call the ``SimpleView``"""

        view = self.SimpleView.as_view()

        return view(RequestFactory().get('/fake/path/not/used'), [], {})

    @override_settings(INSTANCE_IS_MASTER=True)
    def test_call_view_with_master_true(self):
        """Call the view with a ``INSTANCE_IS_MASTER`` to ``True"""

        self.assertEqual(
            self.call_view(),
            'test'
        )

    @override_settings(INSTANCE_IS_MASTER=False)
    def test_call_view_with_master_false(self):
        """Call the view with a ``INSTANCE_IS_MASTER`` to ``False"""

        with self.assertRaises(Http404):
            self.call_view()


class RequireSlaveMixinTestCase(TestCase):
    """Test case for the ``RequireSlaveMixin`` view mixin"""

    class SimpleView(RequireSlaveMixin, View):
        """Simple view for the tests"""

        # pylint:disable=unused-argument,missing-param-doc,missing-type-doc
        def get(self, request, *arg, **kwargs):
            """Simply returns a string for the test needs"""

            return 'test'

    def call_view(self):
        """Call the ``SimpleView``"""

        view = self.SimpleView.as_view()

        return view(RequestFactory().get('/fake/path/not/used'), [], {})

    @override_settings(INSTANCE_IS_SLAVE=True)
    def test_call_view_with_slave_true(self):
        """Call the view with a ``INSTANCE_IS_SLAVE`` to ``True"""

        self.assertEqual(
            self.call_view(),
            'test'
        )

    @override_settings(INSTANCE_IS_SLAVE=False)
    def test_call_view_with_slave_false(self):
        """Call the view with a ``INSTANCE_IS_SLAVE`` to ``False"""

        with self.assertRaises(Http404):
            self.call_view()


class SyncableUndeleteViewTestCase(TestCase):
    """Test case for ``SyncableUndeleteView``"""

    class SimpleView(SyncableUndeleteView):
        """Simple view for the tests"""
        model = MTPUser
        success_url = 'whatever'

    @classmethod
    def setUpTestData(cls):
        """Create a syncable object (MTPUser)."""

        super().setUpTestData()

        cls.syncable_user = MTPUserFactory(is_deleted=True)
        cls.factory = RequestFactory()

    def test_call_view(self):
        """Call the view"""

        request = self.factory.post('fake_url')
        view = self.SimpleView.as_view()

        self.assertTrue(self.syncable_user.is_deleted)

        view(request, pk=self.syncable_user.pk)

        self.syncable_user.refresh_from_db()
        self.assertFalse(self.syncable_user.is_deleted)


class NextSuccessUrlMixinTestCase(TestCase):
    """Test case for ``NextSuccessUrlMixin``"""

    class SimpleBaseView(View):
        """A base view with get_success_url defined"""

        def get_success_url(self):
            """Defines the success_url"""

            return '/original/success/url'

    class SimpleView(NextSuccessUrlMixin, SimpleBaseView):
        """A simple view that uses the mixin"""

        # pylint:disable=unused-argument,missing-param-doc,missing-type-doc
        def get(self, request, *arg, **kwargs):
            """Return the value of success_url for tests"""

            return self.get_success_url()

    @classmethod
    def setUpTestData(cls):
        """Create a syncable object (MTPUser)."""

        super().setUpTestData()

        cls.factory = RequestFactory()

    def test_success_url_with_next(self):
        """Test that next GET parameter becomes success_url"""

        next_url = '/cms/trainings/sessions/?page=2'

        view = self.SimpleView.as_view()
        response = view(self.factory.get('/fake/?next=%s' % next_url), [], {})

        self.assertEqual(response, next_url)

    def test_success_url_with_unsafe_next(self):
        """Test that default success_url is returned if next param is unsafe url"""

        # unsafe because external domain
        unsafe_url = 'https://news.ycombinator.com'

        view = self.SimpleView.as_view()
        response = view(self.factory.get('/fake/?next=%s' % unsafe_url), [], {})

        self.assertEqual(response, '/original/success/url')

    def test_success_url_without_next(self):
        """Test that default success_url is returned if no next param"""

        view = self.SimpleView.as_view()

        response = view(self.factory.get('/fake/path/not/used'), [], {})

        self.assertEqual(response, '/original/success/url')


@modify_settings(MIDDLEWARE_CLASSES={
    'remove': 'mtp.apps.accounts.middleware.PasswordExpiryMiddleware',
})
@override_settings(ROOT_URLCONF='mtp.apps.common.tests.fake_urls')
class CacheMixinTestCase(TestCase):
    """Test case for the ``CacheMixin``"""

    @classmethod
    def setUpClass(cls):
        """Store the url of the view"""

        super().setUpClass()

        cls.cached_page_url = '/url-with-cache/'

    class ViewWithCacheMixin(CacheMixin, View):
        """Simple view using the ``CacheMixin``"""

        def get(self, request, *args, **kwargs):  # pylint:disable=unused-argument
            """A simple database request for the test needs"""

            users = MTPUser.objects.all()

            return HttpResponse(users.count())

    def test_simple_view_with_decorator(self):
        """Test that the view with the decorator should store his result

        The two initial database queries are coming from the test savepoint.

        """

        cache.clear()

        # Call the view a first time, this should output 3 database queries
        with self.assertNumQueries(3):
            self.client.get('/url-with-cache/')

        # Call the view a second time, only 2 database queries should be outputted
        with self.assertNumQueries(2):
            self.client.get('/url-with-cache/')

        # Clear the cache
        cache.clear()

        # We must have 3 queries again
        with self.assertNumQueries(3):
            self.client.get('/url-with-cache/')


