@page "/settings/security/change-password"
@using AliasVault.Client.Utilities
@using AliasVault.Shared.Models.WebApi.PasswordChange
@using AliasVault.Cryptography.Client
@using SecureRemotePassword
@inherits MainBase
@inject HttpClient Http

<LayoutPageTitle>Change password</LayoutPageTitle>

<div class="grid grid-cols-1 px-4 pt-6 xl:grid-cols-3 xl:gap-4 dark:bg-gray-900">
    <div class="mb-4 col-span-full xl:mb-2">
        <Breadcrumb BreadcrumbItems="BreadcrumbItems"/>
        <H1>Change password</H1>
        <p class="mt-2 text-sm text-gray-600 dark:text-gray-400">Changing your master password also changes the vault encryption keys. It is advised to periodically change your master password to keep your vaults secure.</p>
    </div>
</div>

@if (IsLoading)
{
    <LoadingIndicator />
}
else
{
    <div class="p-4 mb-4  mx-4 bg-white border border-gray-200 rounded-lg shadow-sm dark:border-gray-700 sm:p-6 dark:bg-gray-800">
        <EditForm Model="@PasswordChangeModel" OnValidSubmit="@InitiatePasswordChange" class="space-y-4">
            <DataAnnotationsValidator />
            <ValidationSummary />

            <div>
                <label for="currentPassword" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Current Password</label>
                <InputText type="password" id="currentPassword" @bind-Value="PasswordChangeModel.CurrentPassword"
                           class="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-primary-500 focus:border-primary-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500"
                           required />
            </div>

            <div>
                <label for="newPassword" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">New Password</label>
                <InputText type="password" id="newPassword" @bind-Value="PasswordChangeModel.NewPassword"
                           class="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-primary-500 focus:border-primary-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500"
                           required />
            </div>

            <div>
                <label for="newPasswordConfirm" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Confirm New Password</label>
                <InputText type="password" id="newPasswordConfirm" @bind-Value="PasswordChangeModel.NewPasswordConfirm"
                           class="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-primary-500 focus:border-primary-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500"
                           required />
            </div>

            <button type="submit"
                    class="w-full bg-primary-500 text-white py-2 px-4 rounded-md hover:bg-primary-600 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 transition duration-150 ease-in-out">
                Change Password
            </button>
        </EditForm>
    </div>
}

@code {
    /// <summary>
    /// Gets or sets a value indicating whether the component is loading.
    /// </summary>
    private bool IsLoading { get; set; } = true;

    /// <summary>
    /// Gets or sets the password change model.
    /// </summary>
    private PasswordChangeModel PasswordChangeModel { get; set; } = new();

    /// <summary>
    /// Gets or sets the current user's password salt.
    /// </summary>
    private string CurrentSalt { get; set; } = string.Empty;

    /// <summary>
    /// Gets or sets the current user's password server ephemeral.
    /// </summary>
    private string CurrentServerEphemeral { get; set; } = string.Empty;

    /// <summary>
    /// Gets or sets the current user's password encryption type.
    /// </summary>
    private string CurrentEncryptionType { get; set; } = string.Empty;

    /// <summary>
    /// Gets or sets the current user's password encryption settings.
    /// </summary>
    private string CurrentEncryptionSettings { get; set; } = string.Empty;

    private SrpEphemeral ClientEphemeral = new();
    private SrpSession ClientSession = new();

    /// <inheritdoc />
    protected override async Task OnInitializedAsync()
    {
        await base.OnInitializedAsync();

        BreadcrumbItems.Add(new BreadcrumbItem { DisplayName = "Security settings", Url = "/settings/security" });
        BreadcrumbItems.Add(new BreadcrumbItem { DisplayName = "Change password" });
    }

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

        // Check on server if 2FA is enabled
        if (firstRender)
        {
            // Get the QR code and secret for the authenticator app.
            var response = await Http.GetFromJsonAsync<PasswordChangeInitiateResponse>("v1/Auth/change-password/initiate");

            if (response == null)
            {
                GlobalNotificationService.AddErrorMessage("Failed to initiate the password change process.", true);
                IsLoading = false;
                StateHasChanged();
                return;
            }

            CurrentServerEphemeral = response.ServerEphemeral;
            CurrentSalt = response.Salt;
            CurrentEncryptionType = response.EncryptionType;
            CurrentEncryptionSettings = response.EncryptionSettings;

            IsLoading = false;
            StateHasChanged();
        }
    }

    /// <summary>
    /// Initiates the password change process.
    /// </summary>
    private async Task InitiatePasswordChange()
    {
        GlobalLoadingSpinner.Show();
        GlobalNotificationService.ClearMessages();
        StateHasChanged();

        // Generate ephemeral for current password to verify it.
        var currentPasswordHash = await Encryption.DeriveKeyFromPasswordAsync(PasswordChangeModel.CurrentPassword, CurrentSalt, CurrentEncryptionType, CurrentEncryptionSettings);
        var currentPasswordHashString = BitConverter.ToString(currentPasswordHash).Replace("-", string.Empty);

        ClientEphemeral = Srp.GenerateEphemeralClient();
        var username = await GetUsernameAsync();
        var privateKey = Srp.DerivePrivateKey(CurrentSalt, username, currentPasswordHashString);
        ClientSession = Srp.DeriveSessionClient(
            privateKey,
            ClientEphemeral.Secret,
            CurrentServerEphemeral,
            CurrentSalt,
            username);

        // Generate salt and verifier for new password.
        var client = new SrpClient();
        var newSalt = client.GenerateSalt();

        byte[] newPasswordHash = await Encryption.DeriveKeyFromPasswordAsync(PasswordChangeModel.NewPassword, newSalt);
        var newPasswordHashString = BitConverter.ToString(newPasswordHash).Replace("-", string.Empty);

        // Backup current password hash in case of failure.
        var backupPasswordHash = AuthService.GetEncryptionKey();

        // Set new currentPasswordHash locally as it is required for the new database encryption call below so
        // it is encrypted with the new password hash.
        await AuthService.StoreEncryptionKeyAsync(newPasswordHash);

        var srpPasswordChange = Srp.PasswordChangeAsync(client, newSalt, username, newPasswordHashString);

        // Prepare new vault model to update to.
        var encryptedBase64String = await DbService.GetEncryptedDatabaseBase64String();
        var vault = await DbService.PrepareVaultForUploadAsync(encryptedBase64String);

        var vaultPasswordChangeObject = new VaultPasswordChangeRequest
        {
            Blob = vault.Blob,
            Version = vault.Version,
            CurrentRevisionNumber = vault.CurrentRevisionNumber,
            EncryptionPublicKey = vault.EncryptionPublicKey,
            CredentialsCount = vault.CredentialsCount,
            EmailAddressList = vault.EmailAddressList,
            CreatedAt = vault.CreatedAt,
            UpdatedAt = vault.UpdatedAt,
            CurrentClientPublicEphemeral = ClientEphemeral.Public,
            CurrentClientSessionProof = ClientSession.Proof,
            NewPasswordSalt = srpPasswordChange.Salt,
            NewPasswordVerifier = srpPasswordChange.Verifier
        };

        // Clear form.
        PasswordChangeModel = new PasswordChangeModel();

        // 4. Client sends proof of session key to server.
        var response = await Http.PostAsJsonAsync("v1/Vault/change-password", vaultPasswordChangeObject);
        var responseContent = await response.Content.ReadAsStringAsync();

        if (!response.IsSuccessStatusCode)
        {
            foreach (var error in ApiResponseUtility.ParseErrorResponse(responseContent))
            {
                GlobalNotificationService.AddErrorMessage(error, true);
            }

            // Set currentPasswordHash back to original, so we're back to the original state.
            await AuthService.StoreEncryptionKeyAsync(backupPasswordHash);

            GlobalLoadingSpinner.Hide();
            StateHasChanged();
            return;
        }

        // Set success message.
        GlobalNotificationService.AddSuccessMessage("Password changed successfully.", true);

        GlobalLoadingSpinner.Hide();
        StateHasChanged();
    }

}
