@page "/account/manage/enable-authenticator"

@using System.ComponentModel.DataAnnotations
@using System.Globalization
@using System.Text
@using System.Text.Encodings.Web
@using AliasVault.Shared.Models.Enums
@using Microsoft.AspNetCore.Identity

@inject UserManager<AdminUser> UserManager
@inject UrlEncoder UrlEncoder
@inject ILogger<EnableAuthenticator> Logger

<LayoutPageTitle>Configure authenticator app</LayoutPageTitle>

@if (_isLoading)
{
    <LoadingIndicator />
    return;
}

@if (RecoveryCodes is not null)
{
    <ShowRecoveryCodes RecoveryCodes="RecoveryCodes.ToArray()"/>
}
else
{
    <div class="max-w-2xl mx-auto">
        <h3 class="text-2xl font-bold text-gray-900 dark:text-white mb-6">Configure authenticator app</h3>
        <div class="space-y-6">
            <p class="text-gray-700 dark:text-gray-300">To use an authenticator app go through the following steps:</p>
            <ol class="list-decimal space-y-4">
                <li>
                    <p class="text-gray-700 dark:text-gray-300">
                        Register this 2FA code in your AliasVault account itself, or download a separate two-factor authenticator app like Microsoft Authenticator for
                        <a href="https://go.microsoft.com/fwlink/?Linkid=825072" class="text-blue-600 hover:underline dark:text-blue-400">Android</a> and
                        <a href="https://go.microsoft.com/fwlink/?Linkid=825073" class="text-blue-600 hover:underline dark:text-blue-400">iOS</a> or
                        Google Authenticator for
                        <a href="https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2&amp;hl=en" class="text-blue-600 hover:underline dark:text-blue-400">Android</a> and
                        <a href="https://itunes.apple.com/us/app/google-authenticator/id388497605?mt=8" class="text-blue-600 hover:underline dark:text-blue-400">iOS</a>.
                    </p>
                </li>
                <li>
                    <p class="text-gray-700 dark:text-gray-300">Scan the QR Code or enter this key <kbd class="px-2 py-1.5 text-xs font-semibold text-gray-800 bg-gray-100 border border-gray-200 rounded-lg dark:bg-gray-600 dark:text-gray-100 dark:border-gray-500">@SharedKey</kbd> into your two factor authenticator app. Spaces and casing do not matter.</p>
                    <div id="authenticator-uri" data-url="@AuthenticatorUri" class="mt-4"></div>
                </li>
                <li>
                    <p class="text-gray-700 dark:text-gray-300">
                        Once you have scanned the QR code or input the key above, your two factor authentication app will provide you
                        with a unique code. Enter the code in the confirmation box below.
                    </p>
                    <div class="mt-4">
                        <EditForm Model="Input" FormName="send-code" OnValidSubmit="OnValidSubmitAsync" method="post" class="space-y-4">
                            <DataAnnotationsValidator/>
                            <div>
                                <label for="code" class="block mb-2 text-sm font-medium text-gray-700 dark:text-gray-200">Verification Code</label>
                                <InputText @bind-Value="Input.Code" id="code" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-primary-500 focus:border-primary-500 sm:text-sm dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white" autocomplete="one-time-code" placeholder="Please enter the code."/>
                                <ValidationMessage For="() => Input.Code" class="mt-1 text-sm text-red-600 dark:text-red-400"/>
                            </div>
                            <div>
                                <SubmitButton>Verify</SubmitButton>
                            </div>
                            <ValidationSummary class="text-red-600 dark:text-red-400" role="alert"/>
                        </EditForm>
                    </div>
                </li>
            </ol>
        </div>
    </div>
}

@code {
    private const string AuthenticatorUriFormat = "otpauth://totp/{0}:{1}?secret={2}&issuer={0}&digits=6";

    private string? SharedKey { get; set; }
    private string? AuthenticatorUri { get; set; }
    private IEnumerable<string>? RecoveryCodes { get; set; }
    private bool _isLoading = true;

    [SupplyParameterFromForm] private InputModel Input { get; set; } = default!;

    protected override void OnInitialized()
    {
        Input ??= new();
    }

    /// <inheritdoc/>
    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            await LoadSharedKeyAndQrCodeUriAsync();
            _isLoading = false;
            StateHasChanged();
            await JsInvokeService.RetryInvokeAsync("generateQrCode", TimeSpan.Zero, 5, "authenticator-uri");
        }
    }

    private async Task OnValidSubmitAsync()
    {
        // Strip spaces and hyphens
        var verificationCode = Input.Code.Replace(" ", string.Empty).Replace("-", string.Empty);

        var user = await UserManager.FindByIdAsync(UserService.User().Id);
        if (user == null)
        {
            throw new InvalidOperationException("User not found.");
        }

        var is2FaTokenValid = await UserManager.VerifyTwoFactorTokenAsync(user, UserManager.Options.Tokens.AuthenticatorTokenProvider, verificationCode);

        if (!is2FaTokenValid)
        {
            GlobalNotificationService.AddErrorMessage("Error: Verification code is invalid.");
            return;
        }

        await UserManager.SetTwoFactorEnabledAsync(user, true);
        await AuthLoggingService.LogAuthEventSuccessAsync(UserService.User().UserName!, AuthEventType.TwoFactorAuthEnable);
        Logger.LogInformation("User with ID '{UserId}' has enabled 2FA with an authenticator app.", UserService.User().Id);
        GlobalNotificationService.AddSuccessMessage("Your authenticator app has been verified.");

        if (await UserManager.CountRecoveryCodesAsync(user) == 0)
        {
            RecoveryCodes = await UserManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10);
        }
        else
        {
            // Navigate back to the two-factor authentication page.
            NavigationService.RedirectTo("account/manage/2fa", forceLoad: true);
        }
    }

    private async ValueTask LoadSharedKeyAndQrCodeUriAsync()
    {
        var user = await UserManager.FindByIdAsync(UserService.User().Id);
        if (user == null)
        {
            throw new InvalidOperationException("User not found.");
        }

        // Load the authenticator key & QR code URI to display on the form.
        var unformattedKey = await UserManager.GetAuthenticatorKeyAsync(user);
        if (string.IsNullOrEmpty(unformattedKey))
        {
            await UserManager.ResetAuthenticatorKeyAsync(user);
            unformattedKey = await UserManager.GetAuthenticatorKeyAsync(user);
        }

        SharedKey = FormatKey(unformattedKey!);

        var username = await UserManager.GetUserNameAsync(user);
        AuthenticatorUri = GenerateQrCodeUri(username!, unformattedKey!);
    }

    private static string FormatKey(string unformattedKey)
    {
        var result = new StringBuilder();
        int currentPosition = 0;
        while (currentPosition + 4 < unformattedKey.Length)
        {
            result.Append(unformattedKey.AsSpan(currentPosition, 4)).Append(' ');
            currentPosition += 4;
        }

        if (currentPosition < unformattedKey.Length)
        {
            result.Append(unformattedKey.AsSpan(currentPosition));
        }

        return result.ToString().ToLowerInvariant();
    }

    private string GenerateQrCodeUri(string username, string unformattedKey)
    {
        return string.Format(
            CultureInfo.InvariantCulture,
            AuthenticatorUriFormat,
            UrlEncoder.Encode("AliasVault Admin"),
            UrlEncoder.Encode(username),
            unformattedKey);
    }

    private sealed class InputModel
    {
        [Required]
        [StringLength(7, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
        [DataType(DataType.Text)]
        [Display(Name = "Verification Code")]
        public string Code { get; set; } = "";
    }

}
