import attr
from django.test import TestCase
from rest_framework import status
from rest_framework.test import APIRequestFactory

from mantle_drf.generics import (
    CreateAPIView,
    DestroyAPIView,
    ListAPIView,
    ListCreateAPIView,
    RetrieveAPIView,
    RetrieveDestroyAPIView,
    RetrieveUpdateAPIView,
    RetrieveUpdateDestroyAPIView,
    UpdateAPIView,
)
from tests.models import Item


@attr.define
class ItemShape:
    name: str
    slug: str


factory = APIRequestFactory()


class TestRetrieveAPIView(TestCase):
    @classmethod
    def setUpTestData(cls):
        cls.item = Item.objects.create(name="Test", slug="test")

    def test_retrieve(self):
        class TestView(RetrieveAPIView):
            queryset = Item.objects.all()
            shape_class = ItemShape

        request = factory.get("/")
        view = TestView.as_view()
        response = view(request, pk=self.item.pk)
        assert response.status_code == status.HTTP_200_OK
        assert response.data == {"name": "Test", "slug": "test"}


class TestListAPIView(TestCase):
    @classmethod
    def setUpTestData(cls):
        Item.objects.create(name="A", slug="a")
        Item.objects.create(name="B", slug="b")

    def test_list(self):
        class TestView(ListAPIView):
            queryset = Item.objects.all()
            shape_class = ItemShape

        request = factory.get("/")
        view = TestView.as_view()
        response = view(request)
        assert response.status_code == status.HTTP_200_OK
        assert len(response.data) == 2

    def test_list_single_query(self):
        class TestView(ListAPIView):
            queryset = Item.objects.all()
            shape_class = ItemShape

        request = factory.get("/")
        view = TestView.as_view()
        with self.assertNumQueries(1):
            view(request)

    def test_list_paginated_single_query(self):
        from rest_framework.pagination import PageNumberPagination

        class SmallPagination(PageNumberPagination):
            page_size = 1

        class TestView(ListAPIView):
            queryset = Item.objects.all()
            shape_class = ItemShape
            pagination_class = SmallPagination

        request = factory.get("/")
        view = TestView.as_view()
        # 1 query for count, 1 for the page.
        with self.assertNumQueries(2):
            view(request)


class TestCreateAPIView(TestCase):
    def test_create(self):
        class TestView(CreateAPIView):
            queryset = Item.objects.all()
            shape_class = ItemShape

        request = factory.post("/", {"name": "New", "slug": "new"}, format="json")
        view = TestView.as_view()
        response = view(request)
        assert response.status_code == status.HTTP_201_CREATED
        assert response.data["name"] == "New"
        assert Item.objects.filter(slug="new").exists()


class TestDestroyAPIView(TestCase):
    @classmethod
    def setUpTestData(cls):
        cls.item = Item.objects.create(name="Delete Me", slug="delete-me")

    def test_destroy(self):
        class TestView(DestroyAPIView):
            queryset = Item.objects.all()
            shape_class = ItemShape

        request = factory.delete("/")
        view = TestView.as_view()
        response = view(request, pk=self.item.pk)
        assert response.status_code == status.HTTP_204_NO_CONTENT
        assert not Item.objects.filter(pk=self.item.pk).exists()


class TestUpdateAPIView(TestCase):
    @classmethod
    def setUpTestData(cls):
        cls.item = Item.objects.create(name="Old", slug="old")

    def test_update(self):
        class TestView(UpdateAPIView):
            queryset = Item.objects.all()
            shape_class = ItemShape

        request = factory.put(
            "/", {"name": "Updated", "slug": "updated"}, format="json"
        )
        view = TestView.as_view()
        response = view(request, pk=self.item.pk)
        assert response.status_code == status.HTTP_200_OK
        assert response.data["name"] == "Updated"
        self.item.refresh_from_db()
        assert self.item.name == "Updated"


class TestListCreateAPIView(TestCase):
    @classmethod
    def setUpTestData(cls):
        Item.objects.create(name="Existing", slug="existing")

    def test_list(self):
        class TestView(ListCreateAPIView):
            queryset = Item.objects.all()
            shape_class = ItemShape

        request = factory.get("/")
        view = TestView.as_view()
        response = view(request)
        assert response.status_code == status.HTTP_200_OK

    def test_create(self):
        class TestView(ListCreateAPIView):
            queryset = Item.objects.all()
            shape_class = ItemShape

        request = factory.post("/", {"name": "New", "slug": "lc-new"}, format="json")
        view = TestView.as_view()
        response = view(request)
        assert response.status_code == status.HTTP_201_CREATED


class TestRetrieveUpdateAPIView(TestCase):
    @classmethod
    def setUpTestData(cls):
        cls.item = Item.objects.create(name="RU", slug="ru")

    def test_retrieve(self):
        class TestView(RetrieveUpdateAPIView):
            queryset = Item.objects.all()
            shape_class = ItemShape

        request = factory.get("/")
        view = TestView.as_view()
        response = view(request, pk=self.item.pk)
        assert response.status_code == status.HTTP_200_OK

    def test_update(self):
        class TestView(RetrieveUpdateAPIView):
            queryset = Item.objects.all()
            shape_class = ItemShape

        request = factory.put(
            "/", {"name": "Updated", "slug": "ru-updated"}, format="json"
        )
        view = TestView.as_view()
        response = view(request, pk=self.item.pk)
        assert response.status_code == status.HTTP_200_OK


class TestRetrieveDestroyAPIView(TestCase):
    @classmethod
    def setUpTestData(cls):
        cls.item = Item.objects.create(name="RD", slug="rd")

    def test_retrieve(self):
        class TestView(RetrieveDestroyAPIView):
            queryset = Item.objects.all()
            shape_class = ItemShape

        request = factory.get("/")
        view = TestView.as_view()
        response = view(request, pk=self.item.pk)
        assert response.status_code == status.HTTP_200_OK

    def test_destroy(self):
        class TestView(RetrieveDestroyAPIView):
            queryset = Item.objects.all()
            shape_class = ItemShape

        request = factory.delete("/")
        view = TestView.as_view()
        response = view(request, pk=self.item.pk)
        assert response.status_code == status.HTTP_204_NO_CONTENT


class TestRetrieveUpdateDestroyAPIView(TestCase):
    @classmethod
    def setUpTestData(cls):
        cls.item = Item.objects.create(name="RUD", slug="rud")

    def test_retrieve(self):
        class TestView(RetrieveUpdateDestroyAPIView):
            queryset = Item.objects.all()
            shape_class = ItemShape

        request = factory.get("/")
        view = TestView.as_view()
        response = view(request, pk=self.item.pk)
        assert response.status_code == status.HTTP_200_OK

    def test_update(self):
        class TestView(RetrieveUpdateDestroyAPIView):
            queryset = Item.objects.all()
            shape_class = ItemShape

        request = factory.put(
            "/", {"name": "Updated", "slug": "rud-updated"}, format="json"
        )
        view = TestView.as_view()
        response = view(request, pk=self.item.pk)
        assert response.status_code == status.HTTP_200_OK

    def test_destroy(self):
        class TestView(RetrieveUpdateDestroyAPIView):
            queryset = Item.objects.all()
            shape_class = ItemShape

        request = factory.delete("/")
        view = TestView.as_view()
        response = view(request, pk=self.item.pk)
        assert response.status_code == status.HTTP_204_NO_CONTENT
