import json
from urllib.parse import urlencode
from xml.dom.minidom import parseString

from django.contrib.auth.decorators import login_required, permission_required
from django.core import mail
from django.core.exceptions import ValidationError
from django.forms import fields
from django.forms.forms import Form
from django.http import (
    HttpResponse,
    HttpResponseBadRequest,
    HttpResponseNotAllowed,
    HttpResponseNotFound,
    HttpResponseRedirect,
)
from django.shortcuts import render
from django.template import Context, Template
from django.test import Client
from django.utils.decorators import method_decorator
from django.views.generic import TemplateView


def get_view(request):
    "A simple view that expects a GET request, and returns a rendered template"
    t = Template("This is a test. {{ var }} is the value.", name="GET Template")
    c = Context({"var": request.GET.get("var", 42)})

    return HttpResponse(t.render(c))


async def async_get_view(request):
    return HttpResponse(b"GET content.")


def trace_view(request):
    """
    A simple view that expects a TRACE request and echoes its status line.

    TRACE requests should not have an entity; the view will return a 400 status
    response if it is present.
    """
    if request.method.upper() != "TRACE":
        return HttpResponseNotAllowed("TRACE")
    elif request.body:
        return HttpResponseBadRequest("TRACE requests MUST NOT include an entity")
    else:
        protocol = request.META["SERVER_PROTOCOL"]
        t = Template(
            "{{ method }} {{ uri }} {{ version }}",
            name="TRACE Template",
        )
        c = Context(
            {
                "method": request.method,
                "uri": request.path,
                "version": protocol,
            }
        )
        return HttpResponse(t.render(c))


def put_view(request):
    if request.method == "PUT":
        t = Template("Data received: {{ data }} is the body.", name="PUT Template")
        c = Context(
            {
                "Content-Length": request.META["CONTENT_LENGTH"],
                "data": request.body.decode(),
            }
        )
    else:
        t = Template("Viewing GET page.", name="Empty GET Template")
        c = Context()
    return HttpResponse(t.render(c))


def post_view(request):
    """A view that expects a POST, and returns a different template depending
    on whether any POST data is available
    """
    if request.method == "POST":
        if request.POST:
            t = Template(
                "Data received: {{ data }} is the value.", name="POST Template"
            )
            c = Context({"data": request.POST["value"]})
        else:
            t = Template("Viewing POST page.", name="Empty POST Template")
            c = Context()
    else:
        t = Template("Viewing GET page.", name="Empty GET Template")
        # Used by test_body_read_on_get_data.
        request.read(200)
        c = Context()
    return HttpResponse(t.render(c))


def post_then_get_view(request):
    """
    A view that expects a POST request, returns a redirect response
    to itself providing only a ?success=true querystring,
    the value of this querystring is then rendered upon GET.
    """
    if request.method == "POST":
        return HttpResponseRedirect("?success=true")

    t = Template("The value of success is {{ value }}.", name="GET Template")
    c = Context({"value": request.GET.get("success", "false")})

    return HttpResponse(t.render(c))


def json_view(request):
    """
    A view that expects a request with the header 'application/json' and JSON
    data, which is deserialized and included in the context.
    """
    if request.META.get("CONTENT_TYPE") != "application/json":
        return HttpResponse()

    t = Template("Viewing {} page. With data {{ data }}.".format(request.method))
    data = json.loads(request.body.decode("utf-8"))
    c = Context({"data": data})
    return HttpResponse(t.render(c))


def view_with_header(request):
    "A view that has a custom header"
    response = HttpResponse()
    response.headers["X-DJANGO-TEST"] = "Slartibartfast"
    return response


def raw_post_view(request):
    """A view which expects raw XML to be posted and returns content extracted
    from the XML"""
    if request.method == "POST":
        root = parseString(request.body)
        first_book = root.firstChild.firstChild
        title, author = [n.firstChild.nodeValue for n in first_book.childNodes]
        t = Template("{{ title }} - {{ author }}", name="Book template")
        c = Context({"title": title, "author": author})
    else:
        t = Template("GET request.", name="Book GET template")
        c = Context()

    return HttpResponse(t.render(c))


def redirect_view(request):
    "A view that redirects all requests to the GET view"
    if request.GET:
        query = "?" + urlencode(request.GET, True)
    else:
        query = ""
    return HttpResponseRedirect("/get_view/" + query)


def method_saving_307_redirect_query_string_view(request):
    return HttpResponseRedirect("/post_view/?hello=world", status=307)


def method_saving_308_redirect_query_string_view(request):
    return HttpResponseRedirect("/post_view/?hello=world", status=308)


def _post_view_redirect(request, status_code):
    """Redirect to /post_view/ using the status code."""
    redirect_to = request.GET.get("to", "/post_view/")
    return HttpResponseRedirect(redirect_to, status=status_code)


def method_saving_307_redirect_view(request):
    return _post_view_redirect(request, 307)


def method_saving_308_redirect_view(request):
    return _post_view_redirect(request, 308)


def redirect_to_different_hostname(request):
    return HttpResponseRedirect("https://hostname2/get_host_view/")


def get_host_view(request):
    return HttpResponse(request.get_host())


def view_with_secure(request):
    "A view that indicates if the request was secure"
    response = HttpResponse()
    response.test_was_secure_request = request.is_secure()
    response.test_server_port = request.META.get("SERVER_PORT", 80)
    return response


def double_redirect_view(request):
    "A view that redirects all requests to a redirection view"
    return HttpResponseRedirect("/permanent_redirect_view/")


def bad_view(request):
    "A view that returns a 404 with some error content"
    return HttpResponseNotFound("Not found!. This page contains some MAGIC content")


TestChoices = (
    ("a", "First Choice"),
    ("b", "Second Choice"),
    ("c", "Third Choice"),
    ("d", "Fourth Choice"),
    ("e", "Fifth Choice"),
)


class TestForm(Form):
    text = fields.CharField()
    email = fields.EmailField()
    value = fields.IntegerField()
    single = fields.ChoiceField(choices=TestChoices)
    multi = fields.MultipleChoiceField(choices=TestChoices)

    def clean(self):
        cleaned_data = self.cleaned_data
        if cleaned_data.get("text") == "Raise non-field error":
            raise ValidationError("Non-field error.")
        return cleaned_data


def form_view(request):
    "A view that tests a simple form"
    if request.method == "POST":
        form = TestForm(request.POST)
        if form.is_valid():
            t = Template("Valid POST data.", name="Valid POST Template")
            c = Context()
        else:
            t = Template(
                "Invalid POST data. {{ form.errors }}", name="Invalid POST Template"
            )
            c = Context({"form": form})
    else:
        form = TestForm(request.GET)
        t = Template("Viewing base form. {{ form }}.", name="Form GET Template")
        c = Context({"form": form})

    return HttpResponse(t.render(c))


def form_view_with_template(request):
    "A view that tests a simple form"
    if request.method == "POST":
        form = TestForm(request.POST)
        if form.is_valid():
            message = "POST data OK"
        else:
            message = "POST data has errors"
    else:
        form = TestForm()
        message = "GET form page"
    return render(
        request,
        "form_view.html",
        {
            "form": form,
            "message": message,
        },
    )


@login_required
def login_protected_view(request):
    "A simple view that is login protected."
    t = Template(
        "This is a login protected test. Username is {{ user.username }}.",
        name="Login Template",
    )
    c = Context({"user": request.user})

    return HttpResponse(t.render(c))


@login_required(redirect_field_name="redirect_to")
def login_protected_view_changed_redirect(request):
    "A simple view that is login protected with a custom redirect field set"
    t = Template(
        "This is a login protected test. Username is {{ user.username }}.",
        name="Login Template",
    )
    c = Context({"user": request.user})
    return HttpResponse(t.render(c))


def _permission_protected_view(request):
    "A simple view that is permission protected."
    t = Template(
        "This is a permission protected test. "
        "Username is {{ user.username }}. "
        "Permissions are {{ user.get_all_permissions }}.",
        name="Permissions Template",
    )
    c = Context({"user": request.user})
    return HttpResponse(t.render(c))


permission_protected_view = permission_required("permission_not_granted")(
    _permission_protected_view
)
permission_protected_view_exception = permission_required(
    "permission_not_granted", raise_exception=True
)(_permission_protected_view)


class _ViewManager:
    @method_decorator(login_required)
    def login_protected_view(self, request):
        t = Template(
            "This is a login protected test using a method. "
            "Username is {{ user.username }}.",
            name="Login Method Template",
        )
        c = Context({"user": request.user})
        return HttpResponse(t.render(c))

    @method_decorator(permission_required("permission_not_granted"))
    def permission_protected_view(self, request):
        t = Template(
            "This is a permission protected test using a method. "
            "Username is {{ user.username }}. "
            "Permissions are {{ user.get_all_permissions }}.",
            name="Permissions Template",
        )
        c = Context({"user": request.user})
        return HttpResponse(t.render(c))


_view_manager = _ViewManager()
login_protected_method_view = _view_manager.login_protected_view
permission_protected_method_view = _view_manager.permission_protected_view


def session_view(request):
    "A view that modifies the session"
    request.session["tobacconist"] = "hovercraft"

    t = Template(
        "This is a view that modifies the session.",
        name="Session Modifying View Template",
    )
    c = Context()
    return HttpResponse(t.render(c))


def broken_view(request):
    """A view which just raises an exception, simulating a broken view."""
    raise KeyError("Oops! Looks like you wrote some bad code.")


def mail_sending_view(request):
    mail.EmailMessage(
        "Test message",
        "This is a test email",
        "from@example.com",
        ["first@example.com", "second@example.com"],
    ).send()
    return HttpResponse("Mail sent")


def mass_mail_sending_view(request):
    m1 = mail.EmailMessage(
        "First Test message",
        "This is the first test email",
        "from@example.com",
        ["first@example.com", "second@example.com"],
    )
    m2 = mail.EmailMessage(
        "Second Test message",
        "This is the second test email",
        "from@example.com",
        ["second@example.com", "third@example.com"],
    )

    c = mail.get_connection()
    c.send_messages([m1, m2])

    return HttpResponse("Mail sent")


def nesting_exception_view(request):
    """
    A view that uses a nested client to call another view and then raises an
    exception.
    """
    client = Client()
    client.get("/get_view/")
    raise Exception("exception message")


def django_project_redirect(request):
    return HttpResponseRedirect("https://www.djangoproject.com/")


def no_trailing_slash_external_redirect(request):
    """
    RFC 3986 Section 6.2.3: Empty path should be normalized to "/".

    Use https://testserver, rather than an external domain, in order to allow
    use of follow=True, triggering Client._handle_redirects().
    """
    return HttpResponseRedirect("https://testserver")


def index_view(request):
    """Target for no_trailing_slash_external_redirect with follow=True."""
    return HttpResponse("Hello world")


def upload_view(request):
    """Prints keys of request.FILES to the response."""
    return HttpResponse(", ".join(request.FILES))


class TwoArgException(Exception):
    def __init__(self, one, two):
        pass


def two_arg_exception(request):
    raise TwoArgException("one", "two")


class CBView(TemplateView):
    template_name = "base.html"
