import secrets
from typing import final

from django import forms
from django.contrib.auth.models import User
from django.core.cache import cache
from django.core.handlers.wsgi import WSGIRequest
from django.http import HttpResponse
from django.shortcuts import render
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


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

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


@final
class Level1(LevelABC):
    """Scenario with sensitive data hidden in an HTML comment."""

    @override
    @classmethod
    def _get_hint(cls, request: WSGIRequest, hint_num: int) -> str:
        if hint_num == 1:
            return (
                "Sometimes secrets are hidden in plain sight, waiting to be uncovered. Have you ever wondered "
                "what lies beneath the surface of a webpage? The answer might be just a right-click away."
            )
        raise

    @staticmethod
    def get_password(user: User, force_recreate: bool = False) -> str:
        """
        Obtain or generate a random admin password for this user account.

        The admin password will be randomly generated for each account to avoid users
        from sharing the password which worked for them. Once generated, this password
        will be cached using redis for a duration of 1 day. This timeout will be refreshed
        each time this function is called.

        :param force_recreate:
            When True, the cached password will always be recreated, regardless of whether it
            already existed or not.
        """
        key = f"level1-password-{user.username}"

        if not force_recreate and (cached_pw := cache.get(key)):
            cache.touch(key, timeout=86400)  # 1 day (refresh)
            return cached_pw

        rand_password = secrets.token_urlsafe(32)
        cache.set(key, rand_password, timeout=86400)  # 1 day
        return rand_password

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

        form = PasswordForm()
        return render(
            request,
            "vulnerabilities/levels/level1.html",
            {
                "form": form,
                "password": exposed_password,
                "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, 1)
        exposed_password = self.get_password(request.user)

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

        pw = form.cleaned_data["password"]
        if pw != exposed_password:
            form.add_error("password", "Incorrect password")
            return render(
                request,
                "vulnerabilities/levels/level1.html",
                {"form": form, "password": exposed_password, "completed": already_completed},
            )

        # set a new password for the level after completion, to give at least a small challenge
        # to the user if they want to retry the level
        self.get_password(request.user, force_recreate=True)

        return self.completed_redirect(request, 1, skip_db=already_completed)
