"""Test the views of the ``surveys`` app

Notes
-----
The choice was done to only test the overridden methods, not the default functionalities.

So it an attribute of a class based view is changed without changing other relevant stuff, it
may break without any tests notifying it.

"""

from django.http import Http404
from django.test import RequestFactory

from mtp.apps.accounts.factories import AdminFactory, TrainerFactory

from ..factories import PoolFactory
from ..models import Pool
from ..views import PoolCreate, PoolDelete, PoolUpdate, PoolsList

from .test_cases import SurveyTestCase


class PoolsListTestCase(SurveyTestCase):
    """Test case that tests the ``PoolsList`` view."""

    @classmethod
    def setUpTestData(cls):
        """Add additional test data."""
        super().setUpTestData()

        # We'll need a second admin.
        cls.admin2 = AdminFactory()

        # Add more pools.
        PoolFactory(author=cls.trainer)
        PoolFactory(author=cls.admin)
        PoolFactory.create_batch(2, author=cls.admin2)

    def setUp(self):
        """Prepare the request, the view, the users and some pools for each test."""

        super().setUp()

        # We'll need a request.
        self.request = RequestFactory().get('/fake/path/not/used')

        # We'll need a view (to be set up).
        self.view = PoolsList()

    def get_queryset(self):

        # Setup the view, instead of calling ``as_view`` then ``dispatch``, because we just need
        # an instance of the view.
        self.view.request = self.request
        self.view.args, self.view.kwargs = [], {}

        # Then call the method we want to test.
        return self.view.get_queryset()

    def test_get_queryset_should_return_all_global_pools_if_superuser(self):
        """Test that the ``get_queryset`` methods returns all global pools to a superuser."""

        # Call the ``get_queryset`` method for an admin.
        self.request.user = self.admin
        pools = self.get_queryset()

        # Check we've got 4 pools.(2 for each admin).
        self.assertEqual(len(pools), 4)

    def test_get_queryset_should_return_own_pools_if_trainer(self):
        """Test that the ``get_queryset`` methods returns only its own pools to a trainer"""

        # Call the ``get_queryset`` method for the trainer.
        self.request.user = self.trainer
        pools = self.get_queryset()

        # Check we've got only 2  pools.
        self.assertEqual(len(pools), 2)

        # And that the author is the trainer.
        self.assertEqual(pools[0].author, self.trainer)
        self.assertEqual(pools[1].author, self.trainer)

    def test_get_queryset_should_not_return_deleted_pools(self):
        """Test that the ``get_queryset`` methods does not returns deleted pools."""

        # Add two more pools for the trainer, but marked as deleted.
        PoolFactory.create_batch(2, author=self.trainer, is_deleted=True)

        # Call the ``get_queryset`` method for a trainer.
        self.request.user = self.trainer
        pools = self.get_queryset()

        # Check we've got only 2  pools.
        self.assertEqual(len(pools), 2)

        # And not the deleted ones.
        self.assertFalse(pools[0].is_deleted)
        self.assertFalse(pools[1].is_deleted)


class PoolCreateTestCase(SurveyTestCase):
    """Test case that tests the ``PoolCreate`` view."""

    def test_author_should_be_saved(self):
        """Test that the current user is saved as the author of a newly created pool."""

        # Ensure we don't have any pool yet.
        Pool.objects.all().delete()
        self.assertEqual(Pool.objects.count(), 0)

        # Create the request with data to post to create a pool.
        request = RequestFactory().post('/fake/path/not/used', {'name': 'Test pool'})
        # Set the current user to be a trainer.
        request.user = self.trainer

        # Execute the view.
        PoolCreate.as_view()(request)

        # Now we should have a pool.
        self.assertEqual(Pool.objects.count(), 1)
        # And it's author should be our trainer.
        self.assertEqual(Pool.objects.first().author, self.trainer)


class PoolEditBaseTestCaseMixin:
    """Test case with test to be used with ``PoolUpdate`` and ``PoolDelete`` views.

    Each subclass should define the ``view`` attribute."""

    view = None

    def call_view(self, author, pool_pk):
        """ Call the view on the behalf of the given author, for the given pool primary key.

        Returns
        -------
        django.http.HttpResponse
            The response from the called view.

        Raises
        ------
        HTTP404
            If the given author is not allowed to edit the given pool.

        """
        request = RequestFactory().get('/fake/path/not/used')
        request.user = author
        return self.view.as_view()(request, pool_uuid=pool_pk)

    def test_admin_should_only_edit_global_pools(self):
        """Test that an admin can only edit global pools, not trainers ones."""

        # Create another pool another admin.
        admin2 = AdminFactory()
        admin_pool2 = PoolFactory(author=admin2)

        # Editing its own pool should work.
        response = self.call_view(self.admin, self.admin_pool.pk)
        self.assertEqual(response.status_code, 200)

        # Editing the other admin pool should work.
        response = self.call_view(self.admin, admin_pool2.pk)
        self.assertEqual(response.status_code, 200)

        # Editing the trainer pool should fail.
        with self.assertRaises(Http404):
            self.call_view(self.admin, self.trainer_pool.pk)

    def test_trainer_should_only_edit_its_own_pools(self):
        """"Test that a trainer can only edit its own pools, not global ones or other ones"""

        # Create another pool another trainer.
        trainer2 = TrainerFactory()
        trainer_pool2 = PoolFactory(author=trainer2)

        # Editing its own pool should work.
        response = self.call_view(self.trainer, self.trainer_pool.pk)
        self.assertEqual(response.status_code, 200)

        # Editing the other trainer pool should fail.
        with self.assertRaises(Http404):
            self.call_view(self.trainer, trainer_pool2.pk)

        # Editing the admin pool should fail.
        with self.assertRaises(Http404):
            self.call_view(self.trainer, self.admin_pool.pk)

    def test_deleted_pool_should_not_be_editable(self):
        """Test that a pool marked as deleted cannot be edited"""

        # Create a fake deleted pool to play with.
        pool = PoolFactory(is_deleted=True, author=self.trainer)

        # Execute the view, which should fail
        with self.assertRaises(Http404):
            self.call_view(pool.author, pool.pk)


class PoolUpdateTestCase(PoolEditBaseTestCaseMixin, SurveyTestCase):
    """Test case that tests the ``PoolEdit`` view."""
    view = PoolUpdate


class PoolDeleteTestCase(PoolEditBaseTestCaseMixin, SurveyTestCase):
    """Test case that test the ``PoolDelete`` view."""

    view = PoolDelete

    def test_pool_should_not_be_really_deleted(self):
        """Ensure that the pool is not really deleted but marked as so."""

        pools_qs = Pool.objects.filter_for_trainer(self.trainer, include_globals=False)

        # We should have one pool for the trainer.
        self.assertEqual(pools_qs.count(), 1)
        # Not marked as deleted.
        self.assertFalse(self.trainer_pool.is_deleted)

        # Create the request.
        request = RequestFactory().post('/fake/path/not/used')
        request.user = self.trainer_pool.author

        # Execute the view
        PoolDelete.as_view()(request, pool_uuid=self.trainer_pool.pk)

        # We should still have one pool.
        self.assertEqual(pools_qs.count(), 1)
        # But it should be marked as deleted
        self.assertTrue(pools_qs.first().is_deleted)
