using Ikar.Server.Data.Entities;
using Ikar.Shared;

namespace Ikar.Server.Tests;

public sealed class SearchControllerTests
{
    [Fact]
    public async Task SearchPage_FiltersByAuthorDateAndType_AndReturnsJumpFields()
    {
        await using var database = new SqliteTestDatabase();
        await using var dbContext = await database.CreateContextAsync();
        var alice = CreateUser("alice");
        var bob = CreateUser("bob");
        var chat = CreateChat(alice, bob);
        var targetMessage = CreateMessage(chat, bob, "alpha image", new DateTimeOffset(2026, 1, 3, 10, 0, 0, TimeSpan.Zero));
        targetMessage.Attachments.Add(new MessageAttachment
        {
            MessageId = targetMessage.Id,
            OriginalFileName = "alpha.png",
            ContentType = "image/png",
            StoragePath = "alpha.png",
            FileSizeBytes = 123
        });
        dbContext.AddRange(
            alice,
            bob,
            chat,
            CreateMessage(chat, alice, "alpha document", new DateTimeOffset(2026, 1, 2, 10, 0, 0, TimeSpan.Zero)),
            targetMessage,
            CreateMessage(chat, bob, "alpha later file", new DateTimeOffset(2026, 1, 4, 10, 0, 0, TimeSpan.Zero)));
        await dbContext.SaveChangesAsync();

        var controller = ControllerTestFixture.CreateChatsController(dbContext, alice.Id);
        var response = await controller.SearchPage(
            q: "alpha",
            type: "media",
            page: 1,
            limit: 10,
            dateFrom: new DateTimeOffset(2026, 1, 3, 0, 0, 0, TimeSpan.Zero),
            dateTo: new DateTimeOffset(2026, 1, 3, 23, 59, 59, TimeSpan.Zero),
            authorId: bob.Id);

        var page = ControllerTestFixture.ValueOf(response);
        var result = Assert.Single(page.Results);
        Assert.False(page.HasMore);
        Assert.Equal(1, page.Page);
        Assert.Equal(targetMessage.Id, result.MessageId);
        Assert.Equal(chat.Id, result.ChatId);
        Assert.Equal(bob.Id, result.SenderUserId);
        Assert.Equal(targetMessage.SentAt, result.MessageSentAt);
        Assert.Equal(targetMessage.Id, result.Message?.Id);
    }

    [Fact]
    public async Task SearchMessagesPage_PaginatesMessageResults()
    {
        await using var database = new SqliteTestDatabase();
        await using var dbContext = await database.CreateContextAsync();
        var alice = CreateUser("alice");
        var bob = CreateUser("bob");
        var chat = CreateChat(alice, bob);
        dbContext.AddRange(
            alice,
            bob,
            chat,
            CreateMessage(chat, bob, "alpha one", new DateTimeOffset(2026, 1, 1, 10, 0, 0, TimeSpan.Zero)),
            CreateMessage(chat, bob, "alpha two", new DateTimeOffset(2026, 1, 2, 10, 0, 0, TimeSpan.Zero)),
            CreateMessage(chat, bob, "alpha three", new DateTimeOffset(2026, 1, 3, 10, 0, 0, TimeSpan.Zero)));
        await dbContext.SaveChangesAsync();

        var controller = ControllerTestFixture.CreateChatsController(dbContext, alice.Id);
        var firstPageResponse = await controller.SearchMessagesPage(chat.Id, q: "alpha", page: 1, limit: 2);
        var secondPageResponse = await controller.SearchMessagesPage(chat.Id, q: "alpha", page: 2, limit: 2);

        var firstPage = ControllerTestFixture.ValueOf(firstPageResponse);
        var secondPage = ControllerTestFixture.ValueOf(secondPageResponse);
        Assert.True(firstPage.HasMore);
        Assert.Equal(["alpha three", "alpha two"], firstPage.Messages.Select(x => x.Text).ToArray());
        Assert.False(secondPage.HasMore);
        Assert.Equal(["alpha one"], secondPage.Messages.Select(x => x.Text).ToArray());
    }

    [Fact]
    public async Task SearchPage_ReturnsPublicChannelForNonMember_ByAtUsername()
    {
        await using var database = new SqliteTestDatabase();
        await using var dbContext = await database.CreateContextAsync();
        var viewer = CreateUser("viewer");
        var owner = CreateUser("owner");
        var publicChannel = CreateChannel(owner, "Daily News", "daily_news", "Public news channel");
        var privateChannel = CreateChannel(owner, "Daily News Private", null, "Private news channel");
        dbContext.AddRange(viewer, owner, publicChannel, privateChannel);
        await dbContext.SaveChangesAsync();

        var controller = ControllerTestFixture.CreateChatsController(dbContext, viewer.Id);
        var response = await controller.SearchPage(q: "@daily_news", page: 1, limit: 10);

        var page = ControllerTestFixture.ValueOf(response);
        var result = Assert.Single(page.Results);
        Assert.Equal(publicChannel.Id, result.Chat.Id);
        Assert.Equal(ChatType.Channel, result.Chat.Type);
        Assert.True(result.Chat.CanSubscribe);
        Assert.False(result.Chat.IsSubscribed);
    }

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

    private static Chat CreateChat(User first, User second)
    {
        var chat = new Chat
        {
            Id = Guid.NewGuid(),
            Type = ChatType.Direct,
            CreatedById = first.Id,
            CreatedBy = first,
            CreatedAt = new DateTimeOffset(2026, 1, 1, 9, 0, 0, TimeSpan.Zero)
        };
        chat.Members.Add(new ChatMember { ChatId = chat.Id, Chat = chat, UserId = first.Id, User = first });
        chat.Members.Add(new ChatMember { ChatId = chat.Id, Chat = chat, UserId = second.Id, User = second });
        return chat;
    }

    private static Message CreateMessage(Chat chat, User sender, string text, DateTimeOffset sentAt)
    {
        var message = new Message
        {
            Id = Guid.NewGuid(),
            ChatId = chat.Id,
            Chat = chat,
            SenderId = sender.Id,
            Sender = sender,
            Text = text,
            SentAt = sentAt
        };
        chat.Messages.Add(message);
        return message;
    }

    private static Chat CreateChannel(User owner, string title, string? publicUsername, string description)
    {
        var channel = new Chat
        {
            Id = Guid.NewGuid(),
            Type = ChatType.Channel,
            Title = title,
            Description = description,
            PublicUsername = publicUsername,
            CreatedById = owner.Id,
            CreatedBy = owner,
            CreatedAt = new DateTimeOffset(2026, 1, 1, 9, 0, 0, TimeSpan.Zero),
            LastActivityAt = new DateTimeOffset(2026, 1, 1, 9, 0, 0, TimeSpan.Zero)
        };
        channel.Members.Add(new ChatMember
        {
            ChatId = channel.Id,
            Chat = channel,
            UserId = owner.Id,
            User = owner,
            IsOwner = true,
            Role = ChatMemberRole.Owner,
            CanPinMessages = true,
            CanDeleteMessages = true,
            CanInviteUsers = true,
            CanPostMessages = true
        });

        return channel;
    }
}
