import base64
from io import BytesIO

import pyotp
import qrcode
from django.conf import settings
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.views import SuccessURLAllowedHostsMixin
from django.http import HttpResponseBadRequest, HttpResponseRedirect
from django.urls import reverse
from django.utils.http import is_safe_url
from django.utils.translation import gettext as _
from django.views.generic import FormView

from judge.forms import TOTPForm
from judge.utils.views import TitleMixin


class TOTPView(TitleMixin, LoginRequiredMixin, FormView):
    form_class = TOTPForm

    def get_form_kwargs(self):
        result = super(TOTPView, self).get_form_kwargs()
        result["totp_key"] = self.profile.totp_key
        return result

    def dispatch(self, request, *args, **kwargs):
        if request.user.is_authenticated:
            self.profile = request.profile
            if self.check_skip():
                return self.next_page()
        return super(TOTPView, self).dispatch(request, *args, **kwargs)

    def check_skip(self):
        raise NotImplementedError()

    def next_page(self):
        return HttpResponseRedirect(reverse("user_edit_profile"))


class TOTPEnableView(TOTPView):
    title = _("Enable Two Factor Authentication")
    template_name = "registration/totp_enable.html"

    def get(self, request, *args, **kwargs):
        profile = self.profile
        if not profile.totp_key:
            profile.totp_key = pyotp.random_base32(length=32)
            profile.save()
        return self.render_to_response(self.get_context_data())

    def check_skip(self):
        return self.profile.is_totp_enabled

    def post(self, request, *args, **kwargs):
        if not self.profile.totp_key:
            return HttpResponseBadRequest("No TOTP key generated on server side?")
        return super(TOTPEnableView, self).post(request, *args, **kwargs)

    def get_context_data(self, **kwargs):
        context = super(TOTPEnableView, self).get_context_data(**kwargs)
        context["totp_key"] = self.profile.totp_key
        context["qr_code"] = self.render_qr_code(
            self.request.user.username, self.profile.totp_key
        )
        return context

    def form_valid(self, form):
        self.profile.is_totp_enabled = True
        self.profile.save()
        # Make sure users don't get prompted to enter code right after enabling:
        self.request.session["2fa_passed"] = True
        return self.next_page()

    @classmethod
    def render_qr_code(cls, username, key):
        totp = pyotp.TOTP(key)
        uri = totp.provisioning_uri(username, settings.SITE_NAME)

        qr = qrcode.QRCode(box_size=1)
        qr.add_data(uri)
        qr.make(fit=True)

        image = qr.make_image(fill_color="black", back_color="white")
        buf = BytesIO()
        image.save(buf, format="PNG")
        return "data:image/png;base64," + base64.b64encode(buf.getvalue()).decode(
            "ascii"
        )


class TOTPDisableView(TOTPView):
    title = _("Disable Two Factor Authentication")
    template_name = "registration/totp_disable.html"

    def check_skip(self):
        if not self.profile.is_totp_enabled:
            return True
        return settings.DMOJ_REQUIRE_STAFF_2FA and self.request.user.is_staff

    def form_valid(self, form):
        self.profile.is_totp_enabled = False
        self.profile.totp_key = None
        self.profile.save()
        return self.next_page()


class TOTPLoginView(SuccessURLAllowedHostsMixin, TOTPView):
    title = _("Perform Two Factor Authentication")
    template_name = "registration/totp_auth.html"

    def check_skip(self):
        return not self.profile.is_totp_enabled or self.request.session.get(
            "2fa_passed", False
        )

    def next_page(self):
        redirect_to = self.request.GET.get("next", "")
        url_is_safe = is_safe_url(
            url=redirect_to,
            allowed_hosts=self.get_success_url_allowed_hosts(),
            require_https=self.request.is_secure(),
        )
        return HttpResponseRedirect(
            (redirect_to if url_is_safe else "") or reverse("user_page")
        )

    def form_valid(self, form):
        self.request.session["2fa_passed"] = True
        return self.next_page()