@page "/emails"
@using AliasVault.RazorComponents.Tables
@inherits MainBase

<LayoutPageTitle>Emails</LayoutPageTitle>

<PageHeader
    BreadcrumbItems="@BreadcrumbItems"
    Title="@(TotalRecords > 0 ? $"Emails ({TotalRecords:N0})" : "Emails")"
    Description="This page shows all received mails by this AliasVault server. All email fields except 'To' are encrypted with the public key of the user and are unreadable by the server.">
    <CustomActions>
        <a href="email-storage-stats" class="inline-flex items-center px-3 py-2 text-sm font-medium text-gray-600 bg-gray-100 border border-gray-300 rounded-md hover:bg-gray-200 hover:text-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500 dark:bg-gray-700 dark:text-gray-300 dark:border-gray-600 dark:hover:bg-gray-600 dark:hover:text-gray-200 mr-3">
            <svg class="w-4 h-4 mr-2" fill="currentColor" viewBox="0 0 20 20">
                <path d="M3 4a1 1 0 011-1h12a1 1 0 011 1v2a1 1 0 01-1 1H4a1 1 0 01-1-1V4zM3 10a1 1 0 011-1h6a1 1 0 011 1v6a1 1 0 01-1 1H4a1 1 0 01-1-1v-6zM14 9a1 1 0 00-1 1v6a1 1 0 001 1h2a1 1 0 001-1v-6a1 1 0 00-1-1h-2z"/>
            </svg>
            Email Storage Stats
        </a>
        <RefreshButton OnClick="() => RefreshData(CancellationToken.None)" ButtonText="Refresh" />
    </CustomActions>
</PageHeader>

@if (IsInitialized)
{
    <div class="px-4">
        <ResponsivePaginator CurrentPage="CurrentPage" PageSize="PageSize" TotalRecords="TotalRecords" OnPageChanged="HandlePageChanged" />
        <div class="mb-3">
            <div class="relative">
                <SearchIcon />
                <input type="text" @bind-value="SearchTerm" @bind-value:event="oninput" id="search" placeholder="Search emails..." class="w-full px-4 ps-10 py-2 border rounded text-sm text-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white">
            </div>
        </div>
    </div>
}

@if (IsLoading)
{
    <LoadingIndicator />
}
else
{
    <div class="overflow-x-auto px-4">
        <SortableTable Columns="@_tableColumns" SortColumn="@SortColumn" SortDirection="@SortDirection" OnSortChanged="HandleSortChanged">
            @foreach (var viewModel in EmailViewModelList)
            {
                <SortableTableRow>
                    <SortableTableColumn IsPrimary="true">@viewModel.Email.Id</SortableTableColumn>
                    <SortableTableColumn>@viewModel.Email.DateSystem.ToString("yyyy-MM-dd HH:mm")</SortableTableColumn>
                    <SortableTableColumn>@(viewModel.Email.FromLocal.Length > 10 ? viewModel.Email.FromLocal.Substring(0, 10) : viewModel.Email.FromLocal)@@@(viewModel.Email.FromDomain.Length > 10 ? viewModel.Email.FromDomain.Substring(0, 10) : viewModel.Email.FromDomain)</SortableTableColumn>
                    <SortableTableColumn>@viewModel.Email.ToLocal@@@viewModel.Email.ToDomain</SortableTableColumn>
                    <SortableTableColumn>
                        <UserNameDisplay User="@viewModel.User" />
                    </SortableTableColumn>
                    <SortableTableColumn>@viewModel.Email.Attachments.Count</SortableTableColumn>
                </SortableTableRow>
            }
        </SortableTable>
    </div>
}

@code {
    /// <summary>
    /// The search term from the query parameter.
    /// </summary>
    [Parameter]
    [SupplyParameterFromQuery(Name = "search")]
    public string? SearchTermFromQuery { get; set; }

    private readonly List<TableColumn> _tableColumns = [
        new TableColumn { Title = "ID", PropertyName = "Id" },
        new TableColumn { Title = "Time", PropertyName = "DateSystem" },
        new TableColumn { Title = "From", PropertyName = "From" },
        new TableColumn { Title = "To", PropertyName = "To" },
        new TableColumn { Title = "User", Sortable = false },
        new TableColumn { Title = "Attachments", PropertyName = "Attachments" },
    ];

    private List<EmailViewModel> EmailViewModelList { get; set; } = [];
    private bool IsInitialized { get; set; } = false;
    private bool IsLoading { get; set; } = true;
    private int CurrentPage { get; set; } = 1;
    private int PageSize { get; set; } = 50;
    private int TotalRecords { get; set; }
    private string _searchTerm = string.Empty;
    private CancellationTokenSource? _searchCancellationTokenSource;

    /// <summary>
    /// The last search term.
    /// </summary>
    private string _lastSearchTerm = string.Empty;

    private string SearchTerm
    {
        get => _searchTerm;
        set
        {
            if (_searchTerm != value)
            {
                _searchTerm = value;
                _searchCancellationTokenSource?.Cancel();
                _searchCancellationTokenSource = new CancellationTokenSource();
                _ = RefreshData(_searchCancellationTokenSource.Token);
            }
        }
    }

    private string SortColumn { get; set; } = "Id";
    private SortDirection SortDirection { get; set; } = SortDirection.Descending;
    private async Task HandleSortChanged((string column, SortDirection direction) sort)
    {
        SortColumn = sort.column;
        SortDirection = sort.direction;
        await RefreshData(CancellationToken.None);
    }

    /// <inheritdoc />
    protected override async Task OnInitializedAsync()
    {
        await base.OnInitializedAsync();
        BreadcrumbItems.Add(new BreadcrumbItem { DisplayName = "Emails" });
    }

    /// <inheritdoc />
    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            // Set the search term from the query parameter if it exists
            if (!string.IsNullOrEmpty(SearchTermFromQuery))
            {
                _searchTerm = SearchTermFromQuery;
            }

            await RefreshData(CancellationToken.None);
        }
    }

    private void HandlePageChanged(int newPage)
    {
        CurrentPage = newPage;
        _ = RefreshData(CancellationToken.None);
    }

    private async Task RefreshData(CancellationToken cancellationToken = default)
    {
        try
        {
            IsLoading = true;
            StateHasChanged();

            await using var dbContext = await DbContextFactory.CreateDbContextAsync(cancellationToken);

            IQueryable<Email> query = dbContext.Emails;

            query = ApplySearchFilter(query);
            query = ApplySort(query);

            TotalRecords = await query.CountAsync(cancellationToken);
            var emailList = await query
                .Skip((CurrentPage - 1) * PageSize)
                .Take(PageSize)
                .ToListAsync(cancellationToken);

            if (cancellationToken.IsCancellationRequested)
            {
                return;
            }

            // Get all usernames for the emails in the current list
            var encryptionKeyIds = emailList.Select(x => x.UserEncryptionKeyId).Distinct().ToList();
            var encryptionKeyUsernames = await dbContext.UserEncryptionKeys
                        .Where(x => encryptionKeyIds.Contains(x.Id))
                        .Join(dbContext.AliasVaultUsers, x => x.UserId, y => y.Id, (x, y) => new { EncryptionKeyId = x.Id, UserId = y.Id, y.UserName, y.Blocked })
                        .ToListAsync(cancellationToken);

            // Create new list of viewmodels
            EmailViewModelList = new List<EmailViewModel>();

            foreach (var email in emailList)
            {
                var encryptionKey = encryptionKeyUsernames.FirstOrDefault(x => x.EncryptionKeyId == email.UserEncryptionKeyId);
                EmailViewModelList.Add(new EmailViewModel
                {
                    Email = email,
                    User = new UserDisplay
                    {
                        UserId = encryptionKey?.UserId,
                        UserName = encryptionKey?.UserName,
                        Blocked = encryptionKey?.Blocked ?? false,
                    },
                });
            }

            IsLoading = false;
            IsInitialized = true;
            StateHasChanged();
        }
        catch (OperationCanceledException)
        {
            // Expected when cancellation is requested, do nothing
        }
    }

    /// <summary>
    /// Applies a search filter to the query based on the search term.
    /// </summary>
    /// <param name="query">The query to filter.</param>
    /// <returns>The filtered query.</returns>
    private IQueryable<Email> ApplySearchFilter(IQueryable<Email> query)
    {
        if (SearchTerm.Length > 0)
        {
            // Reset page number back to 1 if the search term has changed.
            if (SearchTerm != _lastSearchTerm && CurrentPage != 1)
            {
                CurrentPage = 1;
            }
            _lastSearchTerm = SearchTerm;

            query = query.Where(x => EF.Functions.Like(x.To.ToLower(), "%" + SearchTerm.Trim().ToLower() + "%"));
        }

        return query;
    }

    /// <summary>
    /// Applies sorting to the query based on the sort column and direction.
    /// </summary>
    /// <param name="query">The query to sort.</param>
    /// <returns>The sorted query.</returns>
    private IQueryable<Email> ApplySort(IQueryable<Email> query)
    {
        // Apply sort
        switch (SortColumn)
        {
            case "Id":
                query = SortDirection == SortDirection.Ascending
                    ? query.OrderBy(x => x.Id)
                    : query.OrderByDescending(x => x.Id);
                break;
            case "DateSystem":
                query = SortDirection == SortDirection.Ascending
                    ? query.OrderBy(x => x.DateSystem)
                    : query.OrderByDescending(x => x.DateSystem);
                break;
            case "From":
                query = SortDirection == SortDirection.Ascending
                    ? query.OrderBy(x => x.FromLocal + "@" + x.FromDomain)
                    : query.OrderByDescending(x => x.FromLocal + "@" + x.FromDomain);
                break;
            case "To":
                query = SortDirection == SortDirection.Ascending
                    ? query.OrderBy(x => x.ToLocal + "@" + x.ToDomain)
                    : query.OrderByDescending(x => x.ToLocal + "@" + x.ToDomain);
                break;
            case "Attachments":
                query = SortDirection == SortDirection.Ascending
                    ? query.OrderBy(x => x.Attachments.Count)
                    : query.OrderByDescending(x => x.Attachments.Count);
                break;
            default:
                query = query.OrderByDescending(x => x.DateSystem);
                break;
        }

        return query;
    }

    private sealed class EmailViewModel
    {
        public Email Email { get; set; } = new();
        public UserDisplay User { get; set; } = new();
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            _searchCancellationTokenSource?.Cancel();
            _searchCancellationTokenSource?.Dispose();
        }
        base.Dispose(disposing);
    }
}
