@page "/users/{Id}"
@using AliasVault.Admin.Main.Pages.Users.View.Components
@inherits MainBase

<LayoutPageTitle>User</LayoutPageTitle>

@if (IsLoading || User == null)
{
    <LoadingIndicator/>
}
else
{
    <PageHeader
        BreadcrumbItems="@BreadcrumbItems"
        Title="View user"
        Description="View details of the user below.">
        <CustomActions>
            <RefreshButton OnClick="RefreshData" ButtonText="Refresh" />
            <LinkButton Color="danger" Href="@($"users/{Id}/delete")" Text="Delete user" />
        </CustomActions>
    </PageHeader>

    <div class="grid grid-cols-2 px-4 pt-6 md:grid-cols-3 lg:gap-4 dark:bg-gray-900">
        <div class="col-span-full">
            <div class="p-4 mb-4 bg-white border border-gray-200 rounded-lg shadow-sm dark:border-gray-700 sm:p-6 dark:bg-gray-800">
                <div class="items-center xl:block sm:space-x-4 xl:space-x-0 2xl:space-x-4">
                    <div>
                        <h3 class="mb-1 text-xl font-bold text-gray-900 dark:text-white">@User.UserName</h3>

                        <div class="mb-4">
                            <label class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Id</label>
                            <div class="text-gray-700 dark:text-gray-300">@User.Id</div>
                        </div>
                        <div class="flex items-center space-x-2">
                            <span class="text-sm font-medium text-gray-900 dark:text-white">2FA Status:</span>
                            <StatusPill Enabled="@User.TwoFactorEnabled"/>
                            <span class="text-gray-700 dark:text-gray-300">Authenticator key(s) active: @TwoFactorKeysCount</span>
                            @if (User.TwoFactorEnabled)
                            {
                                <Button Color="danger" OnClick="DisableTwoFactor">Disable 2FA</Button>
                            }
                            else
                            {
                                if (TwoFactorKeysCount > 0)
                                {
                                    <Button Color="success" OnClick="EnableTwoFactor">Enable 2FA</Button>
                                    <Button Color="danger" OnClick="ResetTwoFactor">Remove 2FA keys</Button>
                                }
                            }
                        </div>
                    </div>
                </div>
            </div>

            <div class="p-4 mb-4 bg-white border border-gray-200 rounded-lg shadow-sm dark:border-gray-700 sm:p-6 dark:bg-gray-800">
                <div class="items-center">
                    <div>
                        <h3 class="mb-1 text-xl font-bold text-gray-900 dark:text-white">Vaults</h3>

                        <VaultTable VaultList="@VaultList" OnMakeCurrent="@MakeCurrentAsync" />
                    </div>
                </div>
            </div>

            <div class="p-4 mb-4 bg-white border border-gray-200 rounded-lg shadow-sm dark:border-gray-700 sm:p-6 dark:bg-gray-800">
                <div class="items-center">
                    <div>
                        <h3 class="mb-1 text-xl font-bold text-gray-900 dark:text-white">UserRefreshTokens (Logged in devices)</h3>

                        <RefreshTokenTable RefreshTokenList="@RefreshTokenList" OnRevokeToken="@RevokeRefreshToken" />
                    </div>
                </div>
            </div>

            <div class="p-4 mb-4 bg-white border border-gray-200 rounded-lg shadow-sm dark:border-gray-700 sm:p-6 dark:bg-gray-800">
                <div class="items-center">
                    <div>
                        <h3 class="mb-1 text-xl font-bold text-gray-900 dark:text-white">Email claims</h3>

                        <EmailClaimTable EmailClaimList="@EmailClaimList" />
                    </div>
                </div>
            </div>
        </div>
    </div>
}

@code {
    /// <summary>
    /// Gets or sets the user ID.
    /// </summary>
    [Parameter]
    public string Id { get; set; } = string.Empty;

    private bool IsLoading { get; set; } = true;
    private AliasVaultUser? User { get; set; } = new();
    private int TwoFactorKeysCount { get; set; }
    private List<AliasVaultUserRefreshToken> RefreshTokenList { get; set; } = [];
    private List<Vault> VaultList { get; set; } = [];
    private List<UserEmailClaim> EmailClaimList { get; set; } = [];

    /// <inheritdoc />
    protected override async Task OnInitializedAsync()
    {
        await base.OnInitializedAsync();
        BreadcrumbItems.Add(new BreadcrumbItem { DisplayName = "Users", Url = "/users" });
        BreadcrumbItems.Add(new BreadcrumbItem { DisplayName = "View user" });
    }

    /// <inheritdoc />
    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        await base.OnAfterRenderAsync(firstRender);

        if (firstRender)
        {
            await RefreshData();
        }
    }

    private async Task RefreshData()
    {
        IsLoading = true;
        StateHasChanged();

        // Load the aliases from the webapi via AliasService.
        User = await DbContext.AliasVaultUsers.FindAsync(Id);

        // Get count of user authenticator tokens.
        TwoFactorKeysCount = await DbContext.UserTokens.CountAsync(x => x.UserId == User!.Id && x.Name == "AuthenticatorKey");

        if (User is null)
        {
            // Error loading user.
            GlobalNotificationService.AddErrorMessage("This user does not exist (anymore). Please try again.");
            NavigationService.RedirectTo("users");
            return;
        }

        // Load all active refresh tokens for this user to show which devices are logged in.
        RefreshTokenList = await DbContext.AliasVaultUserRefreshTokens.Where(x => x.UserId == User.Id).Select(x => new AliasVaultUserRefreshToken()
            {
                Id = x.Id,
                DeviceIdentifier = x.DeviceIdentifier,
                IpAddress = x.IpAddress,
                ExpireDate = x.ExpireDate,
                CreatedAt = x.CreatedAt,
            })
            .Where(x => x.ExpireDate > DateTime.UtcNow)
            .OrderBy(x => x.CreatedAt)
            .ToListAsync();

        // Load all vaults for this user (do not load the actual file content for performance reasons).
        VaultList = await DbContext.Vaults.Where(x => x.UserId == User.Id).Select(x => new Vault
            {
                Id = x.Id,
                Version = x.Version,
                RevisionNumber = x.RevisionNumber,
                FileSize = x.FileSize,
                CreatedAt = x.CreatedAt,
                UpdatedAt = x.UpdatedAt,
                Salt = x.Salt,
                Verifier = x.Verifier,
                VaultBlob = string.Empty,
                EncryptionType = x.EncryptionType,
                EncryptionSettings = x.EncryptionSettings,
                CredentialsCount = x.CredentialsCount,
                EmailClaimsCount = x.EmailClaimsCount,
            })
            .OrderBy(x => x.UpdatedAt)
            .ToListAsync();

        // Load all email claims for this user.
        EmailClaimList = await DbContext.UserEmailClaims.Where(x => x.UserId == User.Id)
            .OrderBy(x => x.CreatedAt)
            .ToListAsync();

        IsLoading = false;
        StateHasChanged();
    }

    /// <summary>
    /// This method will revoke a refresh token for the user which will log out the device.
    /// </summary>
    private async Task RevokeRefreshToken(AliasVaultUserRefreshToken entry)
    {
        var token = await DbContext.AliasVaultUserRefreshTokens.FindAsync(entry.Id);

        if (token != null)
        {
            DbContext.AliasVaultUserRefreshTokens.Remove(token);
            await DbContext.SaveChangesAsync();
            await RefreshData();
        }
    }

    /// <summary>
    /// This method enables two-factor authentication for the user based on existing keys. If no keys are present
    /// then 2FA will not work. The user will need to manually set up a new authenticator device.
    /// </summary>
    private async Task EnableTwoFactor()
    {
        User = await DbContext.AliasVaultUsers.FindAsync(Id);

        if (User != null)
        {
            User.TwoFactorEnabled = true;
            await DbContext.SaveChangesAsync();
            await RefreshData();
        }
    }

    /// <summary>
    /// This method disables two-factor authentication for the user. This will NOT remove the authenticator keys.
    /// This means the admin can re-enable 2FA for the user without the user having to set up a new authenticator
    /// keys.
    /// </summary>
    private async Task DisableTwoFactor()
    {
        User = await DbContext.AliasVaultUsers.FindAsync(Id);

        if (User != null)
        {
            User.TwoFactorEnabled = false;
            await DbContext.SaveChangesAsync();
            await RefreshData();
        }
    }

    /// <summary>
    /// This method resets the two-factor authentication for the user which will remove all authenticator keys. The
    /// next time the user enables two-factor authentication new keys will be generated. When keys are removed it
    /// also means 2FA cannot be re-enabled until the user manually sets up a new authenticator device.
    /// </summary>
    private async Task ResetTwoFactor()
    {
        User = await DbContext.AliasVaultUsers.FindAsync(Id);

        if (User != null)
        {
            // Remove all authenticator keys and recovery codes.
            await DbContext.UserTokens
                .Where(x => x.UserId == User.Id && (x.Name == "AuthenticatorKey" || x.Name == "RecoveryCodes"))
                .ForEachAsync(x => DbContext.UserTokens.Remove(x));

            await DbContext.SaveChangesAsync();
            await RefreshData();
        }
    }

    /// <summary>
    /// Handles the action of making a vault the current one.
    /// </summary>
    /// <param name="vault">The vault to make current.</param>
    private async Task MakeCurrentAsync(Vault vault)
    {
        if (await ConfirmModalService.ShowConfirmation(
                title: "Confirm Vault Restoration",
                message: @"Are you sure you want to restore this specific vault and make it the active one?

Important notes:
• The next time the user logs in, they will load this vault version.
• If the user has changed their password recently, their password will be reverted to the one associated with this vault.

Do you want to proceed with the restoration?")) {
            // Load vault
            var currentVault = await DbContext.Vaults.FindAsync(vault.Id);
            if (currentVault == null)
            {
                return;
            }

            // Update time to make this the current vault.
            currentVault.RevisionNumber = VaultList.MaxBy(x => x.RevisionNumber)!.RevisionNumber + 1;

            // Save it.
            await DbContext.SaveChangesAsync();

            // Reload the page.
            await RefreshData();
        }
    }
}
