@page "/Account/Manage/EnableAuthenticator"

@using System.ComponentModel.DataAnnotations
@using System.Globalization
@using System.Text
@using System.Text.Encodings.Web
@using Microsoft.AspNetCore.Identity
@using Microsoft.Extensions.Options
@using Wave.Data

@inject UserManager<ApplicationUser> UserManager
@inject IdentityUserAccessor UserAccessor
@inject UrlEncoder UrlEncoder
@inject IdentityRedirectManager RedirectManager
@inject IOptions<Customization> CustomizationOptions
@inject ILogger<EnableAuthenticator> Logger
@inject IStringLocalizer<EnableAuthenticator> Localizer

<PageTitle>@Localizer["Title"]</PageTitle>

<StatusMessage Message="@Message" />

<BoardComponent>
    @if (RecoveryCodes is not null) {
        <ShowRecoveryCodes RecoveryCodes="RecoveryCodes.ToArray()" StatusMessage="@Message" />
    } else {
        <BoardCardComponent Heading="@Localizer["Title"]">
            <ol class="flex flex-col gap-4 hyphens-auto text-justify" lang="@CultureInfo.CurrentCulture">
                <li><p>@Localizer["Introduction"]</p></li>
                <li>
                    <p>
                        Download a two-factor authenticator app like Microsoft Authenticator for
                        <a class="link" href="https://go.microsoft.com/fwlink/?Linkid=825072">Android</a> and
                        <a class="link" href="https://go.microsoft.com/fwlink/?Linkid=825073">iOS</a> or
                        Google Authenticator for
                        <a class="link" href="https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2">Android</a> and
                        <a class="link" href="https://itunes.apple.com/us/app/google-authenticator/id388497605?mt=8">iOS</a>.
                    </p>
                </li>
                <li>
                    <p>@string.Format(Localizer["QrCode_Description"], SharedKey)</p>
                    <div id="qrCode" class="flex place-content-center p-4 rounded my-3" style="background: white"></div>
                    <div id="qrCodeData" data-url="@AuthenticatorUri"></div>
                </li>
            </ol>
        </BoardCardComponent>
        <BoardCardComponent>
            <p>@Localizer["2faCode_Description"]</p>
            <EditForm Model="Input" FormName="send-code" OnValidSubmit="OnValidSubmitAsync" method="post" class="w-full">
                <DataAnnotationsValidator/>

                <InputLabelComponent LabelText="@Localizer["VerificationCode_Label"]" For="() => Input.Code">
                    <InputText @bind-Value="Input.Code" class="input input-bordered w-full" autocomplete="off"
                               required aria-required placeholder="@Localizer["VerificationCode_Placeholder"]"/>
                </InputLabelComponent>

                <button type="submit" class="btn btn-primary w-full">
                    @Localizer["VerificationCode_Submit"]
                </button>
            </EditForm>
        </BoardCardComponent>
    }
</BoardComponent>

<SectionContent SectionName="scripts">
    <script type="text/javascript" src="/lib/qrcodejs/qrcode.min.js"></script>
    <script type="text/javascript">
        window.addEventListener("DOMContentLoaded", () => {
            const uri = document.getElementById("qrCodeData").getAttribute('data-url');
            new QRCode(document.getElementById("qrCode"), {
                    text: uri,
                    width: 300,
                    height: 300
                });
        });
    </script>
</SectionContent>


@code {
    [CascadingParameter]
    private HttpContext HttpContext { get; set; } = default!;
    [SupplyParameterFromForm]
    private InputModel Input { get; set; } = new();

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

    private string? Message { get; set; }
    private ApplicationUser User { get; set; } = default!;
    private string? SharedKey { get; set; }
    private string? AuthenticatorUri { get; set; }
    private IEnumerable<string>? RecoveryCodes { get; set; }

    protected override async Task OnInitializedAsync() {
        User = await UserAccessor.GetRequiredUserAsync(HttpContext);
        await LoadSharedKeyAndQrCodeUriAsync(User);
    }

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

        bool is2FaTokenValid = await UserManager.VerifyTwoFactorTokenAsync(
            User, UserManager.Options.Tokens.AuthenticatorTokenProvider, verificationCode);

        if (!is2FaTokenValid) {
            Message = Localizer["VerificationCode_ErrorInvalid"];
            return;
        }

        await UserManager.SetTwoFactorEnabledAsync(User, true);
        string userId = await UserManager.GetUserIdAsync(User);
        Logger.LogInformation("User with ID '{UserId}' has enabled 2FA with an authenticator app.", userId);

        Message = Localizer["VerificationCode_Success"];

        if (await UserManager.CountRecoveryCodesAsync(User) == 0) {
            RecoveryCodes = await UserManager.GenerateNewTwoFactorRecoveryCodesAsync(User, 10);
        } else {
            RedirectManager.RedirectToWithStatus("Account/Manage/TwoFactorAuthentication", Message, HttpContext);
        }
    }

    private async ValueTask LoadSharedKeyAndQrCodeUriAsync(ApplicationUser user) {
        // Load the authenticator key & QR code URI to display on the form
        string? unformattedKey = await UserManager.GetAuthenticatorKeyAsync(user);

        if (string.IsNullOrEmpty(unformattedKey)) {
            await UserManager.ResetAuthenticatorKeyAsync(user);
            unformattedKey = await UserManager.GetAuthenticatorKeyAsync(user);
        }

        SharedKey = FormatKey(unformattedKey!);

        string? email = await UserManager.GetEmailAsync(user);
        AuthenticatorUri = GenerateQrCodeUri(email!, unformattedKey!);
    }

    private 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 email, string unformattedKey) {
        return string.Format(
            CultureInfo.InvariantCulture,
            AuthenticatorUriFormat,
            UrlEncoder.Encode(CustomizationOptions.Value.AppName),
            UrlEncoder.Encode(email),
            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; } = "";
    }
}
