<?php

namespace App\Http\Controllers;

use App\Models\User;
use App\WebAuthn\Repository\PublicKeyCredentialSourceRepositoryImpl;
use App\WebAuthn\WebAuthnService;
use Cose\Algorithm\Manager;
use Cose\Algorithm\Signature\ECDSA\ES256;
use Cose\Algorithm\Signature\RSA\RS256;
use GuzzleHttp\Psr7\ServerRequest;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Support\Facades\Auth;
use Webauthn\AttestationStatement\AttestationObjectLoader;
use Webauthn\AttestationStatement\AttestationStatementSupportManager;
use Webauthn\AttestationStatement\NoneAttestationStatementSupport;
use Webauthn\AuthenticationExtensions\ExtensionOutputCheckerHandler;
use Webauthn\AuthenticatorAssertionResponse;
use Webauthn\AuthenticatorAssertionResponseValidator;
use Webauthn\AuthenticatorAttestationResponse;
use Webauthn\AuthenticatorAttestationResponseValidator;
use Webauthn\AuthenticatorSelectionCriteria;
use Webauthn\PublicKeyCredentialCreationOptions;
use Webauthn\PublicKeyCredentialLoader;
use Webauthn\PublicKeyCredentialParameters;
use Webauthn\PublicKeyCredentialRequestOptions;
use Webauthn\PublicKeyCredentialRpEntity;
use Webauthn\PublicKeyCredentialSourceRepository;
use Webauthn\PublicKeyCredentialUserEntity;
use Webauthn\TokenBinding\IgnoreTokenBindingHandler;

class UserWebAuthnController extends BaseController
{
    public function register_options(Request $request): PublicKeyCredentialCreationOptions
    {
        $userEntity = new PublicKeyCredentialUserEntity(
            $request->user("web")->name,
            $request->user("web")->id,
            $request->user("web")->name,
        );
        $challenge = random_bytes(16);
        $request->session()->put("webauthn_register_challenge", $challenge);
        return WebAuthnService::createRequestOptions($userEntity, $challenge);
    }

    public function login_options(Request $request)
    {
        $challenge = random_bytes(32);
        $request->session()->put("webauthn_login_challenge", $challenge);
        $username = $request->post("username", "");
        if ($username) {
            $query = User::query();
            if (str_contains($username, "@")) {
                $query->where("email", "=", $username);
            } else {
                $query->where("name", "=", $username);
            }
            $user = $query->first();
            if ($user) {
                $userHandle = (string) $user->id;
            } else {
                return new Response([
                    "success" => false,
                    "code" => 401,
                    "message" => "无此用户"
                ], 401);
            }
        } else {
            $userHandle = "0";
        }
        $request->session()->put("webauthn_login_user", $userHandle);
        $publicKeyCredentialRequestOptions = new PublicKeyCredentialRequestOptions(
            $challenge
        );
        $publicKeyCredentialRequestOptions->setUserVerification(
            PublicKeyCredentialRequestOptions::USER_VERIFICATION_REQUIREMENT_REQUIRED
        );
        $publicKeyCredentialSources = WebAuthnService::getPublicKeyCredentialSourceRepository()->findAllForUserEntity(
            new PublicKeyCredentialUserEntity("", $userHandle, "")
        );
        array_map(function ($source) use ($publicKeyCredentialRequestOptions) {
            $publicKeyCredentialRequestOptions->allowCredential($source->getPublicKeyCredentialDescriptor());
        } ,$publicKeyCredentialSources);
        return $publicKeyCredentialRequestOptions;
    }

    public function register_validate(Request $request)
    {
        if (!$request->session()->has("webauthn_register_challenge")) {
            return new Response([
                "success" => false,
                "code" => 403,
                "message" => "重放攻击可能?无对应验证请求"
            ], 403);
        }
        $publicKeyCredential = WebAuthnService::getPublicKeyCredentialLoader()->loadArray($request->json()->all());
        $authenticatorAttestationResponse = $publicKeyCredential->getResponse();
        if (!$authenticatorAttestationResponse instanceof AuthenticatorAttestationResponse) {
            //e.g. process here with a redirection to the public key creation page.
            return new Response([
                "success" => false,
                "code" => 400,
                "message" => "接口调用错误?无法验证请求"
            ], 400);
        }
        $userEntity = new PublicKeyCredentialUserEntity(
            $request->user("web")->name,
            $request->user("web")->id,
            $request->user("web")->name,
        );
        $challenge = $request->session()->remove("webauthn_register_challenge");
        $publicKeyCredentialCreationOptions = WebAuthnService::createRequestOptions($userEntity, $challenge);
        try {
            $publicKeyCredentialSource = WebAuthnService::getAuthenticatorAttestationResponseValidator()->check(
                $authenticatorAttestationResponse,
                $publicKeyCredentialCreationOptions,
                ServerRequest::fromGlobals(),
                ["localhost"]
            );
        } catch (\Throwable $e) {
            return new Response([
                "success" => false,
                "code" => 500,
                "message" => "服务器异常",
                "exception" => $e->getMessage()
            ], 500);
        }
        WebAuthnService::getPublicKeyCredentialSourceRepository()->saveCredentialSource($publicKeyCredentialSource);
        return $publicKeyCredentialSource;
    }

    public function login_validate(Request $request)
    {
        if (!$request->session()->has("webauthn_login_challenge")) {
            return new Response([
                "success" => false,
                "code" => 403,
                "message" => "重放攻击可能?无对应验证请求"
            ], 403);
        }
        $publicKeyCredentialRequestOptions = new PublicKeyCredentialRequestOptions(
            $request->session()->remove("webauthn_login_challenge")
        );
        $publicKeyCredentialSources = WebAuthnService::getPublicKeyCredentialSourceRepository()->findAllForUserEntity(
            new PublicKeyCredentialUserEntity("", "0", "")
        );
        $publicKeyCredential = WebAuthnService::getPublicKeyCredentialLoader()->loadArray($request->json()->all());
        $userHandle = null;
        foreach ($publicKeyCredentialSources as $source) {
            if ($source->getPublicKeyCredentialId() === $publicKeyCredential->getRawId()) {
                $userHandle = $source->getUserHandle();
                break;
            }
        }
        if ($userHandle === null) {
            return new Response([
                "success" => false,
                "code" => 401,
                "message" => "无此密钥"
            ], 401);
        }
        $authenticatorAssertionResponse = $publicKeyCredential->getResponse();
        if (!$authenticatorAssertionResponse instanceof AuthenticatorAssertionResponse) {
            //e.g. process here with a redirection to the public key login/MFA page.
            return new Response([
                "success" => false,
                "code" => 400,
                "message" => "接口调用错误?无法验证请求"
            ], 400);
        }
        try {
            $publicKeyCredentialSource = WebAuthnService::getAuthenticatorAssertionResponseValidator()->check(
                $publicKeyCredential->getRawId(),
                $authenticatorAssertionResponse,
                $publicKeyCredentialRequestOptions,
                ServerRequest::fromGlobals(),
                $userHandle,
                ["localhost"]
            );
        } catch (\Throwable $e) {
            return new Response([
                "success" => false,
                "code" => 500,
                "message" => "服务器异常",
                "exception" => $e->getMessage()
            ], 500);
        }
        Auth::loginUsingId($publicKeyCredentialSource->getUserHandle());
        return [
            "success" => true,
            "code" => 0,
            "message" => "登录成功",
            "userId" => $publicKeyCredentialSource->getUserHandle()
        ];
    }
}