using Microsoft.EntityFrameworkCore;
using Ikar.Server.Data;

namespace Ikar.Server.Infrastructure.Push;

public sealed class PushDispatchBackgroundService(
    IServiceScopeFactory serviceScopeFactory,
    PushDispatchQueue queue,
    ILogger<PushDispatchBackgroundService> logger) : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        if (!queue.IsDurable)
        {
            await ProcessFallbackQueueAsync(stoppingToken);
            return;
        }

        while (!stoppingToken.IsCancellationRequested)
        {
            var batch = await queue.LoadDueBatchAsync(stoppingToken);
            if (batch.Count == 0)
            {
                await queue.WaitForWorkAsync(stoppingToken);
                continue;
            }

            foreach (var workItem in batch)
            {
                try
                {
                    await ProcessAsync(workItem, stoppingToken);
                    await queue.CompleteAsync(workItem.OutboxId!.Value, stoppingToken);
                }
                catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested)
                {
                    return;
                }
                catch (Exception ex)
                {
                    logger.LogWarning(
                        ex,
                        "Background push dispatch failed for chat {ChatId}, message {MessageId}, kind {Kind}.",
                        workItem.ChatId,
                        workItem.MessageId,
                        workItem.Kind);
                    await queue.RescheduleAsync(workItem.OutboxId!.Value, ex, stoppingToken);
                }
            }
        }
    }

    private async Task ProcessFallbackQueueAsync(CancellationToken stoppingToken)
    {
        await foreach (var workItem in queue.ReadAllAsync(stoppingToken))
        {
            await ProcessAsync(workItem, stoppingToken);
        }
    }

    private async Task ProcessAsync(PushDispatchWorkItem workItem, CancellationToken cancellationToken)
    {
        await using var scope = serviceScopeFactory.CreateAsyncScope();
        var dbContext = scope.ServiceProvider.GetRequiredService<IkarDbContext>();
        var dispatcher = scope.ServiceProvider.GetRequiredService<PushNotificationDispatcher>();

        var chat = await dbContext.Chats
            .Include(x => x.Members)
            .SingleOrDefaultAsync(x => x.Id == workItem.ChatId, cancellationToken);
        if (chat is null)
        {
            return;
        }

        var message = await dbContext.Messages
            .Include(x => x.Sender)
            .Include(x => x.Attachments)
            .SingleOrDefaultAsync(
                x => x.Id == workItem.MessageId && x.ChatId == workItem.ChatId,
                cancellationToken);
        if (message is null)
        {
            return;
        }

        if (workItem.Kind == PushDispatchKind.MessageReaction)
        {
            if (workItem.ActorUserId is null || string.IsNullOrWhiteSpace(workItem.ReactionEmoji))
            {
                return;
            }

            var reactor = await dbContext.Users
                .SingleOrDefaultAsync(x => x.Id == workItem.ActorUserId.Value, cancellationToken);
            if (reactor is null)
            {
                return;
            }

            await dispatcher.DispatchMessageReactionAsync(
                chat,
                message,
                reactor,
                workItem.ReactionEmoji,
                cancellationToken);
            return;
        }

        await dispatcher.DispatchMessageAsync(chat, message, cancellationToken);
    }
}
