from abc import ABC, abstractmethod
from textwrap import dedent

from django.core.handlers.wsgi import WSGIRequest
from django.http import Http404, HttpResponse, HttpResponseForbidden
from django.shortcuts import redirect
from django.urls import reverse
from django.views.generic.base import View

from vuln_demo.apps.vulnerabilities.models import CompletedLevel
from vuln_demo.util.auth import cast_user
from vuln_demo.util.info import get_client_ip


class LevelABC(ABC, View):
    """Base class for all level classes."""

    HINT_AMOUNT: int = 1

    @classmethod
    def completed_redirect(cls, request: WSGIRequest, level_num: int, *, skip_db: bool = False) -> HttpResponse:
        """
        Called when the user completed this level.

        Redirects to success page and marks the level as completed.
        """
        assert cast_user(request.user, cast=True)
        ip_address = get_client_ip(request)

        if not skip_db:
            CompletedLevel.add_completion(request.user, ip_address, level_num)
        return redirect("vulnerabilities/level/completed", level_num=level_num)

    @classmethod
    def get_hint(cls, request: WSGIRequest, level_num: int, hint_num: int) -> HttpResponse:
        """
        Called through an HTMX request when the next hint is requested.

        The returned content here will be placed into the hint modal as HTML.

        This internally relies on :meth:`_get_hint`, which returns the HTML text of the hints.

        However additionally, this function also includes a button to show the next hint (if available),
        which will also get placed in the modal. This also registers the use of the hint into the db here.
        """
        if hint_num > cls.HINT_AMOUNT:
            raise Http404

        assert cast_user(request.user, cast=True)
        ip_address = get_client_ip(request)

        # Only allow the user to view the next hint, or the hints they already seen
        # we don't want to allow skipping (going from hint_num=1 to hint_num=3 for example)
        # Do allow users who have already completed the level to skip though
        cur_hint_num = CompletedLevel.check_hint(request.user, level_num)
        if hint_num > cur_hint_num + 1 and not CompletedLevel.check_completed(request.user, level_num):
            return HttpResponseForbidden(
                f"Hints must be requested sequentially. You can't jump to hint #{hint_num} "
                f"when your last hint was #{cur_hint_num}."
            )

        # Register the use of this hint, if it wasn't already used
        if hint_num > cur_hint_num:
            CompletedLevel.add_hint(request.user, ip_address, level_num, hint_num)

        hint_html_text = cls._get_hint(request, hint_num)
        next_hint_button_html = (
            cls._get_hint_button(level_num, hint_num + 1, "<i class='fa-solid fa-angles-right'></i> Request next hint")
            if hint_num < cls.HINT_AMOUNT
            else ""
        )
        prev_hint_button_html = (
            cls._get_hint_button(
                level_num, hint_num - 1, "<i class='fa-solid fa-angles-left'></i> Back to previous hint"
            )
            if hint_num > 1
            else ""
        )

        html = dedent(
            f"""
                {hint_html_text}
                <br>
                <div class="row justify-content-center">
                    {prev_hint_button_html}
                    {next_hint_button_html}
                </div>
            """
        )

        return HttpResponse(html)

    @staticmethod
    def _get_hint_button(level_num: int, hint_num: int, text: str) -> str:
        """Return HTML content for a next/previous hint button."""
        req_url = reverse("vulnerabilities/level/hint", kwargs={"level_num": level_num, "hint_num": hint_num})
        return dedent(
            f"""
                <div class="col-auto">
                    <button
                        type="button"
                        class="btn btn-info"
                        hx-get={req_url!r}
                        hx-trigger="click"
                        hx-target="#hintContent">
                        {text}
                    </button>
                </div>
            """
        )

    @classmethod
    @abstractmethod
    def _get_hint(cls, request: WSGIRequest, hint_num: int) -> str:
        """
        Return the hint text (HTML), to be rendered inside of the hint modal for this hint number.

        Note: Avoid registering the use of the hint here, that is already handled
        by :func:`..views.level_hint_view`, which then calls this function.
        """
        raise NotImplementedError

    @abstractmethod
    def get(self, request: WSGIRequest) -> HttpResponse:
        """Main view function called when this level is requested."""
        raise NotImplementedError
