import os

from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.http import HttpResponse, HttpResponsePermanentRedirect
from django.middleware.locale import LocaleMiddleware
from django.template import Context, Template
from django.test import SimpleTestCase, override_settings
from django.test.client import RequestFactory
from django.test.utils import override_script_prefix
from django.urls import clear_url_caches, resolve, reverse, translate_url
from django.utils import translation


class PermanentRedirectLocaleMiddleWare(LocaleMiddleware):
    response_redirect_class = HttpResponsePermanentRedirect


@override_settings(
    USE_I18N=True,
    LOCALE_PATHS=[
        os.path.join(os.path.dirname(__file__), "locale"),
    ],
    LANGUAGE_CODE="en-us",
    LANGUAGES=[
        ("nl", "Dutch"),
        ("en", "English"),
        ("pt-br", "Brazilian Portuguese"),
    ],
    MIDDLEWARE=[
        "django.middleware.locale.LocaleMiddleware",
        "django.middleware.common.CommonMiddleware",
    ],
    ROOT_URLCONF="i18n.patterns.urls.default",
    TEMPLATES=[
        {
            "BACKEND": "django.template.backends.django.DjangoTemplates",
            "DIRS": [os.path.join(os.path.dirname(__file__), "templates")],
            "OPTIONS": {
                "context_processors": [
                    "django.template.context_processors.i18n",
                ],
            },
        }
    ],
)
class URLTestCaseBase(SimpleTestCase):
    """
    TestCase base-class for the URL tests.
    """

    def setUp(self):
        # Make sure the cache is empty before we are doing our tests.
        clear_url_caches()
        # Make sure we will leave an empty cache for other testcases.
        self.addCleanup(clear_url_caches)


class URLPrefixTests(URLTestCaseBase):
    """
    Tests if the `i18n_patterns` is adding the prefix correctly.
    """

    def test_not_prefixed(self):
        with translation.override("en"):
            self.assertEqual(reverse("not-prefixed"), "/not-prefixed/")
            self.assertEqual(
                reverse("not-prefixed-included-url"), "/not-prefixed-include/foo/"
            )
        with translation.override("nl"):
            self.assertEqual(reverse("not-prefixed"), "/not-prefixed/")
            self.assertEqual(
                reverse("not-prefixed-included-url"), "/not-prefixed-include/foo/"
            )

    def test_prefixed(self):
        with translation.override("en"):
            self.assertEqual(reverse("prefixed"), "/en/prefixed/")
        with translation.override("nl"):
            self.assertEqual(reverse("prefixed"), "/nl/prefixed/")
        with translation.override(None):
            self.assertEqual(
                reverse("prefixed"), "/%s/prefixed/" % settings.LANGUAGE_CODE
            )

    @override_settings(ROOT_URLCONF="i18n.patterns.urls.wrong")
    def test_invalid_prefix_use(self):
        msg = "Using i18n_patterns in an included URLconf is not allowed."
        with self.assertRaisesMessage(ImproperlyConfigured, msg):
            reverse("account:register")


@override_settings(ROOT_URLCONF="i18n.patterns.urls.disabled")
class URLDisabledTests(URLTestCaseBase):
    @override_settings(USE_I18N=False)
    def test_prefixed_i18n_disabled(self):
        with translation.override("en"):
            self.assertEqual(reverse("prefixed"), "/prefixed/")
        with translation.override("nl"):
            self.assertEqual(reverse("prefixed"), "/prefixed/")


class RequestURLConfTests(SimpleTestCase):
    @override_settings(ROOT_URLCONF="i18n.patterns.urls.path_unused")
    def test_request_urlconf_considered(self):
        request = RequestFactory().get("/nl/")
        request.urlconf = "i18n.patterns.urls.default"
        middleware = LocaleMiddleware(lambda req: HttpResponse())
        with translation.override("nl"):
            middleware.process_request(request)
        self.assertEqual(request.LANGUAGE_CODE, "nl")


@override_settings(ROOT_URLCONF="i18n.patterns.urls.path_unused")
class PathUnusedTests(URLTestCaseBase):
    """
    If no i18n_patterns is used in root URLconfs, then no language activation
    activation happens based on url prefix.
    """

    def test_no_lang_activate(self):
        response = self.client.get("/nl/foo/")
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.headers["content-language"], "en")
        self.assertEqual(response.context["LANGUAGE_CODE"], "en")


class URLTranslationTests(URLTestCaseBase):
    """
    Tests if the pattern-strings are translated correctly (within the
    `i18n_patterns` and the normal `patterns` function).
    """

    def test_no_prefix_translated(self):
        with translation.override("en"):
            self.assertEqual(reverse("no-prefix-translated"), "/translated/")
            self.assertEqual(
                reverse("no-prefix-translated-regex"), "/translated-regex/"
            )
            self.assertEqual(
                reverse("no-prefix-translated-slug", kwargs={"slug": "yeah"}),
                "/translated/yeah/",
            )

        with translation.override("nl"):
            self.assertEqual(reverse("no-prefix-translated"), "/vertaald/")
            self.assertEqual(reverse("no-prefix-translated-regex"), "/vertaald-regex/")
            self.assertEqual(
                reverse("no-prefix-translated-slug", kwargs={"slug": "yeah"}),
                "/vertaald/yeah/",
            )

        with translation.override("pt-br"):
            self.assertEqual(reverse("no-prefix-translated"), "/traduzidos/")
            self.assertEqual(
                reverse("no-prefix-translated-regex"), "/traduzidos-regex/"
            )
            self.assertEqual(
                reverse("no-prefix-translated-slug", kwargs={"slug": "yeah"}),
                "/traduzidos/yeah/",
            )

    def test_users_url(self):
        with translation.override("en"):
            self.assertEqual(reverse("users"), "/en/users/")

        with translation.override("nl"):
            self.assertEqual(reverse("users"), "/nl/gebruikers/")
            self.assertEqual(reverse("prefixed_xml"), "/nl/prefixed.xml")

        with translation.override("pt-br"):
            self.assertEqual(reverse("users"), "/pt-br/usuarios/")

    def test_translate_url_utility(self):
        with translation.override("en"):
            self.assertEqual(
                translate_url("/en/nonexistent/", "nl"), "/en/nonexistent/"
            )
            self.assertEqual(translate_url("/en/users/", "nl"), "/nl/gebruikers/")
            # Namespaced URL
            self.assertEqual(
                translate_url("/en/account/register/", "nl"), "/nl/profiel/registreren/"
            )
            # path() URL pattern
            self.assertEqual(
                translate_url("/en/account/register-as-path/", "nl"),
                "/nl/profiel/registreren-als-pad/",
            )
            self.assertEqual(translation.get_language(), "en")
            # re_path() URL with parameters.
            self.assertEqual(
                translate_url("/en/with-arguments/regular-argument/", "nl"),
                "/nl/with-arguments/regular-argument/",
            )
            self.assertEqual(
                translate_url(
                    "/en/with-arguments/regular-argument/optional.html", "nl"
                ),
                "/nl/with-arguments/regular-argument/optional.html",
            )
            # path() URL with parameter.
            self.assertEqual(
                translate_url("/en/path-with-arguments/regular-argument/", "nl"),
                "/nl/path-with-arguments/regular-argument/",
            )

        with translation.override("nl"):
            self.assertEqual(translate_url("/nl/gebruikers/", "en"), "/en/users/")
            self.assertEqual(translation.get_language(), "nl")

    def test_reverse_translated_with_captured_kwargs(self):
        with translation.override("en"):
            match = resolve("/translated/apo/")
        # Links to the same page in other languages.
        tests = [
            ("nl", "/vertaald/apo/"),
            ("pt-br", "/traduzidos/apo/"),
        ]
        for lang, expected_link in tests:
            with translation.override(lang):
                self.assertEqual(
                    reverse(
                        match.url_name, args=match.args, kwargs=match.captured_kwargs
                    ),
                    expected_link,
                )

    def test_locale_not_interepreted_as_regex(self):
        with translation.override("e("):
            # Would previously error:
            # re.error: missing ), unterminated subpattern at position 1
            reverse("users")


class URLNamespaceTests(URLTestCaseBase):
    """
    Tests if the translations are still working within namespaces.
    """

    def test_account_register(self):
        with translation.override("en"):
            self.assertEqual(reverse("account:register"), "/en/account/register/")
            self.assertEqual(
                reverse("account:register-as-path"), "/en/account/register-as-path/"
            )

        with translation.override("nl"):
            self.assertEqual(reverse("account:register"), "/nl/profiel/registreren/")
            self.assertEqual(
                reverse("account:register-as-path"), "/nl/profiel/registreren-als-pad/"
            )


class URLRedirectTests(URLTestCaseBase):
    """
    Tests if the user gets redirected to the right URL when there is no
    language-prefix in the request URL.
    """

    def test_no_prefix_response(self):
        response = self.client.get("/not-prefixed/")
        self.assertEqual(response.status_code, 200)

    def test_en_redirect(self):
        response = self.client.get(
            "/account/register/", headers={"accept-language": "en"}
        )
        self.assertRedirects(response, "/en/account/register/")

        response = self.client.get(response.headers["location"])
        self.assertEqual(response.status_code, 200)

    def test_en_redirect_wrong_url(self):
        response = self.client.get(
            "/profiel/registreren/", headers={"accept-language": "en"}
        )
        self.assertEqual(response.status_code, 404)

    def test_nl_redirect(self):
        response = self.client.get(
            "/profiel/registreren/", headers={"accept-language": "nl"}
        )
        self.assertRedirects(response, "/nl/profiel/registreren/")

        response = self.client.get(response.headers["location"])
        self.assertEqual(response.status_code, 200)

    def test_nl_redirect_wrong_url(self):
        response = self.client.get(
            "/account/register/", headers={"accept-language": "nl"}
        )
        self.assertEqual(response.status_code, 404)

    def test_pt_br_redirect(self):
        response = self.client.get(
            "/conta/registre-se/", headers={"accept-language": "pt-br"}
        )
        self.assertRedirects(response, "/pt-br/conta/registre-se/")

        response = self.client.get(response.headers["location"])
        self.assertEqual(response.status_code, 200)

    def test_pl_pl_redirect(self):
        # language from outside of the supported LANGUAGES list
        response = self.client.get(
            "/account/register/", headers={"accept-language": "pl-pl"}
        )
        self.assertRedirects(response, "/en/account/register/")

        response = self.client.get(response.headers["location"])
        self.assertEqual(response.status_code, 200)

    @override_settings(
        MIDDLEWARE=[
            "i18n.patterns.tests.PermanentRedirectLocaleMiddleWare",
            "django.middleware.common.CommonMiddleware",
        ],
    )
    def test_custom_redirect_class(self):
        response = self.client.get(
            "/account/register/", headers={"accept-language": "en"}
        )
        self.assertRedirects(response, "/en/account/register/", 301)


class URLVaryAcceptLanguageTests(URLTestCaseBase):
    """
    'Accept-Language' is not added to the Vary header when using prefixed URLs.
    """

    def test_no_prefix_response(self):
        response = self.client.get("/not-prefixed/")
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.get("Vary"), "Accept-Language")

    def test_en_redirect(self):
        """
        The redirect to a prefixed URL depends on 'Accept-Language' and
        'Cookie', but once prefixed no header is set.
        """
        response = self.client.get(
            "/account/register/", headers={"accept-language": "en"}
        )
        self.assertRedirects(response, "/en/account/register/")
        self.assertEqual(response.get("Vary"), "Accept-Language, Cookie")

        response = self.client.get(response.headers["location"])
        self.assertEqual(response.status_code, 200)
        self.assertFalse(response.get("Vary"))


class URLRedirectWithoutTrailingSlashTests(URLTestCaseBase):
    """
    Tests the redirect when the requested URL doesn't end with a slash
    (`settings.APPEND_SLASH=True`).
    """

    def test_not_prefixed_redirect(self):
        response = self.client.get("/not-prefixed", headers={"accept-language": "en"})
        self.assertRedirects(response, "/not-prefixed/", 301)

    def test_en_redirect(self):
        response = self.client.get(
            "/account/register", headers={"accept-language": "en"}, follow=True
        )
        # We only want one redirect, bypassing CommonMiddleware
        self.assertEqual(response.redirect_chain, [("/en/account/register/", 302)])
        self.assertRedirects(response, "/en/account/register/", 302)

        response = self.client.get(
            "/prefixed.xml", headers={"accept-language": "en"}, follow=True
        )
        self.assertRedirects(response, "/en/prefixed.xml", 302)


class URLRedirectWithoutTrailingSlashSettingTests(URLTestCaseBase):
    """
    Tests the redirect when the requested URL doesn't end with a slash
    (`settings.APPEND_SLASH=False`).
    """

    @override_settings(APPEND_SLASH=False)
    def test_not_prefixed_redirect(self):
        response = self.client.get("/not-prefixed", headers={"accept-language": "en"})
        self.assertEqual(response.status_code, 404)

    @override_settings(APPEND_SLASH=False)
    def test_en_redirect(self):
        response = self.client.get(
            "/account/register-without-slash", headers={"accept-language": "en"}
        )
        self.assertRedirects(response, "/en/account/register-without-slash", 302)

        response = self.client.get(response.headers["location"])
        self.assertEqual(response.status_code, 200)


class URLResponseTests(URLTestCaseBase):
    """Tests if the response has the correct language code."""

    def test_not_prefixed_with_prefix(self):
        response = self.client.get("/en/not-prefixed/")
        self.assertEqual(response.status_code, 404)

    def test_en_url(self):
        response = self.client.get("/en/account/register/")
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.headers["content-language"], "en")
        self.assertEqual(response.context["LANGUAGE_CODE"], "en")

    def test_nl_url(self):
        response = self.client.get("/nl/profiel/registreren/")
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.headers["content-language"], "nl")
        self.assertEqual(response.context["LANGUAGE_CODE"], "nl")

    def test_wrong_en_prefix(self):
        response = self.client.get("/en/profiel/registreren/")
        self.assertEqual(response.status_code, 404)

    def test_wrong_nl_prefix(self):
        response = self.client.get("/nl/account/register/")
        self.assertEqual(response.status_code, 404)

    def test_pt_br_url(self):
        response = self.client.get("/pt-br/conta/registre-se/")
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.headers["content-language"], "pt-br")
        self.assertEqual(response.context["LANGUAGE_CODE"], "pt-br")

    def test_en_path(self):
        response = self.client.get("/en/account/register-as-path/")
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.headers["content-language"], "en")
        self.assertEqual(response.context["LANGUAGE_CODE"], "en")

    def test_nl_path(self):
        response = self.client.get("/nl/profiel/registreren-als-pad/")
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.headers["content-language"], "nl")
        self.assertEqual(response.context["LANGUAGE_CODE"], "nl")


@override_settings(ROOT_URLCONF="i18n.urls_default_unprefixed", LANGUAGE_CODE="nl")
class URLPrefixedFalseTranslatedTests(URLTestCaseBase):
    def test_translated_path_unprefixed_language_other_than_accepted_header(self):
        response = self.client.get("/gebruikers/", headers={"accept-language": "en"})
        self.assertEqual(response.status_code, 200)

    def test_translated_path_unprefixed_language_other_than_cookie_language(self):
        self.client.cookies.load({settings.LANGUAGE_COOKIE_NAME: "en"})
        response = self.client.get("/gebruikers/")
        self.assertEqual(response.status_code, 200)

    def test_translated_path_prefixed_language_other_than_accepted_header(self):
        response = self.client.get("/en/users/", headers={"accept-language": "nl"})
        self.assertEqual(response.status_code, 200)

    def test_translated_path_prefixed_language_other_than_cookie_language(self):
        self.client.cookies.load({settings.LANGUAGE_COOKIE_NAME: "nl"})
        response = self.client.get("/en/users/")
        self.assertEqual(response.status_code, 200)


class URLRedirectWithScriptAliasTests(URLTestCaseBase):
    """
    #21579 - LocaleMiddleware should respect the script prefix.
    """

    def test_language_prefix_with_script_prefix(self):
        prefix = "/script_prefix"
        with override_script_prefix(prefix):
            response = self.client.get(
                "/prefixed/", headers={"accept-language": "en"}, SCRIPT_NAME=prefix
            )
            self.assertRedirects(
                response, "%s/en/prefixed/" % prefix, target_status_code=404
            )


class URLTagTests(URLTestCaseBase):
    """
    Test if the language tag works.
    """

    def test_strings_only(self):
        t = Template(
            """{% load i18n %}
            {% language 'nl' %}{% url 'no-prefix-translated' %}{% endlanguage %}
            {% language 'pt-br' %}{% url 'no-prefix-translated' %}{% endlanguage %}"""
        )
        self.assertEqual(
            t.render(Context({})).strip().split(), ["/vertaald/", "/traduzidos/"]
        )

    def test_context(self):
        ctx = Context({"lang1": "nl", "lang2": "pt-br"})
        tpl = Template(
            """{% load i18n %}
            {% language lang1 %}{% url 'no-prefix-translated' %}{% endlanguage %}
            {% language lang2 %}{% url 'no-prefix-translated' %}{% endlanguage %}"""
        )
        self.assertEqual(
            tpl.render(ctx).strip().split(), ["/vertaald/", "/traduzidos/"]
        )

    def test_args(self):
        tpl = Template(
            """
            {% load i18n %}
            {% language 'nl' %}
            {% url 'no-prefix-translated-slug' 'apo' %}{% endlanguage %}
            {% language 'pt-br' %}
            {% url 'no-prefix-translated-slug' 'apo' %}{% endlanguage %}
            """
        )
        self.assertEqual(
            tpl.render(Context({})).strip().split(),
            ["/vertaald/apo/", "/traduzidos/apo/"],
        )

    def test_kwargs(self):
        tpl = Template(
            """
            {% load i18n %}
            {% language 'nl'  %}
            {% url 'no-prefix-translated-slug' slug='apo' %}{% endlanguage %}
            {% language 'pt-br' %}
            {% url 'no-prefix-translated-slug' slug='apo' %}{% endlanguage %}
            """
        )
        self.assertEqual(
            tpl.render(Context({})).strip().split(),
            ["/vertaald/apo/", "/traduzidos/apo/"],
        )
