@inherits ComponentBase
@inject GlobalNotificationService GlobalNotificationService
@inject ConfirmModalService ConfirmModalService
@inject IStringLocalizerFactory LocalizerFactory
@inject IJSRuntime JSRuntime
@using AliasVault.RazorComponents.Services
@using TotpGenerator
@using Microsoft.Extensions.Localization
@using AliasVault.Client.Main.Components.Layout

<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 relative">
    <div class="flex justify-between items-start">
        <div>
            <h3 class="text-xl font-semibold dark:text-white">@Localizer["TwoFactorAuthenticationTitle"]</h3>
        </div>
        <div class="flex items-center gap-2">
            @if (TotpCodeList.Any(t => !t.IsDeleted) && !IsAddFormVisible)
            {
                <button id="add-totp-code" @onclick="ShowAddForm" type="button" class="text-primary-700 hover:text-white border border-primary-700 hover:bg-primary-800 focus:ring-2 focus:outline-none focus:ring-primary-300 font-medium rounded-lg text-xs w-6 h-6 flex items-center justify-center dark:border-primary-500 dark:text-primary-500 dark:hover:text-white dark:hover:bg-primary-600 dark:focus:ring-primary-800" title="@Localizer["AddTotpCodeDescription"]">
                    <svg class="w-3 h-3" fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24">
                        <path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
                    </svg>
                </button>
            }
            @if (CanRemove)
            {
                <button type="button" @onclick="HandleRemove" @onclick:preventDefault="true"
                        class="text-gray-400 hover:text-red-500 transition-colors w-6 h-6 flex items-center justify-center" title="@Localizer["Delete"]">
                    <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
                    </svg>
                </button>
            }
        </div>
    </div>

    @* Always show add form or existing codes - no empty state *@
    @if (IsAddFormVisible)
        {
            <div class="p-4 mb-4 bg-gray-50 border border-gray-200 rounded-lg dark:bg-gray-700 dark:border-gray-600 mt-4">
                <EditForm Model="@NewTotpCode" OnValidSubmit="AddTotpCode">
                    <DataAnnotationsValidator />
                    <div class="flex justify-between items-center mb-4">
                        <h4 class="text-lg font-medium text-gray-900 dark:text-white">@Localizer["AddTotpCodeModalTitle"]</h4>
                        @if (HasExistingCodes)
                        {
                            <button @onclick="HideAddForm" type="button" class="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm w-8 h-8 ms-auto inline-flex justify-center items-center dark:hover:bg-gray-600 dark:hover:text-white">
                                <svg class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
                                    <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"/>
                                </svg>
                                <span class="sr-only">@Localizer["CloseFormButton"]</span>
                            </button>
                        }
                    </div>
                    <p class="mb-4 text-sm text-gray-500 dark:text-gray-400">@Localizer["TotpInstructions"]</p>
                    <div class="mb-4">
                        <label for="totp-secret" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">@Localizer["SecretKeyLabel"]</label>
                        <InputTextOnInput id="totp-secret" @bind-Value="NewTotpCode.SecretKey" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-500 focus:border-primary-500 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white" placeholder="@Localizer["SecretKeyPlaceholder"]" />
                        <ValidationMessage For="@(() => NewTotpCode.SecretKey)" class="text-red-600 dark:text-red-400 text-sm mt-1" />
                    </div>
                    @* Name is optional and hidden by default; revealed on demand since most codes don't need one. *@
                    @if (ShowNameField)
                    {
                        <div class="mb-4">
                            <div class="flex items-center justify-between mb-2">
                                <label for="totp-name" class="text-sm font-medium text-gray-900 dark:text-white">@Localizer["NameOptionalLabel"]</label>
                                <button id="remove-totp-name" type="button" @onclick="HideNameField" class="w-5 h-5 flex items-center justify-center text-gray-300 hover:text-red-400 dark:text-gray-500 dark:hover:text-red-400 transition-colors" title="@SharedLocalizer["Remove"]">
                                    <svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                                        <line x1="18" y1="6" x2="6" y2="18"/>
                                        <line x1="6" y1="6" x2="18" y2="18"/>
                                    </svg>
                                </button>
                            </div>
                            <InputTextOnInput id="totp-name" @bind-Value="NewTotpCode.Name" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-500 focus:border-primary-500 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white" />
                            <ValidationMessage For="@(() => NewTotpCode.Name)" class="text-red-600 dark:text-red-400 text-sm mt-1" />
                        </div>
                    }
                    else
                    {
                        <div class="mb-4">
                            <button id="add-totp-name" type="button" @onclick="() => ShowNameField = true" class="text-sm font-medium text-primary-700 hover:text-primary-800 dark:text-primary-500 dark:hover:text-primary-400">
                                @Localizer["AddNameButton"]
                            </button>
                        </div>
                    }
                    <div class="flex justify-end">
                        <button id="save-totp-code" type="submit" class="text-white bg-primary-700 hover:bg-primary-800 focus:ring-4 focus:outline-none focus:ring-primary-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-primary-600 dark:hover:bg-primary-700 dark:focus:ring-primary-800">
                            @Localizer["SaveButton"]
                        </button>
                    </div>
                </EditForm>
            </div>
        }

        <div class="grid grid-cols-1 gap-4 mt-4">
            @foreach (var totpCode in TotpCodeList.Where(t => !t.IsDeleted))
            {
                <div class="p-2 ps-3 pe-3 bg-gray-50 border border-gray-200 rounded-lg dark:bg-gray-700 dark:border-gray-600">
                    <div class="flex justify-between items-center gap-2">
                        <div class="flex items-center flex-1">
                            <h4 class="text-sm font-medium text-gray-900 dark:text-white">@(string.IsNullOrEmpty(totpCode.Name) ? SharedLocalizer["TotpDefaultName"] : totpCode.Name)</h4>
                        </div>
                        <div class="flex items-center gap-2">
                            <div class="flex flex-col items-end">
                                <div class="text-sm text-gray-500 dark:text-gray-400">@Localizer["SaveToViewCodeMessage"]</div>
                            </div>
                            <button type="button" @onclick="() => ShowEditModal(totpCode)" class="edit-totp-code text-gray-400 hover:text-gray-600 dark:hover:text-gray-300" title="@SharedLocalizer["Edit"]">
                                <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"></path>
                                </svg>
                            </button>
                            <button type="button" @onclick="() => DeleteTotpCode(totpCode)" class="delete-totp-code text-red-600 hover:text-red-800 dark:text-red-500 dark:hover:text-red-400">
                                <svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
                                    <path fill-rule="evenodd" d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z" clip-rule="evenodd"></path>
                                </svg>
                            </button>
                        </div>
                    </div>
                </div>
            }
        </div>
</div>

@* Edit TOTP Modal *@
<FormModal IsOpen="@IsEditModalOpen"
           Title="@SharedLocalizer["Edit"]"
           ShowDefaultFooter="false"
           MaxWidth="lg"
           OnClose="CloseEditModal">
    <div class="space-y-4">
        @if (EditingTotpCode != null)
        {
            <div class="space-y-3">
                <div>
                    <div class="flex items-center justify-between mb-2">
                        <label class="text-sm font-medium text-gray-900 dark:text-white">@Localizer["SecretKeyLabel"]</label>
                        <button type="button" @onclick="ToggleQrCode" class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 p-1" title="@(ShowQrCode ? SharedLocalizer["Hide"] : SharedLocalizer["Show"]) QR Code">
                            <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v1m6 11h2m-6 0h-2v4m0-11v3m0 0h.01M12 12h4.01M16 20h4M4 12h4m12 0h.01M5 8h2a1 1 0 001-1V5a1 1 0 00-1-1H5a1 1 0 00-1 1v2a1 1 0 001 1zm12 0h2a1 1 0 001-1V5a1 1 0 00-1-1h-2a1 1 0 00-1 1v2a1 1 0 001 1zM5 20h2a1 1 0 001-1v-2a1 1 0 00-1-1H5a1 1 0 00-1 1v2a1 1 0 001 1z" />
                            </svg>
                        </button>
                    </div>
                    <input type="text" @bind="EditSecret" class="bg-gray-50 border border-gray-300 text-gray-900 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:text-white font-mono" placeholder="@Localizer["SecretKeyLabel"]" />
                    @if (ShowQrCode)
                    {
                        <div class="flex justify-center mt-3">
                            <div id="totp-qrcode-container" data-url="@GetTotpUri(EditingTotpCode)"></div>
                        </div>
                    }
                </div>

                @* Name is optional; hidden by default unless a custom name was set. *@
                @if (ShowEditNameField)
                {
                    <div>
                        <div class="flex items-center justify-between mb-2">
                            <label class="text-sm font-medium text-gray-900 dark:text-white">@Localizer["NameOptionalLabel"]</label>
                            <button type="button" @onclick="HideEditNameField" class="w-5 h-5 flex items-center justify-center text-gray-300 hover:text-red-400 dark:text-gray-500 dark:hover:text-red-400 transition-colors" title="@SharedLocalizer["Remove"]">
                                <svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                                    <line x1="18" y1="6" x2="6" y2="18"/>
                                    <line x1="6" y1="6" x2="18" y2="18"/>
                                </svg>
                            </button>
                        </div>
                        <input type="text" @bind="EditName" class="bg-gray-50 border border-gray-300 text-gray-900 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:text-white" placeholder="@Localizer["NameOptionalLabel"]" />
                    </div>
                }
                else
                {
                    <button type="button" @onclick="() => ShowEditNameField = true" class="text-sm font-medium text-primary-700 hover:text-primary-800 dark:text-primary-500 dark:hover:text-primary-400">
                        @Localizer["AddNameButton"]
                    </button>
                }

                <button type="button" @onclick="SaveEditedTotpCode" class="w-full text-white bg-primary-700 hover:bg-primary-800 focus:ring-4 focus:outline-none focus:ring-primary-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-primary-600 dark:hover:bg-primary-700 dark:focus:ring-primary-800">
                    @SharedLocalizer["Save"]
                </button>
            </div>
        }
    </div>
</FormModal>

@code {
    private IStringLocalizer Localizer => LocalizerFactory.Create("Components.Main.Components.TotpCodes.TotpCodes", "AliasVault.Client");

    private IStringLocalizer SharedLocalizer => LocalizerFactory.Create("SharedResources", "AliasVault.Client");

    /// <summary>
    /// The list of TOTP codes.
    /// </summary>
    [Parameter]
    public List<TotpCode> TotpCodeList { get; set; } = [];

    /// <summary>
    /// Event callback for when the TOTP codes list changes.
    /// </summary>
    [Parameter]
    public EventCallback<List<TotpCode>> TotpCodesChanged { get; set; }

    /// <summary>
    /// Gets or sets whether the section can be removed.
    /// </summary>
    [Parameter]
    public bool CanRemove { get; set; }

    /// <summary>
    /// Gets or sets the callback when the remove button is clicked.
    /// </summary>
    [Parameter]
    public EventCallback OnRemove { get; set; }

    /// <summary>
    /// Gets or sets the item display name for QR code generation.
    /// </summary>
    [Parameter]
    public string? ItemDisplayName { get; set; }

    /// <summary>
    /// Gets or sets the item username or email for QR code generation.
    /// </summary>
    [Parameter]
    public string? ItemUsername { get; set; }

    private bool IsAddFormVisible { get; set; } = false;
    private bool ShowNameField { get; set; } = false;
    private TotpCodeEdit NewTotpCode { get; set; } = new();
    private List<Guid> OriginalTotpCodeIds { get; set; } = [];
    private bool IsEditModalOpen { get; set; } = false;
    private TotpCode? EditingTotpCode { get; set; }
    private string EditName { get; set; } = string.Empty;
    private bool ShowEditNameField { get; set; } = false;
    private string EditSecret { get; set; } = string.Empty;
    private bool ShowQrCode { get; set; } = false;

    /// <summary>
    /// Returns true if there are existing (non-deleted) TOTP codes.
    /// </summary>
    private bool HasExistingCodes => TotpCodeList.Any(t => !t.IsDeleted);

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

    /// <inheritdoc/>
    protected override void OnInitialized()
    {
        base.OnInitialized();

        // Keep track of the original TOTP codes.
        OriginalTotpCodeIds = TotpCodeList.Where(t => !t.IsDeleted).Select(t => t.Id).ToList();

        // Auto-show the add form if there are no existing codes
        if (!TotpCodeList.Any(t => !t.IsDeleted))
        {
            ShowAddForm();
        }
    }

    /// <summary>
    /// Shows the add form.
    /// </summary>
    private void ShowAddForm()
    {
        NewTotpCode = new TotpCodeEdit();
        ShowNameField = false;
        IsAddFormVisible = true;
    }

    /// <summary>
    /// Hides the optional name field again and clears any entered value.
    /// </summary>
    private void HideNameField()
    {
        NewTotpCode.Name = null;
        ShowNameField = false;
    }

    /// <summary>
    /// Hides the add form.
    /// </summary>
    private void HideAddForm()
    {
        IsAddFormVisible = false;
    }

    /// <summary>
    /// Adds a new TOTP code called from the add form.
    /// </summary>
    private async Task AddTotpCode()
    {
        // Sanitize the secret key by converting from URI to secret key and name.
        try {
            var (secretKey, name) = TotpHelper.SanitizeSecretKey(NewTotpCode.SecretKey, NewTotpCode.Name);
            NewTotpCode.SecretKey = secretKey;
            NewTotpCode.Name = name;
        }
        catch (Exception ex)
        {
            GlobalNotificationService.AddErrorMessage(ex.Message, true);
            return;
        }

        // Create a new TOTP code in memory
        var newTotpCode = NewTotpCode.ToEntity();
        newTotpCode.Name = NewTotpCode.Name ?? string.Empty;

        // Add to the list
        TotpCodeList.Add(newTotpCode);

        // Notify parent component
        await TotpCodesChanged.InvokeAsync(TotpCodeList);

        HideAddForm();
        StateHasChanged();
    }

    /// <summary>
    /// Deletes a TOTP code.
    /// </summary>
    /// <param name="totpCode">The TOTP code to delete.</param>
    private async Task DeleteTotpCode(TotpCode totpCode)
    {
        // Show confirmation modal.
        var result = await ConfirmModalService.ShowConfirmation(Localizer["DeleteTotpCodeTitle"], Localizer["DeleteTotpCodeConfirmation"], SharedLocalizer["Confirm"], SharedLocalizer["Cancel"]);
        if (!result)
        {
            return;
        }

        // Check if the TOTP code was part of the original set
        if (OriginalTotpCodeIds.Contains(totpCode.Id))
        {
            // If it was part of the original set, we soft delete it
            var totpCodeToDelete = TotpCodeList.FirstOrDefault(t => t.Id == totpCode.Id);
            if (totpCodeToDelete is not null)
            {
                totpCodeToDelete.IsDeleted = true;
                totpCodeToDelete.UpdatedAt = DateTime.UtcNow;
            }
        }
        else
        {
            // If it was not part of the original set, we hard delete it
            TotpCodeList.Remove(totpCode);
        }

        // Notify parent component
        await TotpCodesChanged.InvokeAsync(TotpCodeList);
        StateHasChanged();
    }

    /// <summary>
    /// Handles the remove section button click.
    /// </summary>
    private async Task HandleRemove()
    {
        await OnRemove.InvokeAsync();
    }

    /// <summary>
    /// Shows the edit modal for a TOTP code.
    /// </summary>
    /// <param name="totpCode">The TOTP code to edit.</param>
    private void ShowEditModal(TotpCode totpCode)
    {
        EditingTotpCode = totpCode;

        // Only reveal the name field when a name was actually set.
        EditName = totpCode.Name;
        ShowEditNameField = !string.IsNullOrEmpty(totpCode.Name);

        EditSecret = totpCode.SecretKey;
        ShowQrCode = false;
        IsEditModalOpen = true;
        StateHasChanged();
    }

    /// <summary>
    /// Closes the edit modal.
    /// </summary>
    private void CloseEditModal()
    {
        IsEditModalOpen = false;
        EditingTotpCode = null;
        EditName = string.Empty;
        ShowEditNameField = false;
        EditSecret = string.Empty;
        ShowQrCode = false;
        StateHasChanged();
    }

    /// <summary>
    /// Hides the edit name field again and clears any entered value so the code
    /// falls back to the default name on save.
    /// </summary>
    private void HideEditNameField()
    {
        EditName = string.Empty;
        ShowEditNameField = false;
    }

    /// <summary>
    /// Saves the edited TOTP code.
    /// </summary>
    private async Task SaveEditedTotpCode()
    {
        if (EditingTotpCode == null) return;

        var totpCodeToUpdate = TotpCodeList.FirstOrDefault(t => t.Id == EditingTotpCode.Id);
        if (totpCodeToUpdate != null)
        {
            // The name is optional; store it as-is (blank when the user left it empty).
            totpCodeToUpdate.Name = EditName?.Trim() ?? string.Empty;
            totpCodeToUpdate.SecretKey = EditSecret;
            totpCodeToUpdate.UpdatedAt = DateTime.UtcNow;
        }

        // Notify parent component
        await TotpCodesChanged.InvokeAsync(TotpCodeList);

        CloseEditModal();
    }

    /// <summary>
    /// Toggles the QR code visibility in the edit modal.
    /// </summary>
    private async Task ToggleQrCode()
    {
        ShowQrCode = !ShowQrCode;
        StateHasChanged();

        if (ShowQrCode)
        {
            // Wait for the QR code container to render, then generate QR code
            await Task.Delay(100);
            await JSRuntime.InvokeVoidAsync("generateQrCode", "totp-qrcode-container");
        }
    }

    /// <summary>
    /// Generates the TOTP URI for QR code generation.
    /// </summary>
    /// <param name="totpCode">The TOTP code.</param>
    /// <returns>The TOTP URI.</returns>
    private string GetTotpUri(TotpCode totpCode)
    {
        var issuer = ItemDisplayName ?? "AliasVault";
        var accountName = ItemUsername ?? totpCode.Name;
        var label = $"{Uri.EscapeDataString(issuer)}:{Uri.EscapeDataString(accountName)}";
        return $"otpauth://totp/{label}?secret={totpCode.SecretKey}&issuer={Uri.EscapeDataString(issuer)}";
    }
}
