from unittest import mock

from django.core import checks
from django.test import RequestFactory, TestCase
from django.test.utils import override_settings
from django.urls import path
from django.urls.exceptions import NoReverseMatch

from wagtail.contrib.routable_page.templatetags.wagtailroutablepage_tags import (
    routablepageurl,
)
from wagtail.models import Page, Site
from wagtail.test.routablepage.models import (
    RoutablePageTest,
    RoutablePageWithOverriddenIndexRouteTest,
)


class TestRoutablePage(TestCase):
    model = RoutablePageTest

    def setUp(self):
        self.home_page = Page.objects.get(id=2)
        self.routable_page = self.home_page.add_child(
            instance=self.model(
                title="Routable Page",
                live=True,
            )
        )

    def test_resolve_index_route_view(self):
        view, args, kwargs = self.routable_page.resolve_subpage("/")

        self.assertEqual(view, self.routable_page.index_route)
        self.assertEqual(args, ())
        self.assertEqual(kwargs, {})

    def test_resolve_archive_by_year_view(self):
        view, args, kwargs = self.routable_page.resolve_subpage("/archive/year/2014/")

        self.assertEqual(view, self.routable_page.archive_by_year)
        self.assertEqual(args, ("2014",))
        self.assertEqual(kwargs, {})

    def test_resolve_archive_by_author_view(self):
        view, args, kwargs = self.routable_page.resolve_subpage(
            "/archive/author/joe-bloggs/"
        )

        self.assertEqual(view, self.routable_page.archive_by_author)
        self.assertEqual(args, ())
        self.assertEqual(kwargs, {"author_slug": "joe-bloggs"})

    def test_resolve_archive_by_title_view(self):
        view, args, kwargs = self.routable_page.resolve_subpage(
            "/archive/title/some-title/"
        )

        self.assertEqual(view, self.routable_page.archive_by_title)
        self.assertEqual(args, ())
        self.assertEqual(kwargs, {"title": "some-title"})

    def test_resolve_archive_by_category_view(self):
        view, args, kwargs = self.routable_page.resolve_subpage(
            "/archive/category/some-category/"
        )

        self.assertEqual(view, self.routable_page.archive_by_category)
        self.assertEqual(args, ())
        self.assertEqual(kwargs, {"category_slug": "some-category"})

    def test_resolve_external_view(self):
        view, args, kwargs = self.routable_page.resolve_subpage("/external/joe-bloggs/")

        self.assertEqual(view, self.routable_page.external_view)
        self.assertEqual(args, ("joe-bloggs",))
        self.assertEqual(kwargs, {})

    def test_resolve_external_view_other_route(self):
        view, args, kwargs = self.routable_page.resolve_subpage("/external-no-arg/")

        self.assertEqual(view, self.routable_page.external_view)
        self.assertEqual(args, ())
        self.assertEqual(kwargs, {})

    def test_reverse_index_route_view(self):
        url = self.routable_page.reverse_subpage("index_route")

        self.assertEqual(url, "")

    def test_reverse_archive_by_year_view(self):
        url = self.routable_page.reverse_subpage("archive_by_year", args=("2014",))

        self.assertEqual(url, "archive/year/2014/")

    def test_reverse_archive_by_author_view(self):
        url = self.routable_page.reverse_subpage(
            "archive_by_author", kwargs={"author_slug": "joe-bloggs"}
        )

        self.assertEqual(url, "archive/author/joe-bloggs/")

    def test_reverse_archive_by_title_view(self):
        url = self.routable_page.reverse_subpage(
            "archive_by_title", kwargs={"title": "some-title"}
        )

        self.assertEqual(url, "archive/title/some-title/")

    def test_reverse_overridden_name(self):
        url = self.routable_page.reverse_subpage("name_overridden")

        self.assertEqual(url, "override-name-test/")

    def test_reverse_overridden_name_default_doesnt_work(self):
        with self.assertRaises(NoReverseMatch):
            self.routable_page.reverse_subpage("override_name_test")

    def test_reverse_external_view(self):
        url = self.routable_page.reverse_subpage("external_view", args=("joe-bloggs",))

        self.assertEqual(url, "external/joe-bloggs/")

    def test_reverse_external_view_other_route(self):
        url = self.routable_page.reverse_subpage("external_view")

        self.assertEqual(url, "external-no-arg/")

    def test_get_index_route_view(self):
        with self.assertTemplateUsed("routablepagetests/routable_page_test.html"):
            response = self.client.get(self.routable_page.url)
            context = response.context_data
            self.assertEqual(
                (context["page"], context["self"], context.get("foo")),
                (self.routable_page, self.routable_page, None),
            )
            self.assertEqual(
                context["request"].routable_resolver_match.url_name, "index_route"
            )

    def test_get_render_method_route_view(self):
        with self.assertTemplateUsed("routablepagetests/routable_page_test.html"):
            response = self.client.get(self.routable_page.url + "render-method-test/")
            context = response.context_data
            self.assertEqual(
                (context["page"], context["self"], context["foo"]),
                (self.routable_page, None, "bar"),
            )

    def test_get_render_method_route_view_with_custom_template(self):
        with self.assertTemplateUsed(
            "routablepagetests/routable_page_test_alternate.html"
        ):
            response = self.client.get(
                self.routable_page.url + "render-method-test-custom-template/"
            )
            context = response.context_data
            self.assertEqual(
                (context["page"], context["self"], context["foo"]),
                (self.routable_page, 1, "fighters"),
            )

    def test_get_render_method_route_view_with_arg(self):
        response = self.client.get(
            self.routable_page.url + "render-method-with-arg/foo/"
        )
        resolver_match = response.context_data["request"].routable_resolver_match
        self.assertEqual(resolver_match.url_name, "render_method_test_with_arg")
        self.assertEqual(resolver_match.kwargs, {"slug": "foo"})

    def test_get_routable_page_with_overridden_index_route(self):
        page = self.home_page.add_child(
            instance=RoutablePageWithOverriddenIndexRouteTest(
                title="Routable Page with overridden index", live=True
            )
        )
        response = self.client.get(page.url)
        self.assertContains(response, "OVERRIDDEN INDEX ROUTE")
        self.assertNotContains(response, "DEFAULT PAGE TEMPLATE")

    def test_get_archive_by_year_view(self):
        response = self.client.get(self.routable_page.url + "archive/year/2014/")

        self.assertContains(response, "ARCHIVE BY YEAR: 2014")

    def test_earlier_view_takes_precedence(self):
        response = self.client.get(self.routable_page.url + "archive/year/1984/")

        self.assertContains(response, "we were always at war with eastasia")

    def test_get_archive_by_author_view(self):
        response = self.client.get(
            self.routable_page.url + "archive/author/joe-bloggs/"
        )

        self.assertContains(response, "ARCHIVE BY AUTHOR: joe-bloggs")

    def test_get_archive_by_title_view(self):
        response = self.client.get(self.routable_page.url + "archive/title/some-title/")

        self.assertContains(response, "ARCHIVE BY TITLE: some-title")

    def test_get_archive_by_category_view(self):
        response = self.client.get(
            self.routable_page.url + "archive/category/some-category/"
        )

        self.assertContains(response, "ARCHIVE BY CATEGORY: some-category")

    def test_get_external_view(self):
        response = self.client.get(self.routable_page.url + "external/joe-bloggs/")

        self.assertContains(response, "EXTERNAL VIEW: joe-bloggs")

    def test_get_external_view_other_route(self):
        response = self.client.get(self.routable_page.url + "external-no-arg/")

        self.assertContains(response, "EXTERNAL VIEW: ARG NOT SET")

    def test_routable_page_can_have_instance_bound_descriptors(self):
        # This descriptor pretends that it does not exist in the class, hence
        # it raises an AttributeError when class bound. This is, for instance,
        # the behaviour of django's FileFields.
        class InstanceDescriptor:
            def __get__(self, instance, cls=None):
                if instance is None:
                    raise AttributeError
                return "value"

            def __set__(self, instance, value):
                raise AttributeError

        try:
            RoutablePageTest.descriptor = InstanceDescriptor()
            RoutablePageTest.get_subpage_urls()
        finally:
            del RoutablePageTest.descriptor

    def test_warning_path_with_regex(self):
        route = path(r"^foo/$", lambda request: None, name="path_with_regex")

        warning = checks.Warning(
            "Your URL pattern path_with_regex has a route that contains '(?P<', begins with a '^', or ends with a '$'.",
            hint="Decorate your view with re_path if you want to use regexp.",
            obj=RoutablePageTest,
            id="wagtailroutablepage.W001",
        )
        with mock.patch.object(
            RoutablePageTest, "get_subpage_urls", return_value=[route]
        ):
            self.assertEqual(RoutablePageTest.check(), [warning])


class TestRoutablePageTemplateTag(TestCase):
    def setUp(self):
        self.home_page = Page.objects.get(id=2)
        self.routable_page = self.home_page.add_child(
            instance=RoutablePageTest(
                title="Routable Page",
                live=True,
            )
        )

        self.rf = RequestFactory()
        self.request = self.rf.get(self.routable_page.url)
        self.context = {"request": self.request}

    def test_templatetag_reverse_index_route(self):
        url = routablepageurl(self.context, self.routable_page, "index_route")
        self.assertEqual(url, "/%s/" % self.routable_page.slug)

    def test_templatetag_reverse_archive_by_year_view(self):
        url = routablepageurl(
            self.context, self.routable_page, "archive_by_year", "2014"
        )

        self.assertEqual(url, "/%s/archive/year/2014/" % self.routable_page.slug)

    def test_templatetag_reverse_archive_by_author_view(self):
        url = routablepageurl(
            self.context,
            self.routable_page,
            "archive_by_author",
            author_slug="joe-bloggs",
        )

        self.assertEqual(
            url, "/%s/archive/author/joe-bloggs/" % self.routable_page.slug
        )

    def test_templatetag_reverse_archive_by_title_view(self):
        url = routablepageurl(
            self.context, self.routable_page, "archive_by_title", title="some-title"
        )

        self.assertEqual(url, "/%s/archive/title/some-title/" % self.routable_page.slug)

    def test_templatetag_reverse_archive_by_category_view(self):
        url = routablepageurl(
            self.context,
            self.routable_page,
            "archive_by_category",
            category_slug="some-category",
        )

        self.assertEqual(
            url, "/%s/archive/category/some-category/" % self.routable_page.slug
        )

    def test_templatetag_reverse_external_view(self):
        url = routablepageurl(
            self.context, self.routable_page, "external_view", "joe-bloggs"
        )

        self.assertEqual(url, "/%s/external/joe-bloggs/" % self.routable_page.slug)

    def test_templatetag_reverse_external_view_without_append_slash(self):
        with mock.patch("wagtail.models.WAGTAIL_APPEND_SLASH", False):
            url = routablepageurl(
                self.context, self.routable_page, "external_view", "joe-bloggs"
            )
            expected = "/" + self.routable_page.slug + "/" + "external/joe-bloggs/"

        self.assertEqual(url, expected)


@override_settings(ALLOWED_HOSTS=["testserver", "localhost", "development.local"])
class TestRoutablePageTemplateTagForSecondSiteAtSameRoot(TestCase):
    """
    When multiple sites exist on the same root page, relative URLs within that subtree should
    omit the domain, in line with #4390
    """

    def setUp(self):
        default_site = Site.objects.get(is_default_site=True)
        second_site = Site.objects.create(  # add another site with the same root page
            hostname="development.local",
            port=default_site.port,
            root_page_id=default_site.root_page_id,
        )

        self.home_page = Page.objects.get(id=2)
        self.routable_page = self.home_page.add_child(
            instance=RoutablePageTest(
                title="Routable Page",
                live=True,
            )
        )

        self.rf = RequestFactory()
        self.request = self.rf.get(self.routable_page.url)
        self.context = {"request": self.request}
        self.request.META["HTTP_HOST"] = second_site.hostname
        self.request.META["SERVER_PORT"] = second_site.port

    def test_templatetag_reverse_index_route(self):
        url = routablepageurl(self.context, self.routable_page, "index_route")
        self.assertEqual(url, "/%s/" % self.routable_page.slug)

    def test_templatetag_reverse_archive_by_year_view(self):
        url = routablepageurl(
            self.context, self.routable_page, "archive_by_year", "2014"
        )

        self.assertEqual(url, "/%s/archive/year/2014/" % self.routable_page.slug)

    def test_templatetag_reverse_archive_by_author_view(self):
        url = routablepageurl(
            self.context,
            self.routable_page,
            "archive_by_author",
            author_slug="joe-bloggs",
        )

        self.assertEqual(
            url, "/%s/archive/author/joe-bloggs/" % self.routable_page.slug
        )

    def test_templatetag_reverse_archive_by_title_view(self):
        url = routablepageurl(
            self.context, self.routable_page, "archive_by_title", title="some-title"
        )

        self.assertEqual(url, "/%s/archive/title/some-title/" % self.routable_page.slug)

    def test_templatetag_reverse_archive_by_category_view(self):
        url = routablepageurl(
            self.context,
            self.routable_page,
            "archive_by_category",
            category_slug="some-category",
        )

        self.assertEqual(
            url, "/%s/archive/category/some-category/" % self.routable_page.slug
        )

    def test_templatetag_reverse_external_view(self):
        url = routablepageurl(
            self.context, self.routable_page, "external_view", "joe-bloggs"
        )

        self.assertEqual(url, "/%s/external/joe-bloggs/" % self.routable_page.slug)

    def test_templatetag_reverse_external_view_without_append_slash(self):
        with mock.patch("wagtail.models.WAGTAIL_APPEND_SLASH", False):
            url = routablepageurl(
                self.context, self.routable_page, "external_view", "joe-bloggs"
            )
            expected = "/" + self.routable_page.slug + "/" + "external/joe-bloggs/"

        self.assertEqual(url, expected)


@override_settings(ALLOWED_HOSTS=["testserver", "localhost", "events.local"])
class TestRoutablePageTemplateTagForSecondSiteAtDifferentRoot(TestCase):
    """
    When multiple sites exist, relative URLs between such sites should include the domain portion
    """

    def setUp(self):
        self.home_page = Page.objects.get(id=2)

        events_page = self.home_page.add_child(instance=Page(title="Events", live=True))

        second_site = Site.objects.create(
            hostname="events.local",
            port=80,
            root_page=events_page,
        )

        self.routable_page = self.home_page.add_child(
            instance=RoutablePageTest(
                title="Routable Page",
                live=True,
            )
        )

        self.rf = RequestFactory()
        self.request = self.rf.get(self.routable_page.url)
        self.context = {"request": self.request}

        self.request.META["HTTP_HOST"] = second_site.hostname
        self.request.META["SERVER_PORT"] = second_site.port

    def test_templatetag_reverse_index_route(self):
        url = routablepageurl(self.context, self.routable_page, "index_route")
        self.assertEqual(url, "http://localhost/%s/" % self.routable_page.slug)

    def test_templatetag_reverse_archive_by_year_view(self):
        url = routablepageurl(
            self.context, self.routable_page, "archive_by_year", "2014"
        )

        self.assertEqual(
            url, "http://localhost/%s/archive/year/2014/" % self.routable_page.slug
        )

    def test_templatetag_reverse_archive_by_author_view(self):
        url = routablepageurl(
            self.context,
            self.routable_page,
            "archive_by_author",
            author_slug="joe-bloggs",
        )

        self.assertEqual(
            url,
            "http://localhost/%s/archive/author/joe-bloggs/" % self.routable_page.slug,
        )

    def test_templatetag_reverse_archive_by_title_view(self):
        url = routablepageurl(
            self.context, self.routable_page, "archive_by_title", title="some-title"
        )

        self.assertEqual(
            url,
            "http://localhost/%s/archive/title/some-title/" % self.routable_page.slug,
        )

    def test_templatetag_reverse_archive_by_category_view(self):
        url = routablepageurl(
            self.context,
            self.routable_page,
            "archive_by_category",
            category_slug="some-category",
        )

        self.assertEqual(
            url,
            "http://localhost/%s/archive/category/some-category/"
            % self.routable_page.slug,
        )

    def test_templatetag_reverse_external_view(self):
        url = routablepageurl(
            self.context, self.routable_page, "external_view", "joe-bloggs"
        )

        self.assertEqual(
            url, "http://localhost/%s/external/joe-bloggs/" % self.routable_page.slug
        )

    def test_templatetag_reverse_external_view_without_append_slash(self):
        with mock.patch("wagtail.models.WAGTAIL_APPEND_SLASH", False):
            url = routablepageurl(
                self.context, self.routable_page, "external_view", "joe-bloggs"
            )
            expected = (
                "http://localhost/"
                + self.routable_page.slug
                + "/"
                + "external/joe-bloggs/"
            )

        self.assertEqual(url, expected)
