@page "/credentials/create"
@page "/credentials/{id:guid}/edit"
@inherits MainBase
@inject CredentialService CredentialService
@using System.Globalization
@using System.Text.Json
@using System.Text.Json.Serialization
@using AliasVault.Generators.Identity
@using AliasVault.Generators.Identity.Implementations.Factories
@using AliasVault.Generators.Identity.Models
@inject IJSRuntime JSRuntime
@implements IAsyncDisposable

<PageHeader
    BreadcrumbItems="@BreadcrumbItems"
    Title="@(EditMode ? "Edit credentials" : "Add credentials")"
    Description="@(EditMode ? "Edit the existing credentials entry below." : "Create a new credentials entry below.")">
    <CustomActions>
        <ConfirmButton OnClick="TriggerFormSubmit">Save Credentials</ConfirmButton>
        <CancelButton OnClick="Cancel">Cancel</CancelButton>
    </CustomActions>
</PageHeader>

@if (Loading)
{
    <LoadingIndicator />
}
else
{
    <EditForm @ref="EditFormRef" Model="Obj" OnValidSubmit="SaveAlias">
        <DataAnnotationsValidator />
        <div class="grid grid-cols-1 px-4 pt-6 md:grid-cols-2 lg:grid-cols-3 md:gap-4 dark:bg-gray-900">
            <div class="col-span-1 md:col-span-1 lg:col-span-1">
                <div class="p-4 mb-4 bg-white border-2 border-primary-600 rounded-lg shadow-sm 2xl:col-span-2 dark:border-gray-700 sm:p-6 dark:bg-gray-800">
                    <h3 class="mb-4 text-xl font-semibold dark:text-white">Service</h3>
                    <div class="grid gap-6">
                        <div class="col-span-6 sm:col-span-3">
                            <EditFormRow Id="service-name" Label="Service Name" Placeholder="E.g. Facebook" @bind-Value="Obj.ServiceName"></EditFormRow>
                            <ValidationMessage For="() => Obj.ServiceName"/>
                        </div>
                        <div class="col-span-6 sm:col-span-3">
                            <EditFormRow Id="service-url" OnFocus="OnFocusUrlInput" Label="Service URL" @bind-Value="Obj.ServiceUrl"></EditFormRow>
                        </div>
                    </div>
                </div>

                <div class="col-span-1 md:col-span-1 lg:col-span-1">
                    <div class="p-4 mb-4 bg-white border border-gray-200 rounded-lg shadow-sm 2xl:col-span-2 dark:border-gray-700 sm:p-6 dark:bg-gray-800">
                        <h3 class="mb-4 text-xl font-semibold dark:text-white">Notes</h3>
                        <div class="grid gap-6">
                            <div class="col-span-6 sm:col-span-3">
                                <EditFormRow Type="textarea" Id="notes" Label="Notes" @bind-Value="Obj.Notes"></EditFormRow>
                            </div>
                        </div>
                    </div>
                </div>

                <div class="col-span-1 md:col-span-1 lg:col-span-1">
                    <div class="p-4 mb-4 bg-white border border-gray-200 rounded-lg shadow-sm 2xl:col-span-2 dark:border-gray-700 sm:p-6 dark:bg-gray-800">
                        <h3 class="mb-4 text-xl font-semibold dark:text-white">Attachments</h3>
                        <div class="grid gap-6">
                            <div class="col-span-6 sm:col-span-3">
                                <AttachmentUploader
                                    Attachments="@Obj.Attachments"
                                    AttachmentsChanged="@HandleAttachmentsChanged" />
                            </div>
                        </div>
                    </div>
                </div>
            </div>
            <div class="col-span-1 md:col-span-1 lg:col-span-2">
                <div class="p-4 mb-4 bg-white border border-gray-200 rounded-lg shadow-sm 2xl:col-span-2 dark:border-gray-700 sm:p-6 dark:bg-gray-800">
                    <h3 class="mb-4 text-xl font-semibold dark:text-white">Login credentials</h3>
                    <div class="mb-4">
                        <Button OnClick="GenerateRandomAlias">Generate Random Alias</Button>
                    </div>
                    <div class="grid gap-6">
                        <div class="col-span-6 sm:col-span-3">
                            <EditEmailFormRow Id="email" Label="Email" @bind-Value="Obj.Alias.Email"></EditEmailFormRow>
                        </div>
                        <div class="col-span-6 sm:col-span-3">
                            <div class="relative">
                                <EditFormRow Id="username" Label="Username" @bind-Value="Obj.Username"></EditFormRow>
                                <button type="button" class="text-white absolute end-1 bottom-1 bg-gray-700 hover:bg-gray-800 focus:ring-4 focus:outline-none focus:ring-gray-300 font-medium rounded-lg text-sm px-4 py-2 dark:bg-gray-600 dark:hover:bg-gray-700 dark:focus:ring-gray-800" @onclick="GenerateRandomUsername">New Username</button>
                            </div>
                        </div>
                        <div class="col-span-6 sm:col-span-3">
                            <div class="relative">
                                <EditFormRow Id="password" Label="Password" @bind-Value="Obj.Password.Value"></EditFormRow>
                                <button type="button" class="text-white absolute end-1 bottom-1 bg-gray-700 hover:bg-gray-800 focus:ring-4 focus:outline-none focus:ring-gray-300 font-medium rounded-lg text-sm px-4 py-2 dark:bg-gray-600 dark:hover:bg-gray-700 dark:focus:ring-gray-800" @onclick="GenerateRandomPassword">New Password</button>
                            </div>
                        </div>
                    </div>
                </div>

                <div class="col-span-1 md:col-span-1 lg:col-span-2">
                    <div class="p-4 mb-4 bg-white border border-gray-200 rounded-lg shadow-sm 2xl:col-span-2 dark:border-gray-700 sm:p-6 dark:bg-gray-800">
                        <h3 class="mb-4 text-xl font-semibold dark:text-white">Alias</h3>
                        <div class="grid gap-6">
                            <div class="col-span-6 sm:col-span-3">
                                <EditFormRow Id="first-name" Label="First Name" @bind-Value="Obj.Alias.FirstName"></EditFormRow>
                            </div>
                            <div class="col-span-6 sm:col-span-3">
                                <EditFormRow Id="last-name" Label="Last Name" @bind-Value="Obj.Alias.LastName"></EditFormRow>
                            </div>
                            <div class="col-span-6 sm:col-span-3">
                                <EditFormRow Id="nickname" Label="Nick Name" @bind-Value="Obj.Alias.NickName"></EditFormRow>
                            </div>
                            <div class="col-span-6 sm:col-span-3">
                                <EditFormRow Id="gender" Label="Gender" @bind-Value="Obj.Alias.Gender"></EditFormRow>
                            </div>
                            <div class="col-span-6 sm:col-span-3">
                                <EditFormRow Id="birthdate" Label="Birth Date" @bind-Value="Obj.AliasBirthDate"></EditFormRow>
                                <ValidationMessage For="() => Obj.AliasBirthDate"/>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
        <button type="submit" class="hidden">Save Credentials</button>
    </EditForm>
}

@code {
    /// <summary>
    /// Gets or sets the Credentials ID.
    /// </summary>
    [Parameter]
    public Guid? Id { get; set; }

    private bool EditMode { get; set; }
    private EditForm EditFormRef { get; set; } = null!;
    private bool Loading { get; set; } = true;
    private CredentialEdit Obj { get; set; } = new();
    private IJSObjectReference? Module;

    /// <inheritdoc />
    async ValueTask IAsyncDisposable.DisposeAsync()
    {
        await KeyboardShortcutService.UnregisterShortcutAsync("gc");
        if (Module is not null)
        {
            await Module.DisposeAsync();
        }
    }

    /// <inheritdoc />
    protected override void OnInitialized()
    {
        if (Id.HasValue)
        {
            // Edit mode
            EditMode = true;
        }
        else
        {
            // Add mode
            EditMode = false;
        }
    }

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

        if (EditMode)
        {
            BreadcrumbItems.Add(new BreadcrumbItem { DisplayName = "View credentials entry", Url = $"/credentials/{Id}" });
            BreadcrumbItems.Add(new BreadcrumbItem { DisplayName = "Edit credential" });
        }
        else
        {
            BreadcrumbItems.Add(new BreadcrumbItem { DisplayName = "Add new credential" });
        }
    }

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

        if (firstRender)
        {
            Module = await JSRuntime.InvokeAsync<IJSObjectReference>("import", "./js/modules/newIdentityWidget.js");
            if (EditMode)
            {
                if (Id is null)
                {
                    // Error loading alias.
                    GlobalNotificationService.AddErrorMessage("This credential does not exist (anymore). Please try again.");
                    NavigationManager.NavigateTo("/", false, true);
                    return;
                }

                // Load existing Obj, retrieve from service
                var alias = await CredentialService.LoadEntryAsync(Id.Value);
                if (alias is null)
                {
                    // Error loading alias.
                    GlobalNotificationService.AddErrorMessage("This credential does not exist (anymore). Please try again.");
                    NavigationManager.NavigateTo("/", false, true);
                    return;
                }

                Obj = CredentialToCredentialEdit(alias);
                if (Obj.ServiceUrl is null)
                {
                    Obj.ServiceUrl = CredentialService.DefaultServiceUrl;
                }
            }
            else
            {
                // Create new Obj
                var alias = new Credential();
                alias.Alias = new Alias();
                alias.Alias.Email = "@" + CredentialService.GetDefaultEmailDomain();
                alias.Service = new Service();
                alias.Passwords = new List<Password> { new Password() };

                Obj = CredentialToCredentialEdit(alias);
                Obj.ServiceUrl = CredentialService.DefaultServiceUrl;
            }

            Loading = false;
            StateHasChanged();

            if (!EditMode)
            {
                // When creating a new alias: start with focus on the service name input.
                await JsInteropService.FocusElementById("service-name");
            }
        }
    }

    /// <summary>
    /// When the URL input is focused, place cursor at the end of the default URL to allow for easy typing.
    /// </summary>
    private void OnFocusUrlInput(FocusEventArgs e)
    {
        if (Obj.ServiceUrl != CredentialService.DefaultServiceUrl)
        {
            return;
        }

        // Use a small delay to ensure the focus is set after the browser's default behavior.
        Task.Delay(1).ContinueWith(_ =>
        {
            JSRuntime.InvokeVoidAsync("eval", $"document.getElementById('service-url').setSelectionRange({CredentialService.DefaultServiceUrl.Length}, {CredentialService.DefaultServiceUrl.Length})");
        });
    }

    private void HandleAttachmentsChanged(List<Attachment> updatedAttachments)
    {
        Obj.Attachments = updatedAttachments;
        StateHasChanged();
    }

    private async Task GenerateRandomAlias()
    {
        GlobalLoadingSpinner.Show();
        StateHasChanged();

        Obj = CredentialToCredentialEdit(await CredentialService.GenerateRandomIdentity(CredentialEditToCredential(Obj)));

        GlobalLoadingSpinner.Hide();
        StateHasChanged();
    }

    /// <summary>
    /// Generate a new random username based on existing identity, or if no identity is present,
    /// generate a new random identity.
    /// </summary>
    private async Task GenerateRandomUsername()
    {
        // If current object is null, then we create a new random identity.
        Identity identity;
        if (Obj.Alias.FirstName is null && Obj.Alias.LastName is null && Obj.Alias.BirthDate == DateTime.MinValue)
        {
            identity = await IdentityGeneratorFactory.CreateIdentityGenerator(DbService.Settings.DefaultIdentityLanguage).GenerateRandomIdentityAsync();
        }
        else
        {
            // Assemble identity model with the current values
            identity = new Identity
            {
                FirstName = Obj.Alias.FirstName ?? string.Empty,
                LastName = Obj.Alias.LastName ?? string.Empty,
                BirthDate = Obj.Alias.BirthDate,
                Gender = Obj.Alias.Gender == Gender.Female.ToString() ? Gender.Female : Gender.Male,
                NickName = Obj.Alias.NickName ?? string.Empty,
            };
        }

        var generator = new UsernameEmailGenerator();
        Obj.Username = generator.GenerateUsername(identity);
    }

    /// <summary>
    /// Generate a new random password.
    /// </summary>
    private void GenerateRandomPassword()
    {
        Obj.Password.Value = CredentialService.GenerateRandomPassword();
    }

    /// <summary>
    /// Helper method to convert a Credential object to a CredentialEdit object.
    /// </summary>
    private CredentialEdit CredentialToCredentialEdit(Credential alias)
    {
        // Create a deep copy of the alias object to prevent changes to the original object
        // when editing the alias in the form. We only want to save the changes when the user
        // clicks the save button.
        var options = new JsonSerializerOptions
        {
            ReferenceHandler = ReferenceHandler.Preserve,
            MaxDepth = 128 // Adjust this value as needed
        };

        // Create a deep copy of the credential object
        var aliasJson = JsonSerializer.Serialize(alias, options);
        var aliasCopy = JsonSerializer.Deserialize<Credential>(aliasJson, options)!;

        return new CredentialEdit
        {
            Id = aliasCopy.Id,
            Notes = aliasCopy.Notes ?? string.Empty,
            Username = aliasCopy.Username ?? string.Empty,
            ServiceName = aliasCopy.Service.Name ?? string.Empty,
            ServiceUrl = aliasCopy.Service.Url,
            ServiceLogo = aliasCopy.Service.Logo,
            Password = aliasCopy.Passwords.FirstOrDefault() ?? new Password
            {
                Value = string.Empty,
                CreatedAt = DateTime.UtcNow,
                UpdatedAt = DateTime.UtcNow,
            },
            Alias = aliasCopy.Alias,
            AliasBirthDate = aliasCopy.Alias.BirthDate.ToString("yyyy-MM-dd"),
            Attachments = aliasCopy.Attachments.ToList(),
            CreateDate = aliasCopy.CreatedAt,
            LastUpdate = aliasCopy.UpdatedAt
        };
    }

    /// <summary>
    /// Helper method to convert a CredentialEdit object to a Credential object.
    /// </summary>
    private Credential CredentialEditToCredential(CredentialEdit alias)
    {
        var credential = new Credential
        {
            Id = alias.Id,
            Notes = alias.Notes,
            Username = alias.Username,
            Service = new Service
            {
                Name = alias.ServiceName,
                Url = alias.ServiceUrl,
                Logo = alias.ServiceLogo,
            },
            Passwords = new List<Password>
            {
                alias.Password,
            },
            Alias = alias.Alias,
            Attachments = alias.Attachments,
        };

        if (string.IsNullOrWhiteSpace(alias.AliasBirthDate))
        {
            credential.Alias.BirthDate = DateTime.MinValue;
        }
        else
        {
            credential.Alias.BirthDate = DateTime.Parse(alias.AliasBirthDate, CultureInfo.InvariantCulture);
        }

        return credential;
    }

    /// <summary>
    /// Cancel the edit operation and navigate back to the credentials view.
    /// </summary>
    private void Cancel()
    {
        NavigationManager.NavigateTo("/credentials/" + Id);
    }

    /// <summary>
    /// Trigger the form submit.
    /// </summary>
    private async Task TriggerFormSubmit()
    {
        if (EditFormRef.EditContext?.Validate() == false)
        {
            return;
        }

        await SaveAlias();
    }

    /// <summary>
    /// Save the alias to the database.
    /// </summary>
    private async Task SaveAlias()
    {
        GlobalLoadingSpinner.Show();
        StateHasChanged();

        if (EditMode)
        {
            if (Id is not null)
            {
                Id = await CredentialService.UpdateEntryAsync(CredentialEditToCredential(Obj));
            }
        }
        else
        {
            Id = await CredentialService.InsertEntryAsync(CredentialEditToCredential(Obj));
        }

        GlobalLoadingSpinner.Hide();
        StateHasChanged();

        if (Id is null || Id == Guid.Empty)
        {
            // Error saving.
            GlobalNotificationService.AddErrorMessage("Error saving credentials. Please try again.", true);
            return;
        }

        // No error, add success message.
        if (EditMode)
        {
            GlobalNotificationService.AddSuccessMessage("Credential updated successfully.");
        }
        else
        {
            GlobalNotificationService.AddSuccessMessage("Credential created successfully.");
        }

        NavigationManager.NavigateTo("/credentials/" + Id);
    }
}
