@using System.Net
@using System.Text.Json
@using AliasVault.Shared.Models.Spamok
@using AliasVault.Shared.Models.WebApi
@inherits ComponentBase
@inject IHttpClientFactory HttpClientFactory
@inject HttpClient HttpClient
@inject JsInteropService JsInteropService
@inject DbService DbService
@inject Config Config
@inject EmailService EmailService
@using System.Timers
@inject ILogger<RecentEmails> Logger
@implements IDisposable

@if (EmailModalVisible)
{
    <EmailModal Email="@Email" IsSpamOk="@IsSpamOk" OnClose="CloseEmailModal" OnEmailDeleted="ManualRefresh" />
}

@if (ShowComponent)
{
    <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">Email</h3>
            </div>
            <div class="flex justify-end items-center space-x-2">
                @if (RefreshTimer is not null)
                {
                    <div class="w-3 h-3 mr-2 rounded-full bg-primary-300 border-2 border-primary-100 animate-pulse" title="Auto-refresh enabled"></div>
                }
                <button id="recent-email-refresh" @onclick="ManualRefresh" type="button" class="text-blue-700 border border-blue-700 hover:bg-blue-700 hover:text-white focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-full text-sm p-2.5 text-center inline-flex items-center dark:border-blue-500 dark:text-blue-500 dark:hover:text-white dark:focus:ring-blue-800 dark:hover:bg-blue-500">
                    Refresh
                </button>
            </div>
        </div>

        @if (IsLoading)
        {
            <LoadingIndicator/>
        }
        else if (!string.IsNullOrEmpty(Error))
        {
            <AlertMessageError Message="@Error" />
        }
        else if (MailboxEmails.Count == 0)
        {
            <div class="text-gray-900 dark:text-white">No emails received (yet).</div>
        }
        else
        {
            <div class="flex flex-col mt-6">
                <div class="overflow-x-auto rounded-lg">
                    <div class="inline-block min-w-full align-middle">
                        <div class="overflow-hidden shadow sm:rounded-lg">
                            <table class="min-w-full divide-y divide-gray-200 dark:divide-gray-600">
                                <thead class="bg-gray-50 dark:bg-gray-700">
                                <tr>
                                    <th scope="col" class="p-4 text-xs font-medium tracking-wider text-left text-gray-500 uppercase dark:text-white">
                                        Subject
                                    </th>
                                    <th scope="col" class="p-4 text-xs font-medium tracking-wider text-left text-gray-500 uppercase dark:text-white">
                                        Date &amp; Time
                                    </th>
                                </tr>
                                </thead>
                                <tbody class="bg-white dark:bg-gray-800">
                                @foreach (var mail in MailboxEmails)
                                {
                                    <tr class="hover:bg-gray-50 dark:hover:bg-gray-600">
                                        <td class="p-4 text-sm font-normal text-gray-900 whitespace-nowrap dark:text-white">
                                            <span class="cursor-pointer" @onclick="() => OpenEmail(mail.Id)">@(mail.Subject.Substring(0, mail.Subject.Length > 30 ? 30 : mail.Subject.Length))...</span>
                                        </td>
                                        <td class="p-4 text-sm font-normal text-gray-500 whitespace-nowrap dark:text-gray-400">
                                            <span class="cursor-pointer" @onclick="() => OpenEmail(mail.Id)">@mail.DateSystem</span>
                                        </td>
                                    </tr>
                                }
                                </tbody>
                            </table>
                        </div>
                    </div>
                </div>
            </div>
        }
    </div>
}

@code {
    /// <summary>
    /// The email address to show recent emails for.
    /// </summary>
    [Parameter]
    public string? EmailAddress { get; set; } = string.Empty;

    private List<MailboxEmailApiModel> MailboxEmails { get; set; } = new();
    private bool ShowComponent { get; set; } = false;
    private EmailApiModel Email { get; set; } = new();
    private bool EmailModalVisible { get; set; }
    private string Error { get; set; } = string.Empty;
    private Timer? RefreshTimer { get; set; }

    private bool IsRefreshing { get; set; } = true;
    private bool IsLoading { get; set; } = true;

    private bool IsSpamOk { get; set; } = false;


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

        if (EmailAddress is null)
        {
            return;
        }

        // Check if email has a known SpamOK domain, if not, don't show this component.
        if (IsSpamOkDomain(EmailAddress) || IsAliasVaultDomain(EmailAddress))
        {
            ShowComponent = true;
        }
        IsSpamOk = IsSpamOkDomain(EmailAddress);

        // Only enable auto-refresh if the setting is enabled.
        if (DbService.Settings.AutoEmailRefresh)
        {
            RefreshTimer = new Timer(2000);
            RefreshTimer.Elapsed += async (sender, e) => await TimerRefresh();
            RefreshTimer.Start();
        }
    }

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

        if (EmailAddress is null)
        {
            return;
        }

        IsSpamOk = IsSpamOkDomain(EmailAddress);
    }

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

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

        if (!ShowComponent)
        {
            return;
        }

        if (firstRender)
        {
            await ManualRefresh();
        }
    }

    /// <summary>
    /// Returns true if the email address is from a known SpamOK domain.
    /// </summary>
    private bool IsSpamOkDomain(string email)
    {
        return Config.PublicEmailDomains.Exists(x => email.EndsWith(x));
    }

    /// <summary>
    /// Returns true if the email address is from a known AliasVault domain.
    /// </summary>
    private bool IsAliasVaultDomain(string email)
    {
        return Config.PrivateEmailDomains.Exists(x => email.EndsWith(x));
    }

    private async Task TimerRefresh()
    {
        IsRefreshing = true;
        StateHasChanged();
        await LoadRecentEmailsAsync();
        IsRefreshing = false;
        StateHasChanged();
    }

    private async Task ManualRefresh()
    {
        IsLoading = true;
        StateHasChanged();
        await LoadRecentEmailsAsync();
        IsLoading = false;
        StateHasChanged();
    }

    private async Task LoadRecentEmailsAsync()
    {
        if (!ShowComponent || EmailAddress is null)
        {
            return;
        }

        Error = string.Empty;
        StateHasChanged();

        // Get email prefix, which is the part before the @ symbol.
        string emailPrefix = EmailAddress.Split('@')[0];

        if (IsSpamOkDomain(EmailAddress))
        {
            await LoadSpamOkEmails(emailPrefix);
        }
        else if (IsAliasVaultDomain(EmailAddress))
        {
            await LoadAliasVaultEmails();
        }
    }

    /// <summary>
    /// Open the email modal.
    /// </summary>
    private async Task OpenEmail(int emailId)
    {
        if (EmailAddress is null)
        {
            return;
        }

        // Get email prefix, which is the part before the @ symbol.
        string emailPrefix = EmailAddress.Split('@')[0];

        if (IsSpamOkDomain(EmailAddress))
        {
            await ShowSpamOkEmailInModal(emailPrefix, emailId);
        }
        else if (IsAliasVaultDomain(EmailAddress))
        {
            await ShowAliasVaultEmailInModal(emailId);
        }
    }

    /// <summary>
    /// Load recent emails from SpamOK.
    /// </summary>
    private async Task LoadSpamOkEmails(string emailPrefix)
    {
        // We construct a new HttpClient to avoid using the default one, which is used for the API and sends
        // the Authorization header. We don't want to send the Authorization header to the external email API.
        var client = HttpClientFactory.CreateClient("EmailClient");
        var mailbox = await client.GetFromJsonAsync<MailboxApiModel>($"https://api.spamok.com/v2/EmailBox/{emailPrefix}");

        if (mailbox?.Mails != null)
        {
            // Show maximum of 10 recent emails.
            MailboxEmails = mailbox.Mails.Take(10).ToList();
        }
    }

    /// <summary>
    /// Load recent emails from SpamOK.
    /// </summary>
    private async Task ShowSpamOkEmailInModal(string emailPrefix, int emailId)
    {
        var client = HttpClientFactory.CreateClient("EmailClient");
        EmailApiModel? mail = await client.GetFromJsonAsync<EmailApiModel>($"https://api.spamok.com/v2/Email/{emailPrefix}/{emailId}");
        if (mail != null)
        {
            Email = mail;
            EmailModalVisible = true;
            StateHasChanged();
        }
    }

    /// <summary>
    /// Load recent emails from AliasVault.
    /// </summary>
    private async Task LoadAliasVaultEmails()
    {
        var request = new HttpRequestMessage(HttpMethod.Get, $"v1/EmailBox/{EmailAddress}");
        try
        {
            var response = await HttpClient.SendAsync(request);
            if (response.IsSuccessStatusCode)
            {
                var mailbox = await response.Content.ReadFromJsonAsync<MailboxApiModel>();
                await UpdateMailboxEmails(mailbox);
            }
            else
            {
                var errorContent = await response.Content.ReadAsStringAsync();
                var errorResponse = JsonSerializer.Deserialize<ApiErrorResponse>(errorContent);
                switch (response.StatusCode)
                {
                    case HttpStatusCode.BadRequest:
                        if (errorResponse != null)
                        {
                            switch (errorResponse.Code)
                            {
                                case "CLAIM_DOES_NOT_MATCH_USER":
                                    Error = "The current chosen email address is already in use. Please change the email address by editing this credential.";
                                    break;
                                case "CLAIM_DOES_NOT_EXIST":
                                    Error = "An error occurred while trying to load the emails. Please try to edit and " +
                                            "save the credential entry to synchronize the database, then try again.";
                                    break;
                                default:
                                    throw new ArgumentException(errorResponse.Message);
                            }
                        }

                        break;
                    case HttpStatusCode.Unauthorized:
                        throw new UnauthorizedAccessException(errorResponse?.Message);
                    default:
                        throw new WebException(errorResponse?.Message);
                }
            }
        }
        catch (Exception ex)
        {
            Error = ex.Message;
            Logger.LogError(ex, "An error occurred while loading AliasVault emails.");
        }
    }

    /// <summary>
    /// Update the mailbox emails and decrypt the subject locally.
    /// </summary>
    private async Task UpdateMailboxEmails(MailboxApiModel? mailbox)
    {
        if (mailbox?.Mails != null)
        {
            // Show maximum of 10 recent emails.
            MailboxEmails = mailbox.Mails.Take(10).ToList();
        }

        MailboxEmails = await EmailService.DecryptEmailList(MailboxEmails);
    }

    /// <summary>
    /// Load recent emails from AliasVault.
    /// </summary>
    private async Task ShowAliasVaultEmailInModal(int emailId)
    {
        EmailApiModel? mail = await HttpClient.GetFromJsonAsync<EmailApiModel>($"v1/Email/{emailId}");
        if (mail != null)
        {
            // Decrypt the email content locally.
            var context = await DbService.GetDbContextAsync();
            var privateKey = await context.EncryptionKeys.FirstOrDefaultAsync(x => x.PublicKey == mail.EncryptionKey);
            if (privateKey is not null)
            {
                mail = await EmailService.DecryptEmail(mail);
            }

            Email = mail;
            EmailModalVisible = true;
            StateHasChanged();
        }
    }

    /// <summary>
    /// Close the email modal.
    /// </summary>
    private void CloseEmailModal()
    {
        EmailModalVisible = false;
        StateHasChanged();
    }
}
