using PlotLine.Data;

namespace PlotLine.Services;

public sealed class EmailQueueWorker(
    IServiceScopeFactory scopeFactory,
    ILogger<EmailQueueWorker> logger) : BackgroundService
{
    private const int BatchSize = 10;
    private static readonly TimeSpan PollInterval = TimeSpan.FromSeconds(10);

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        logger.LogInformation("Email queue worker started.");

        await ProcessQueueAsync(stoppingToken);

        using var timer = new PeriodicTimer(PollInterval);
        while (await timer.WaitForNextTickAsync(stoppingToken))
        {
            await ProcessQueueAsync(stoppingToken);
        }
    }

    private async Task ProcessQueueAsync(CancellationToken stoppingToken)
    {
        try
        {
            using var scope = scopeFactory.CreateScope();
            var queue = scope.ServiceProvider.GetRequiredService<IEmailQueueRepository>();
            var sender = scope.ServiceProvider.GetRequiredService<IQueuedEmailSender>();

            await queue.ResetStuckSendingAsync();
            var emails = await queue.DequeueBatchAsync(BatchSize);
            if (!emails.Any())
            {
                return;
            }

            logger.LogInformation("Email queue worker dequeued {EmailCount} email(s).", emails.Count);

            foreach (var email in emails)
            {
                if (stoppingToken.IsCancellationRequested)
                {
                    return;
                }

                try
                {
                    await sender.SendAsync(email, stoppingToken);
                    await queue.MarkSentAsync(email.EmailQueueID);
                }
                catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested)
                {
                    return;
                }
                catch (Exception ex)
                {
                    logger.LogWarning(ex, "Queued email {EmailQueueID} failed on attempt {AttemptNumber}.", email.EmailQueueID, email.AttemptCount + 1);
                    await queue.MarkFailedAsync(email.EmailQueueID, BuildSafeError(ex));
                }
            }
        }
        catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested)
        {
        }
        catch (Exception ex)
        {
            logger.LogError(ex, "Email queue worker cycle failed.");
        }
    }

    private static string BuildSafeError(Exception exception)
    {
        var message = exception.Message;
        return message.Length > 2000 ? message[..2000] : message;
    }
}
