import textwrap
from pathlib import Path
from typing import final

from django import forms
from django.contrib.auth.decorators import login_required
from django.contrib.staticfiles import finders
from django.core.handlers.wsgi import WSGIRequest
from django.http import Http404, HttpResponse, HttpResponseForbidden
from django.shortcuts import render
from django.urls import reverse
from django.views.decorators.http import require_GET
from typing_extensions import override

from vuln_demo.apps.vulnerabilities.levels.abc import LevelABC
from vuln_demo.apps.vulnerabilities.models import CompletedLevel
from vuln_demo.util.auth import cast_user
from vuln_demo.util.pw_entries import get_cached_single_password, reset_single_password_cache


class PasswordForm(forms.Form):
    """Form class passed to level 5 view."""

    password = forms.CharField(
        label="Password",
        widget=forms.PasswordInput(attrs={"autofocus": "autofocus"}),
    )


@login_required
@require_GET
def htaccess_file(request: WSGIRequest) -> HttpResponse:
    """
    Endpoint to show the contents of the .htaccess file for this level.

    This will mimic an apache .htaccess file, used to configure directory access permissions.
    It will indicate a file that is ignored in the index, which holds the user password.
    """
    return HttpResponse(
        textwrap.dedent(
            """
            <html>
            <body class="vsc-initialized">
            <pre>
            IndexIgnore password.txt .htaccess
            &lt;Files .htaccess&gt;
            require all granted
            &lt;/Files&gt;
            </pre>
            </body>
            </html>
            """
        )
    )


@login_required
@require_GET
def answer_file(request: WSGIRequest) -> HttpResponse:
    """
    Endpoint to show the contents of the answer file for this level.

    This will contain the user password, which is the answer to this level.
    """
    assert cast_user(request.user, cast=True)
    password = get_cached_single_password(request.user, "level5")
    return HttpResponse(password)


@login_required
@require_GET
def funny_cat_image(request: WSGIRequest) -> HttpResponse:
    """
    Endpoint to show the funny cat image for this level.

    This is the single most important endpoint in the entire portal.
    """
    img_path = finders.find("images/funny_cat.jpg")
    if isinstance(img_path, str):
        with Path(img_path).open("rb") as f:
            return HttpResponse(f.read(), content_type="image/jpeg")
    else:
        raise Http404("Image not found :(")


@login_required
@require_GET
def path_index(request: WSGIRequest) -> HttpResponse:
    """
    Endpoint to show a (fake) path index page for this level.

    The directory structure stemming from URL path of this level is as follows:
    .
    |-static
    ||-config
    |||-.htaccess (hidden)
    |||-password.txt (hidden)
    ||-images
    |||-funny_cat.jpg

    The directory index will be dynamically populated based on the URL path.
    """
    path = request.path.removeprefix("/vulnerabilities/level/5/").removesuffix("/")

    view_args_map = {
        "static": {
            "parent_url": reverse("vulnerabilities/level", kwargs={"level_num": 5}),
            "directories": [
                {
                    "name": "config",
                    "url": reverse("vulnerabilities/level5/path_index_static_config"),
                    "last_modified": "2019-03-01 20:34",
                },
                {
                    "name": "images",
                    "url": reverse("vulnerabilities/level5/path_index_static_images"),
                    "last_modified": "2019-03-01 20:34",
                },
            ],
            "files": [],
        },
        "static/config": {
            "parent_url": reverse("vulnerabilities/level5/path_index_static"),
            "directories": [],
            "files": [],
        },
        "static/images": {
            "parent_url": reverse("vulnerabilities/level5/path_index_static"),
            "directories": [],
            "files": [
                {
                    "name": "funny_cat.jpg",
                    "url": reverse("vulnerabilities/level5/path_index_static_images_funny_cat"),
                    "last_modified": "2019-03-01 20:34",
                    "size": "46.57 KB",
                    "desscription": "A funny cat picture.",
                    "icon_name": "image2.gif",
                }
            ],
        },
    }

    if path not in view_args_map:
        # This function should never trigger for other URL paths
        # but if it does, just return a 403
        return HttpResponseForbidden()

    view_args = view_args_map[path]

    return render(
        request,
        "vulnerabilities/levels/level5_path_index.html",
        view_args,
    )


@final
class Level5(LevelABC):
    """Scenario with unrestricted access to a hidden configuration file."""

    HINT_AMOUNT = 3

    @override
    @classmethod
    def _get_hint(cls, request: WSGIRequest, hint_num: int) -> str:
        if hint_num == 1:
            return textwrap.dedent(
                """
                <p>
                    Based on the way the directory index looks, you were able to determine that
                    it is autogenerated and comes from an apache server.
                </p>
                """
            )
        if hint_num == 2:
            return textwrap.dedent(
                """
                <p>
                    Can you think of a way, that the server admin might configure this apache directory
                    index to hide certain files, so that they won't show up in the index?
                </p>
                """
            )
        if hint_num == 3:
            return textwrap.dedent(
                """
                <p>
                    Apache servers can be configured using a file called .htaccess.
                </p>
                """
            )
        raise

    @override
    def get(self, request: WSGIRequest) -> HttpResponse:
        assert cast_user(request.user, cast=True)
        already_completed = CompletedLevel.check_completed(request.user, 5)

        form = PasswordForm()
        return render(
            request,
            "vulnerabilities/levels/level5.html",
            {
                "form": form,
                "completed": already_completed,
            },
        )

    def post(self, request: WSGIRequest) -> HttpResponse:
        """Handle password form submission."""
        assert cast_user(request.user, cast=True)
        already_completed = CompletedLevel.check_completed(request.user, 5)

        form = PasswordForm(request.POST)
        if not form.is_valid():
            return render(
                request,
                "vulnerabilities/levels/level5.html",
                {"form": form, "completed": already_completed},
            )

        password = get_cached_single_password(request.user, "level5")
        if form.cleaned_data["password"] != password:
            return render(
                request,
                "vulnerabilities/levels/level5.html",
                {
                    "form": form,
                    "completed": already_completed,
                    "error_msg": "Invalid password.",
                },
            )

        reset_single_password_cache(request.user, "level5")
        return self.completed_redirect(request, 5, skip_db=already_completed)
