import json

import webauthn
from django.contrib import messages
from django.contrib.auth import authenticate
from django.contrib.auth import get_user_model
from django.contrib.auth import login
from django.contrib.auth.decorators import login_required
from django.contrib.sites.shortcuts import get_current_site
from django.http import JsonResponse
from django.shortcuts import redirect
from django.shortcuts import render
from django.views.decorators.csrf import csrf_exempt
from webauthn.helpers import bytes_to_base64url
from webauthn.helpers.structs import AuthenticatorSelectionCriteria
from webauthn.helpers.structs import ResidentKeyRequirement
from webauthn.helpers.structs import UserVerificationRequirement

from . import settings as wa_settings
from .models import AuthData

User = get_user_model()


def login_view(request):
    return render(request, "webauthin_login.html")


@csrf_exempt
@login_required
def register_begin(request):
    site = get_current_site(request)
    options = webauthn.generate_registration_options(
        rp_id=site.domain,
        rp_name=site.name,
        user_id=str(request.user.id).encode(),
        user_name=request.user.get_username(),
        user_display_name="%s user: %s" % (site.name, request.user.get_username()),
        authenticator_selection=AuthenticatorSelectionCriteria(
            resident_key=ResidentKeyRequirement.REQUIRED,
            user_verification=UserVerificationRequirement.REQUIRED,
        ),
    )
    request.session["challenge"] = bytes_to_base64url(options.challenge)
    return JsonResponse(json.loads(webauthn.options_to_json(options)))


@csrf_exempt
@login_required
def register_verify(request):
    site = get_current_site(request)
    challenge = request.session.pop("challenge", None)
    if not challenge:
        messages.error(request, "No challenge exists in your session.")
        return JsonResponse({"redirect": wa_settings.REGISTRATION_ERROR_URL})

    try:
        credential = webauthn.verify_registration_response(
            credential=json.loads(request.body),
            expected_challenge=webauthn.base64url_to_bytes(challenge),
            expected_origin="https://%s" % site.domain,
            expected_rp_id=site.domain,
            require_user_verification=True,
        )
    except Exception as e:
        messages.error(request, "Registration failed. Error: {}".format(e))
        return JsonResponse({"redirect": wa_settings.REGISTRATION_ERROR_URL})

    credential_id = bytes_to_base64url(credential.credential_id)
    if AuthData.objects.filter(credential_id=credential_id).exists():
        messages.error(
            request,
            "This key is already registered to an account. Try logging in with it.",
        )
        return JsonResponse({"redirect": wa_settings.REGISTRATION_ERROR_URL})

    AuthData.objects.create(
        user=request.user,
        credential_id=credential_id,
        public_key=bytes_to_base64url(credential.credential_public_key),
    )
    messages.success(request, "Your key has been successfully registered.")
    return JsonResponse({"redirect": wa_settings.REGISTRATION_REDIRECT_URL})


@csrf_exempt
def login_begin(request):
    site = get_current_site(request)
    options = webauthn.generate_authentication_options(
        rp_id=site.domain,
        user_verification=UserVerificationRequirement.REQUIRED,
    )
    request.session["challenge"] = bytes_to_base64url(options.challenge)
    return JsonResponse(json.loads(webauthn.options_to_json(options)))


@csrf_exempt
def login_verify(request):
    challenge = request.session.pop("challenge", None)
    if not challenge:
        messages.error(request, "No challenge exists for your session.")
        return JsonResponse({"redirect": wa_settings.LOGIN_ERROR_URL})

    try:
        credential_json = json.loads(request.body)
        credential_id = credential_json["id"]
    except (ValueError, KeyError):
        messages.error(request, "Your credentials could not be validated.")
        return JsonResponse({"redirect": wa_settings.LOGIN_ERROR_URL})

    user = authenticate(
        request, credential_id=credential_id, credential_json=credential_json
    )
    if user is None:
        messages.error(request, "Your credentials could not be validated.")
        return JsonResponse({"redirect": wa_settings.LOGIN_ERROR_URL})

    login(request, user)

    messages.success(request, "You have been successfully logged in.")
    return JsonResponse({"redirect": wa_settings.LOGIN_REDIRECT_URL})


@login_required
def rename_key(request):
    key = AuthData.objects.filter(pk=request.POST["key_id"]).first()
    if not key or key.user != request.user:
        messages.error(request, "That key does not exist.")
    else:
        key.name = request.POST["name"]
        key.save()
        messages.success(request, "The key has been renamed.")
    return redirect(wa_settings.REGISTRATION_REDIRECT_URL)


@login_required
def delete_key(request):
    key = AuthData.objects.filter(pk=request.POST["key_id"]).first()
    if not key or key.user != request.user:
        messages.error(request, "That key does not exist.")
    else:
        key.delete()
        messages.success(request, "The key has been deleted.")
    return redirect(wa_settings.REGISTRATION_REDIRECT_URL)
