from django.test import override_settings
from django.core.exceptions import ValidationError
from django.conf.urls import url
from django.core.cache import cache
from rest_framework.test import APIClient, APIRequestFactory
from rest_framework import status, filters

from .utils import MultipleModelTestCase
from .models import Play, Poem
from .serializers import PlaySerializer, PoemSerializer
from drf_multiple_model.views import ObjectMultipleModelAPIView


factory = APIRequestFactory()


class BasicObjectView(ObjectMultipleModelAPIView):
    querylist = (
        {'queryset': Play.objects.all(), 'serializer_class': PlaySerializer},
        {'queryset': Poem.objects.filter(style="Sonnet"), 'serializer_class': PoemSerializer},
    )


class CustomLabelView(ObjectMultipleModelAPIView):
    querylist = (
        {
            'queryset': Play.objects.all(),
            'serializer_class': PlaySerializer,
            'label': 'Drama',
        },
        {
            'queryset': Poem.objects.filter(style="Sonnet"),
            'serializer_class': PoemSerializer,
            'label': 'Poetry',
        },
    )


class DynamicQueryView(ObjectMultipleModelAPIView):
    def get_querylist(self):
        title = self.kwargs['play'].replace('-', ' ')

        querylist = (
            {'queryset': Play.objects.filter(title=title), 'serializer_class': PlaySerializer},
            {'queryset': Poem.objects.filter(style="Sonnet"), 'serializer_class': PoemSerializer},
        )

        return querylist


class SearchFilterView(BasicObjectView):
    filter_backends = (filters.SearchFilter,)
    search_fields = ('title',)


# Testing filter_fn
def title_without_letter(queryset, request, *args, **kwargs):
    letter_to_exclude = request.query_params['letter']
    return queryset.exclude(title__icontains=letter_to_exclude)


class FilterFnView(ObjectMultipleModelAPIView):
    querylist = (
        {
            'queryset': Play.objects.all(),
            'serializer_class': PlaySerializer,
            'filter_fn': title_without_letter,
        },
        {
            'queryset': Poem.objects.filter(style="Sonnet"),
            'serializer_class': PoemSerializer,
        },
    )


class CachedQueryView(ObjectMultipleModelAPIView):
    querylist = (
        {'queryset': Play.objects.all(), 'serializer_class': PlaySerializer},
        {'queryset': Poem.objects.filter(style="Sonnet"), 'serializer_class': PoemSerializer},
    )

    def load_queryset(self, query_data, request, *args, **kwargs):
        queryset = cache.get('{}-queryset'.format(query_data['queryset'].model.__name__))
        if not queryset:
            queryset = query_data['queryset'].all()
            cache.set('{}-queryset'.format(query_data['queryset'].model.__name__), queryset)
        return queryset


# Broken Views
class NoQuerylistView(ObjectMultipleModelAPIView):
    pass


class NoQuerysetView(ObjectMultipleModelAPIView):
    querylist = [
        {'serializer_class': PlaySerializer},
        {'serializer_class': PoemSerializer},
    ]


class NoSerializerClassView(ObjectMultipleModelAPIView):
    querylist = [
        {'queryset': Play.objects.all()},
        {'queryset': Poem.objects.all()},
    ]


urlpatterns = [
    url(r'^$', BasicObjectView.as_view()),
]


# TESTS
@override_settings(ROOT_URLCONF=__name__)
class TestMMObjectViews(MultipleModelTestCase):
    maxDiff = None

    def test_post(self):
        """
        POST requests should throw a 405 Error
        """
        view = BasicObjectView.as_view()

        data = {'fake': 'data'}
        request = factory.post('/', data, format='json')

        with self.assertNumQueries(0):
            response = view(request).render()

        self.assertEqual(response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED)
        self.assertEqual(response.data, {"detail": 'Method "POST" not allowed.'})

    def test_put(self):
        """
        PUT requests should throw a 405 Error
        """
        view = BasicObjectView.as_view()

        data = {'fake': 'data'}
        request = factory.put('/', data, format='json')

        with self.assertNumQueries(0):
            response = view(request).render()

        self.assertEqual(response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED)
        self.assertEqual(response.data, {"detail": 'Method "PUT" not allowed.'})

    def test_delete(self):
        """
        DELETE requests should throw a 405 Error
        """
        view = BasicObjectView.as_view()

        request = factory.delete('/')

        with self.assertNumQueries(0):
            response = view(request).render()

        self.assertEqual(response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED)
        self.assertEqual(response.data, {"detail": 'Method "DELETE" not allowed.'})

    def test_no_querylist(self):
        """
        A view with no querylist and no `get_querylist` overwrite should raise
        an assertion error with the appropriate message
        """
        view = NoQuerylistView.as_view()

        request = factory.get('/')

        with self.assertRaises(AssertionError) as error:
            view(request).render()

        self.assertEqual(str(error.exception), (
            'NoQuerylistView should either include a `querylist` attribute, '
            'or override the `get_querylist()` method.'
        ))

    def test_no_queryset(self):
        """
        A querylist with no `queryset` key should raise a ValidationError with the
        appropriate message
        """
        view = NoQuerysetView.as_view()

        request = factory.get('/')

        with self.assertRaises(ValidationError) as error:
            view(request).render()

        self.assertEqual(error.exception.message, (
            'All items in the NoQuerysetView querylist attribute '
            'should contain a `queryset` key'
        ))

    def test_no_serializer_class(self):
        """
        A querylist with no `serializer_class` key should raise a ValidationError with the
        appropriate message
        """
        view = NoSerializerClassView.as_view()

        request = factory.get('/')

        with self.assertRaises(ValidationError) as error:
            view(request).render()

        self.assertEqual(error.exception.message, (
            'All items in the NoSerializerClassView querylist attribute '
            'should contain a `serializer_class` key'
        ))

    def test_basic_object_view(self):
        """
        The default setting for the `ObjectMultipleModelView` should return
        the serialized objects in querylist order
        """
        view = BasicObjectView.as_view()

        request = factory.get('/')
        with self.assertNumQueries(2):
            response = view(request).render()

        self.assertEqual(response.status_code, status.HTTP_200_OK)

        self.assertEqual(len(response.data), 2)
        self.assertEqual(response.data, {
            'Play': [
                {'title': 'Romeo And Juliet', 'genre': 'Tragedy', 'year': 1597},
                {'title': "A Midsummer Night's Dream", 'genre': 'Comedy', 'year': 1600},
                {'title': 'Julius Caesar', 'genre': 'Tragedy', 'year': 1623},
                {'title': 'As You Like It', 'genre': 'Comedy', 'year': 1623},
            ],
            'Poem': [
                {'title': "Shall I compare thee to a summer's day?", 'style': 'Sonnet'},
                {'title': "As a decrepit father takes delight", 'style': 'Sonnet'}
            ]
        })

    def test_new_labels(self):
        """
        Adding the 'label' key to querylist elements should use those labels
        instead of the model names
        """
        view = CustomLabelView.as_view()

        request = factory.get('/')
        with self.assertNumQueries(2):
            response = view(request).render()

        self.assertEqual(response.status_code, status.HTTP_200_OK)

        self.assertEqual(len(response.data), 2)
        self.assertEqual(response.data, {
            'Drama': [
                {'title': 'Romeo And Juliet', 'genre': 'Tragedy', 'year': 1597},
                {'title': "A Midsummer Night's Dream", 'genre': 'Comedy', 'year': 1600},
                {'title': 'Julius Caesar', 'genre': 'Tragedy', 'year': 1623},
                {'title': 'As You Like It', 'genre': 'Comedy', 'year': 1623},
            ],
            'Poetry': [
                {'title': "Shall I compare thee to a summer's day?", 'style': 'Sonnet'},
                {'title': "As a decrepit father takes delight", 'style': 'Sonnet'}
            ]
        })

    def test_filter_fn_view(self):
        """
        The filter function is useful if you want to apply filtering to one query
        but not another (unlike adding view level filtering, which will filter all the
        querysets), but that filtering can't be provided at the beginning (for example, it
        needs to access a query_param).  This is testing the filter_fn.
        """
        view = FilterFnView.as_view()

        request = factory.get('/', {'letter': 'o'})

        with self.assertNumQueries(2):
            response = view(request).render()

        # Check that the plays have been filter to remove those with the letter 'o'
        # But the poems haven't been affected
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(response.data, {
            'Play': [
                {'title': "A Midsummer Night's Dream", 'genre': 'Comedy', 'year': 1600},
                {'title': 'Julius Caesar', 'genre': 'Tragedy', 'year': 1623},
            ],
            'Poem': [
                {'title': "Shall I compare thee to a summer's day?", 'style': 'Sonnet'},
                {'title': "As a decrepit father takes delight", 'style': 'Sonnet'}
            ]
        })

    def test_dynamic_querylist(self):
        """
        using get_querylist allows the construction of dynamic queryLists
        """
        view = DynamicQueryView.as_view()

        request = factory.get('/Julius-Caesar')
        with self.assertNumQueries(2):
            response = view(request, play="Julius-Caesar")

        self.assertEqual(response.status_code, status.HTTP_200_OK)

        self.assertEqual(len(response.data), 2)
        self.assertEqual(response.data, {
            'Play': [
                {'title': 'Julius Caesar', 'genre': 'Tragedy', 'year': 1623},
            ],
            'Poem': [
                {'title': "Shall I compare thee to a summer's day?", 'style': 'Sonnet'},
                {'title': "As a decrepit father takes delight", 'style': 'Sonnet'}
            ]
        })

    def test_cached_querylist(self):
        view = CachedQueryView.as_view()

        request = factory.get('/Julius-Caesar')
        with self.assertNumQueries(2):
            response = view(request)
        with self.assertNumQueries(0):
            response = view(request)

        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(response.data, {
            'Play': [
                {'title': 'Romeo And Juliet', 'genre': 'Tragedy', 'year': 1597},
                {'title': "A Midsummer Night's Dream", 'genre': 'Comedy', 'year': 1600},
                {'title': 'Julius Caesar', 'genre': 'Tragedy', 'year': 1623},
                {'title': 'As You Like It', 'genre': 'Comedy', 'year': 1623},
            ],
            'Poem': [
                {'title': "Shall I compare thee to a summer's day?", 'style': 'Sonnet'},
                {'title': "As a decrepit father takes delight", 'style': 'Sonnet'}
            ]
        })

    def test_search_filter_view(self):
        """
        Tests use of built in DRF filtering with ObjectMultipleModelAPIView
        """
        view = SearchFilterView.as_view()

        request = factory.get('/', {'search': 'as'})

        with self.assertNumQueries(2):
            response = view(request).render()

        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(response.data, {
            'Play': [
                {'title': 'As You Like It', 'genre': 'Comedy', 'year': 1623},
            ],
            'Poem': [
                {'title': "As a decrepit father takes delight", 'style': 'Sonnet'}
            ]
        })

    def test_url_endpoint(self):
        """
        DRF 3.3 broke the MultipleModelAPIView with a load_queryset call
        This test is to replicate (and then fix) that problem
        """
        client = APIClient()
        response = client.get('/', format='api')
        self.assertEqual(response.status_code, status.HTTP_200_OK)