"""This module holds the tests for the views of the ``tasks`` app"""

import json

from django.conf import settings
from django.http import JsonResponse

from mtp.apps.common.tests.test_cases import MTPTestCase

from ..factories import DjangoTaskFactory
from ..models import DjangoTask, TASK_STATES
from ..views import BadStateFilter, TaskFilterSet


class TestCaseMixin(MTPTestCase):
    """Base test case for testing tasks views, that created three tasks

    Attributes
    ----------
    pending_task: DjangoTask
        A task with the state ``pending``
    failure_task: DjangoTask
        A task with the state ``failure``
    success_task: DjangoTask
        A task with the state ``success``

    """

    @classmethod
    def setUpTestData(cls):
        """Create three tasks with different states: ``pending``, ``failure`` and ``success``"""
        super().setUpTestData()

        cls.pending_task = DjangoTaskFactory(user=cls.admin, state=TASK_STATES.PENDING)
        cls.failure_task = DjangoTaskFactory(user=cls.admin, state=TASK_STATES.FAILURE)
        cls.success_task = DjangoTaskFactory(user=cls.admin, state=TASK_STATES.SUCCESS)


class TaskFilterSetTestCase(TestCaseMixin):
    """Test the ``TaskFilterSet`` class"""

    def test_filtering_on_valid_state(self):
        """Test using the ``state`` filter with a valid state"""

        filterset = TaskFilterSet(data={'state': 'PENDING'})
        self.assertCountEqual(filterset.qs, [
            self.pending_task,
        ])

    def test_filtering_on_invalid_state(self):
        """Test using the ``state`` filter with an invalid state"""

        filterset = TaskFilterSet(data={'state': 'FOOBAR'})

        with self.assertRaises(BadStateFilter) as raise_context:
            filterset.qs  # pylint: disable=pointless-statement

        self.assertEqual(str(raise_context.exception), '`FOOBAR` is not a valid state')

    def test_filtering_on_valid_subset(self):
        """Test using the ``state_in`` filter with a valid subset"""

        filterset = TaskFilterSet(data={'state_in': 'READY_STATES'})
        self.assertCountEqual(filterset.qs, [
            self.failure_task,
            self.success_task,
        ])

    def test_filtering_on_invalid_subset(self):
        """Test using the ``state_in`` filter with an invalid subset"""

        filterset = TaskFilterSet(data={'state_in': 'FOOBAR'})

        with self.assertRaises(BadStateFilter) as raise_context:
            filterset.qs  # pylint: disable=pointless-statement

        self.assertEqual(str(raise_context.exception), '`FOOBAR` is not a valid states subset')


class TaskListJsonViewTestCase(TestCaseMixin):
    """Test the ``TaskListJsonView`` view

    Attributes
    ----------
    url: str
        The url of the tasks list view

    """

    @classmethod
    def setUpClass(cls):
        """Save the tasks list url"""
        super().setUpClass()

        cls.url = DjangoTask.get_list_url()

    def setUp(self):
        """Logout the client before each tests, as we use different logins, or none"""
        super().setUp()

        self.client.logout()

    def assertTasksInResponse(self, response, expected_tasks):
        """Utility function asserting that the given response contains exactly the given tasks.

        Parameters
        ----------
        response: HttpResponse
            The response, expected to be ``JsonResponse`` from which Json data will be decoded
        expected_tasks: list
            List of ``DjangoTask`` instances, that are expected to be in the response

        """

        self.assertIsInstance(response, JsonResponse)

        json_data = json.loads(response.content.decode('utf-8'))

        self.assertIn('tasks', json_data)

        self.assertCountEqual(
            json_data['tasks'],
            [
                {
                    'task_id': task.celery_task_id,
                    'state': TASK_STATES.for_value(task.state).constant,
                } for task in expected_tasks
            ]
        )

    def test_view_without_filtering(self):
        """Test that a user can retrieve all its tasks via ajax when logged, without filtering"""

        self.login_as_admin()

        response = self.client.get(self.url, HTTP_X_REQUESTED_WITH='XMLHttpRequest')

        self.assertTasksInResponse(response, [
            self.pending_task,
            self.failure_task,
            self.success_task,
        ])

    def test_view_with_valid_state_filtering(self):
        """Test that a user can retrieve asked tasks via ajax when logged, with valid filtering"""

        self.login_as_admin()
        response = self.client.get(self.url + '?state=PENDING',
                                   HTTP_X_REQUESTED_WITH='XMLHttpRequest')
        self.assertTasksInResponse(response, [
            self.pending_task,
        ])

    def test_view_with_valid_subset_filtering(self):
        """Test that a user can retrieve asked tasks via ajax when logged, with valid filtering"""
        self.login_as_admin()
        response = self.client.get(self.url + '?state_in=READY_STATES',
                                   HTTP_X_REQUESTED_WITH='XMLHttpRequest')
        self.assertTasksInResponse(response, [
            self.failure_task,
            self.success_task,
        ])

    def test_view_with_invalid_filtering(self):
        """Test that a user receives a 404 via ajax when logged, with invalid filtering"""

        self.login_as_admin()
        response = self.client.get(self.url + '?state_in=FOOBAR',
                                   HTTP_X_REQUESTED_WITH='XMLHttpRequest')
        self.assertEqual(response.status_code, 404)

    def test_view_in_non_ajax_mode(self):
        """Test that a user receives a 403 in non-ajax mode, when logged"""

        self.login_as_admin()
        response = self.client.get(self.url)
        self.assertEqual(response.status_code, 403)

    def test_not_authenticated(self):
        """Test that a user receives a 302 if not logged"""

        response = self.client.get(self.url, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
        self.assertEqual(response.status_code, 302)
        self.assertIn('%s?next=%s' % (settings.LOGIN_URL, self.url), response['Location'])

    def test_no_result(self):
        """Test that a user receives no tasks if none are tied to this user"""

        self.login_as_trainer()
        response = self.client.get(self.url, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
        self.assertTasksInResponse(response, [])
