@inherits ComponentBase
@inject ClipboardCopyService ClipboardCopyService
@inject JsInteropService JsInteropService
@inject IStringLocalizerFactory LocalizerFactory
@implements IDisposable
@using TotpGenerator
@using Microsoft.Extensions.Localization

<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">
    <div class="flex justify-between">
        <div>
            <h3 class="mb-4 text-xl font-semibold dark:text-white">@Localizer["TwoFactorAuthenticationTitle"]</h3>
        </div>
    </div>

    @if (IsLoading)
    {
        <LoadingIndicator />
    }
    else if (TotpCodeList.Count == 0)
    {
        <div class="flex flex-col justify-center">
            <p class="text-gray-500 dark:text-gray-400">@Localizer["NoTotpCodesMessage"]</p>
        </div>
    }
    else
    {
        <div class="grid grid-cols-1 gap-4 mt-4">
            @foreach (var totpCode in TotpCodeList)
            {
                <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">@totpCode.Name</h4>
                        </div>
                        <div class="flex items-center gap-2">
                            <div class="flex flex-col items-end">
                                <div class="totp-code text-lg font-bold cursor-pointer text-gray-900 dark:text-white hover:text-primary-600 dark:hover:text-primary-400 transition-colors" @onclick="() => CopyToClipboard(totpCode)">
                                    @GetTotpCode(totpCode.SecretKey)
                                </div>
                                <div class="text-xs">
                                    @if (IsCopied(totpCode.Id.ToString()))
                                    {
                                        <span class="text-green-600 dark:text-green-400">@Localizer["CopiedMessage"]</span>
                                    }
                                    else
                                    {
                                        <span class="text-gray-500 dark:text-gray-400">@GetRemainingSeconds()s</span>
                                    }
                                </div>
                            </div>
                            <div class="w-1.5 h-8 bg-gray-200 rounded-full dark:bg-gray-600">
                                <div class="bg-blue-600 rounded-full transition-all" style="height: @(GetRemainingPercentage())%; width: 100%"></div>
                            </div>
                        </div>
                    </div>
                </div>
            }
        </div>
    }
</div>

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

    /// <summary>
    /// The list of TOTP codes to display.
    /// </summary>
    [Parameter]
    public required ICollection<TotpCode> TotpCodeList { get; set; }

    /// <summary>
    /// The dictionary of current cached TOTP codes.
    /// </summary>
    private readonly Dictionary<string, string> _currentCodes = new();

    private bool IsLoading { get; set; } = true;
    private Timer? _refreshTimer;

    /// <inheritdoc />
    public void Dispose()
    {
        _refreshTimer?.Dispose();
    }

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

        // Generate initial codes
        foreach (var code in TotpCodeList.Select(t => t.SecretKey))
        {
            _currentCodes[code] = TotpGenerator.GenerateTotpCode(code);
        }

        // Start a timer to refresh the TOTP codes every second
        _refreshTimer = new Timer(async _ => await RefreshCodesAsync(), null, 0, 1000);

        IsLoading = false;
    }

    /// <summary>
    /// Gets the remaining seconds for the TOTP code.
    /// </summary>
    /// <returns>The remaining seconds.</returns>
    private static int GetRemainingSeconds(int step = 30)
    {
        var unixTimestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
        return step - (int)(unixTimestamp % step);
    }

    /// <summary>
    /// Gets the remaining percentage for the TOTP code.
    /// </summary>
    /// <returns>The remaining percentage.</returns>
    private static int GetRemainingPercentage()
    {
        var remaining = GetRemainingSeconds();
        // Invert the percentage so it counts down instead of up
        return (int)(((30.0 - remaining) / 30.0) * 100);
    }

    /// <summary>
    /// Refreshes the TOTP codes by generating new codes based on the secret keys.
    /// </summary>
    private async Task RefreshCodesAsync()
    {
        foreach (var code in TotpCodeList.Select(t => t.SecretKey))
        {
            var newCode = TotpGenerator.GenerateTotpCode(code);
            if (!_currentCodes.ContainsKey(code) || _currentCodes[code] != newCode)
            {
                _currentCodes[code] = newCode;
            }
        }

        // Always update the UI to refresh the progress bar
        await InvokeAsync(StateHasChanged);
    }

    /// <summary>
    /// Gets the TOTP code for a given secret key from the cached current codes dictionary.
    /// </summary>
    /// <param name="secretKey">The secret key to get the code for.</param>
    /// <returns>The TOTP code.</returns>
    private string GetTotpCode(string secretKey)
    {
        if (_currentCodes.TryGetValue(secretKey, out var code))
        {
            return code;
        }

        var newCode = TotpGenerator.GenerateTotpCode(secretKey);
        _currentCodes[secretKey] = newCode;
        return newCode;
    }

    /// <summary>
    /// Copies the TOTP code to the clipboard.
    /// </summary>
    /// <param name="totpCode">The TOTP code to copy.</param>
    private async Task CopyToClipboard(TotpCode totpCode)
    {
        var code = GetTotpCode(totpCode.SecretKey);
        await JsInteropService.CopyToClipboard(code);
        ClipboardCopyService.SetCopied(totpCode.Id.ToString());
        StateHasChanged();

        // After 2 seconds, reset the copied state
        await Task.Delay(2000);
        if (ClipboardCopyService.GetCopiedId() == totpCode.Id.ToString())
        {
            ClipboardCopyService.SetCopied(string.Empty);
        }
        StateHasChanged();
    }

    /// <summary>
    /// Checks if the TOTP code was last copied.
    /// </summary>
    /// <param name="code">The TOTP code to check.</param>
    /// <returns>True if the TOTP code was last copied, false otherwise.</returns>
    private bool IsCopied(string code) => ClipboardCopyService.GetCopiedId() == code;
}
