from datetime import timedelta

from django.core.paginator import Page, Paginator
from django.http import Http404
from django.template import Context, Template
from django.test import RequestFactory, TestCase
from django.test.utils import override_settings
from infinite_scroll_pagination.serializers import to_page_key

from spirit.core.utils.paginator.yt_paginator import InvalidPage, YTPage, YTPaginator

from ...comment.models import Comment
from ..tags import paginator as ttag_paginator
from ..tags.paginator import render_paginator
from ..tests import utils
from ..utils import paginator
from ..utils.paginator import infinite_paginator, paginate, yt_paginate


class UtilsPaginatorTest(TestCase):
    def setUp(self):
        utils.cache_clear()

    def test_paginator_page(self):
        per_page = 15
        obj_number = 1
        self.assertEqual(
            paginator.get_page_number(obj_number=obj_number, per_page=per_page), 1
        )
        obj_number = per_page
        self.assertEqual(
            paginator.get_page_number(obj_number=obj_number, per_page=per_page), 1
        )
        obj_number = per_page - 1
        self.assertEqual(
            paginator.get_page_number(obj_number=obj_number, per_page=per_page), 1
        )
        obj_number = per_page + 1
        self.assertEqual(
            paginator.get_page_number(obj_number=obj_number, per_page=per_page), 2
        )

    def test_paginator_url(self):
        per_page = 15
        obj_number = 1
        page_var = "page"
        url = "/path/"
        first_page = url + "#c" + str(obj_number)
        self.assertEqual(
            paginator.get_url(url, obj_number, per_page, page_var), first_page
        )
        obj_number = 16
        expected = "{url}?{page_var}={page_num}#c{obj_number}".format(
            url=url, page_var=page_var, page_num=2, obj_number=obj_number
        )
        self.assertEqual(
            paginator.get_url(url, obj_number, per_page, page_var), expected
        )


class UtilsInfinitePaginatorTest(TestCase):
    def setUp(self):
        utils.cache_clear()
        self.user = utils.create_user()
        self.topic = utils.create_topic(utils.create_category())

        for _ in range(300):
            utils.create_comment(user=self.user, topic=self.topic)

        self.queryset = Comment.objects.all().order_by("-pk")

    def test_paginate(self):
        # first page
        req = RequestFactory().get("/")
        page = infinite_paginator.paginate(
            req, self.queryset, per_page=15, lookup_field="date"
        )
        page_last = list(self.queryset[:15])[-1]
        self.assertEqual(
            page.next_page(), {"pk": page_last.pk, "value": (page_last.date,)}
        )

        # second page
        page_last = list(self.queryset[:15])[-1]
        param = to_page_key(value=page_last.date, pk=page_last.pk)
        req = RequestFactory().get("/?p=%s" % param)
        page = infinite_paginator.paginate(
            req, self.queryset, per_page=15, lookup_field="date", page_var="p"
        )
        second_page_last = list(self.queryset[15:30])[-1]
        self.assertEqual(
            page.next_page(),
            {"pk": second_page_last.pk, "value": (second_page_last.date,)},
        )

        # invalid (id) page
        last = self.queryset.order_by("-date").last()
        param = to_page_key(value=last.date - timedelta(days=123), pk=last.pk)
        req = RequestFactory().get("/?p=%s" % param)
        self.assertRaises(
            Http404,
            infinite_paginator.paginate,
            req,
            self.queryset,
            per_page=15,
            lookup_field="date",
            page_var="p",
        )

        # empty page
        valid = self.queryset.last()
        param = to_page_key(value=valid.date, pk=valid.pk)
        req = RequestFactory().get("/?p=%s" % param)
        self.assertRaises(
            Http404,
            infinite_paginator.paginate,
            req,
            self.queryset.none(),
            per_page=15,
            lookup_field="date",
            page_var="p",
        )

        # empty first page
        req = RequestFactory().get("/")
        page = infinite_paginator.paginate(
            req, self.queryset.none(), per_page=15, lookup_field="date"
        )
        self.assertEqual(len(page), 0)


class UtilsYTPaginatorTests(TestCase):
    def setUp(self):
        utils.cache_clear()
        self.user = utils.create_user()
        self.topic = utils.create_topic(utils.create_category())

        for _ in range(300):
            utils.create_comment(user=self.user, topic=self.topic)

        self.queryset = Comment.objects.all()

    def test_yt_paginator_page(self):
        yt_paginator = YTPaginator(self.queryset, per_page=10)
        page = yt_paginator.page(1)
        self.assertEqual(page.number, 1)
        self.assertListEqual([i for i in page], list(self.queryset[:10]))

        # empty first page
        yt_paginator = YTPaginator(self.queryset.none(), per_page=10)
        self.assertListEqual([i for i in yt_paginator.page(1)], [])

    def test_yt_paginator_page_invalid(self):
        yt_paginator = YTPaginator(self.queryset, per_page=10)
        self.assertRaises(InvalidPage, lambda: yt_paginator.page("one"))
        self.assertRaises(InvalidPage, lambda: yt_paginator.page(-1))
        self.assertRaises(InvalidPage, lambda: yt_paginator.page(5000))

    def tests_yt_paginator_num_pages(self):
        yt_paginator = YTPaginator(self.queryset, per_page=10)
        page = yt_paginator.page(1)

        for page_num in range(1, 30 + 1):
            page._num_pages = None
            page._max_pages = page_num
            self.assertEqual(page.num_pages, page_num)

        page._num_pages = None
        page._max_pages = 8
        self.assertEqual(page.num_pages, 8)

        page._num_pages = None
        page._max_pages = 3000
        self.assertEqual(page.num_pages, 30)

    @override_settings(ST_YT_PAGINATOR_PAGE_RANGE=3)
    def test_yt_paginator_page_range(self):
        # 10 pages
        yt_paginator = YTPaginator(list(range(0, 100)), per_page=10)

        page = yt_paginator.page(1)
        self.assertListEqual(list(page.page_range), [1, 2, 3, 4, 5, 6, 7])

        page = yt_paginator.page(4)
        self.assertListEqual(list(page.page_range), [1, 2, 3, 4, 5, 6, 7])

        page = yt_paginator.page(7)
        self.assertListEqual(list(page.page_range), [4, 5, 6, 7, 8, 9, 10])

        page = yt_paginator.page(10)
        self.assertListEqual(list(page.page_range), [4, 5, 6, 7, 8, 9, 10])

        # 2 pages
        yt_paginator = YTPaginator(list(range(0, 20)), per_page=10)

        page = yt_paginator.page(1)
        self.assertListEqual(list(page.page_range), [1, 2])

        page = yt_paginator.page(2)
        self.assertListEqual(list(page.page_range), [1, 2])


class UtilsYTPaginatorTemplateTagsTests(TestCase):
    def setUp(self):
        utils.cache_clear()

    def tests_yt_paginate(self):
        # first page
        items = list(range(0, 20))
        page = yt_paginate(items, per_page=10)
        self.assertIsInstance(page, YTPage)
        self.assertEqual(list(page), items[:10])

        # second page
        page = yt_paginate(items, per_page=10, page_number=2)
        self.assertEqual(list(page), items[10:20])

        # invalid page
        self.assertRaises(Http404, yt_paginate, items, per_page=10, page_number=99)

        # empty first page
        page = yt_paginate([], per_page=10)
        self.assertListEqual(list(page), [])

    def tests_render_yt_paginator(self):
        def mock_render(template, context):
            return template, context

        req = RequestFactory().get("/")
        context = {"request": req}
        items = list(range(0, 20))
        page = YTPaginator(items, per_page=10).page(1)

        org_render, ttag_paginator.render_to_string = (
            ttag_paginator.render_to_string,
            mock_render,
        )
        try:
            template, context2 = render_paginator(context, page)
            self.assertDictEqual(
                context2,
                {"page": page, "page_var": "page", "hashtag": "", "extra_query": ""},
            )
            self.assertEqual(template, "spirit/utils/paginator/_yt_paginator.html")
        finally:
            ttag_paginator.render_to_string = org_render

    def tests_render_yt_paginator_extra(self):
        def mock_render(template, context):
            return template, context

        req = RequestFactory().get("/?foo_page=1&extra=foo")
        context = {"request": req}
        items = list(range(0, 20))
        page = YTPaginator(items, per_page=10).page(1)

        org_render, ttag_paginator.render_to_string = (
            ttag_paginator.render_to_string,
            mock_render,
        )
        try:
            template, context2 = render_paginator(
                context, page, page_var="foo_page", hashtag="c20"
            )
            self.assertDictEqual(
                context2,
                {
                    "page": page,
                    "page_var": "foo_page",
                    "hashtag": "#c20",
                    "extra_query": "&extra=foo",
                },
            )
            self.assertEqual(template, "spirit/utils/paginator/_yt_paginator.html")
        finally:
            ttag_paginator.render_to_string = org_render


class UtilsPaginatorTemplateTagsTests(TestCase):
    def setUp(self):
        utils.cache_clear()

    def tests_paginate(self):
        # first page
        items = list(range(0, 20))
        page = paginate(items, per_page=10)
        self.assertIsInstance(page, Page)
        self.assertEqual(list(page), items[:10])

        # second page
        page = paginate(items, per_page=10, page_number=2)
        self.assertEqual(list(page), items[10:20])

        # invalid page
        self.assertRaises(Http404, paginate, items, per_page=10, page_number=99)

        # empty first page
        page = paginate([], per_page=10)
        self.assertListEqual(list(page), [])

    def tests_render_paginator_tag(self):
        """
        Minimal test to check it works
        """
        req = RequestFactory().get("/")
        items = list(range(0, 20))
        page = Paginator(items, per_page=10).page(1)
        Template("{% load spirit_tags %}{% render_paginator page %}").render(
            Context({"request": req, "page": page})
        )

    def tests_render_paginator(self):
        def mock_render(template, context):
            return template, context

        req = RequestFactory().get("/")
        context = {"request": req}
        items = list(range(0, 20))
        page = Paginator(items, per_page=10).page(1)

        org_render, ttag_paginator.render_to_string = (
            ttag_paginator.render_to_string,
            mock_render,
        )
        try:
            template, context2 = render_paginator(context, page)
            self.assertDictEqual(
                context2,
                {"page": page, "page_var": "page", "hashtag": "", "extra_query": ""},
            )
            self.assertEqual(template, "spirit/utils/paginator/_paginator.html")
        finally:
            ttag_paginator.render_to_string = org_render

    def tests_render_paginator_extra(self):
        def mock_render(template, context):
            return template, context

        req = RequestFactory().get("/?foo_page=1&extra=foo")
        context = {"request": req}
        items = list(range(0, 20))
        page = Paginator(items, per_page=10).page(1)

        org_render, ttag_paginator.render_to_string = (
            ttag_paginator.render_to_string,
            mock_render,
        )
        try:
            template, context2 = render_paginator(
                context, page, page_var="foo_page", hashtag="c20"
            )
            self.assertDictEqual(
                context2,
                {
                    "page": page,
                    "page_var": "foo_page",
                    "hashtag": "#c20",
                    "extra_query": "&extra=foo",
                },
            )
            self.assertEqual(template, "spirit/utils/paginator/_paginator.html")
        finally:
            ttag_paginator.render_to_string = org_render
