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)
    {
        await foreach (var workItem in queue.ReadAllAsync(stoppingToken))
        {
            try
            {
                await ProcessAsync(workItem, stoppingToken);
            }
            catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested)
            {
                break;
            }
            catch (Exception ex)
            {
                logger.LogWarning(
                    ex,
                    "Background push dispatch failed for chat {ChatId}, message {MessageId}, kind {Kind}.",
                    workItem.ChatId,
                    workItem.MessageId,
                    workItem.Kind);
            }
        }
    }

    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)
        {
            logger.LogDebug("Skipping push dispatch because chat {ChatId} was not found.", workItem.ChatId);
            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)
        {
            logger.LogDebug(
                "Skipping push dispatch because message {MessageId} for chat {ChatId} was not found.",
                workItem.MessageId,
                workItem.ChatId);
            return;
        }

        if (workItem.Kind == PushDispatchKind.MessageReaction)
        {
            if (workItem.ActorUserId is null || string.IsNullOrWhiteSpace(workItem.ReactionEmoji))
            {
                logger.LogDebug(
                    "Skipping reaction push dispatch for message {MessageId} because actor or emoji is missing.",
                    workItem.MessageId);
                return;
            }

            var reactor = await dbContext.Users
                .SingleOrDefaultAsync(x => x.Id == workItem.ActorUserId.Value, cancellationToken);
            if (reactor is null)
            {
                logger.LogDebug(
                    "Skipping reaction push dispatch because reactor {UserId} was not found.",
                    workItem.ActorUserId.Value);
                return;
            }

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

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