using Ikar.Server.Data.Entities;
using Ikar.Server.Infrastructure.Push;
using Ikar.Shared;
using Microsoft.Extensions.Logging.Abstractions;

namespace Ikar.Server.Tests;

public sealed class PushNotificationDispatcherTests
{
    [Fact]
    public async Task DispatchMessageReactionAsync_SendsOnlyToMessageAuthorDevices()
    {
        await using var database = new SqliteTestDatabase();
        await using var dbContext = await database.CreateContextAsync();

        var owner = CreateUser("owner");
        var reactor = CreateUser("reactor");
        var ownerDevice = CreateAndroidDevice(owner, "owner-device", notificationsEnabled: true);
        var reactorDevice = CreateAndroidDevice(reactor, "reactor-device", notificationsEnabled: true);
        var disabledOwnerDevice = CreateAndroidDevice(owner, "disabled-owner-device", notificationsEnabled: false);
        var chat = CreateChat(owner, reactor);
        var message = new Message
        {
            Chat = chat,
            ChatId = chat.Id,
            Sender = owner,
            SenderId = owner.Id,
            Text = "hello"
        };

        dbContext.AddRange(owner, reactor, chat, message, ownerDevice, reactorDevice, disabledOwnerDevice);
        await dbContext.SaveChangesAsync();

        var sender = new RecordingPushNotificationSender();
        var dispatcher = new PushNotificationDispatcher(
            dbContext,
            sender,
            NullLogger<PushNotificationDispatcher>.Instance);

        await dispatcher.DispatchMessageReactionAsync(chat, message, reactor, "+1");

        var call = Assert.Single(sender.ReactionCalls);
        Assert.Equal(chat.Id, call.Chat.Id);
        Assert.Equal(message.Id, call.Message.Id);
        Assert.Equal(reactor.Id, call.Reactor.Id);
        Assert.Equal("+1", call.Emoji);
        var device = Assert.Single(call.Devices);
        Assert.Equal(ownerDevice.Id, device.Id);
    }

    [Fact]
    public async Task DispatchMessageReactionAsync_DoesNotNotifySelfReaction()
    {
        await using var database = new SqliteTestDatabase();
        await using var dbContext = await database.CreateContextAsync();

        var owner = CreateUser("owner");
        var ownerDevice = CreateAndroidDevice(owner, "owner-device", notificationsEnabled: true);
        var chat = CreateChat(owner);
        var message = new Message
        {
            Chat = chat,
            ChatId = chat.Id,
            Sender = owner,
            SenderId = owner.Id,
            Text = "hello"
        };

        dbContext.AddRange(owner, chat, message, ownerDevice);
        await dbContext.SaveChangesAsync();

        var sender = new RecordingPushNotificationSender();
        var dispatcher = new PushNotificationDispatcher(
            dbContext,
            sender,
            NullLogger<PushNotificationDispatcher>.Instance);

        await dispatcher.DispatchMessageReactionAsync(chat, message, owner, "+1");

        Assert.Empty(sender.ReactionCalls);
    }

    private static User CreateUser(string username) =>
        new()
        {
            Username = username,
            NormalizedUsername = username.ToUpperInvariant(),
            DisplayName = username,
            PasswordHash = "test"
        };

    private static Chat CreateChat(params User[] users)
    {
        var chat = new Chat
        {
            Type = users.Length == 1 ? ChatType.Direct : ChatType.Group,
            Title = "Test chat",
            CreatedBy = users[0],
            CreatedById = users[0].Id
        };

        foreach (var user in users)
        {
            chat.Members.Add(new ChatMember
            {
                Chat = chat,
                ChatId = chat.Id,
                User = user,
                UserId = user.Id
            });
        }

        return chat;
    }

    private static PushDevice CreateAndroidDevice(User user, string installationId, bool notificationsEnabled) =>
        new()
        {
            User = user,
            UserId = user.Id,
            Platform = PushPlatform.Android,
            InstallationId = installationId,
            DeviceToken = $"{installationId}-token",
            NotificationsEnabled = notificationsEnabled
        };

    private sealed class RecordingPushNotificationSender : IPushNotificationSender
    {
        public List<ReactionPushCall> ReactionCalls { get; } = [];

        public Task SendMessageAsync(
            Chat chat,
            Message message,
            IReadOnlyCollection<PushDevice> devices,
            CancellationToken cancellationToken = default) =>
            Task.CompletedTask;

        public Task SendMessageReactionAsync(
            Chat chat,
            Message message,
            User reactor,
            string emoji,
            IReadOnlyCollection<PushDevice> devices,
            CancellationToken cancellationToken = default)
        {
            ReactionCalls.Add(new ReactionPushCall(chat, message, reactor, emoji, devices.ToList()));
            return Task.CompletedTask;
        }

        public Task SendIncomingCallAsync(
            IncomingCallDto call,
            IReadOnlyCollection<PushDevice> devices,
            CancellationToken cancellationToken = default) =>
            Task.CompletedTask;

        public Task SendCallEndedAsync(
            CallEndedDto call,
            IReadOnlyCollection<PushDevice> devices,
            CancellationToken cancellationToken = default) =>
            Task.CompletedTask;

        public Task SendAppUpdateAvailableAsync(
            AndroidAppReleaseDto release,
            IReadOnlyCollection<PushDevice> devices,
            CancellationToken cancellationToken = default) =>
            Task.CompletedTask;
    }

    private sealed record ReactionPushCall(
        Chat Chat,
        Message Message,
        User Reactor,
        string Emoji,
        IReadOnlyCollection<PushDevice> Devices);
}
