using Ikar.Server.Data.Entities;
using Ikar.Shared;
using Microsoft.AspNetCore.Mvc;

namespace Ikar.Server.Tests;

public sealed class PrivacyAndStoriesControllerTests
{
    [Fact]
    public async Task UpdatePrivacySettings_NormalizesVisibilityAndDropsOwnUserId()
    {
        await using var database = new SqliteTestDatabase();
        await using var dbContext = await database.CreateContextAsync();
        var alice = CreateUser("alice");
        var bob = CreateUser("bob");
        dbContext.Users.AddRange(alice, bob);
        await dbContext.SaveChangesAsync();

        var controller = ControllerTestFixture.CreateUsersController(dbContext, alice.Id);
        var response = await controller.UpdateMyPrivacySettings(
            new UpdatePrivacySettingsRequest(
                PhoneNumberVisibility: "invalid",
                LastSeenVisibility: "nobody",
                ProfilePhotoVisibility: "everyone",
                BlockedUserIds: [alice.Id, bob.Id, bob.Id],
                AlwaysAllowUserIds: [alice.Id],
                NeverAllowUserIds: [bob.Id],
                PasscodeLockEnabled: true,
                ShowMessagePreview: false),
            CancellationToken.None);

        var settings = ControllerTestFixture.ValueOf(response);
        Assert.Equal("contacts", settings.PhoneNumberVisibility);
        Assert.Equal("nobody", settings.LastSeenVisibility);
        Assert.Equal([bob.Id], settings.BlockedUserIds);
        Assert.Empty(settings.AlwaysAllowUserIds);
        Assert.Equal([bob.Id], settings.NeverAllowUserIds);
        Assert.True(settings.PasscodeLockEnabled);
        Assert.False(settings.ShowMessagePreview);
    }

    [Fact]
    public async Task GetStories_ReturnsEveryoneStoriesWithoutSharedChat()
    {
        await using var database = new SqliteTestDatabase();
        await using var dbContext = await database.CreateContextAsync();
        var alice = CreateUser("alice");
        var bob = CreateUser("bob");
        var charlie = CreateUser("charlie");
        var sharedChat = CreateDirectChat(alice, bob);
        var peerStory = CreateStory(bob, "peer", StoryVisibility.Everyone);
        var publicStory = CreateStory(charlie, "public", StoryVisibility.Everyone);
        dbContext.AddRange(alice, bob, charlie, sharedChat, peerStory, publicStory);
        await dbContext.SaveChangesAsync();

        var controller = ControllerTestFixture.CreateStoriesController(dbContext, alice.Id);
        var response = await controller.GetStories(CancellationToken.None);

        var stories = ControllerTestFixture.ValueOf(response);
        Assert.Contains(stories, story => story.Id == peerStory.Id);
        Assert.Contains(stories, story => story.Id == publicStory.Id);
    }

    [Fact]
    public async Task GetStories_ContactsOnlyReturnsStoriesFromSharedChats()
    {
        await using var database = new SqliteTestDatabase();
        await using var dbContext = await database.CreateContextAsync();
        var alice = CreateUser("alice");
        var bob = CreateUser("bob");
        var charlie = CreateUser("charlie");
        var sharedChat = CreateDirectChat(alice, bob);
        var visibleStory = CreateStory(bob, "visible", StoryVisibility.Contacts);
        var hiddenStory = CreateStory(charlie, "hidden", StoryVisibility.Contacts);
        dbContext.AddRange(alice, bob, charlie, sharedChat, visibleStory, hiddenStory);
        await dbContext.SaveChangesAsync();

        var controller = ControllerTestFixture.CreateStoriesController(dbContext, alice.Id);
        var response = await controller.GetStories(CancellationToken.None);

        var stories = ControllerTestFixture.ValueOf(response);
        var story = Assert.Single(stories);
        Assert.Equal(visibleStory.Id, story.Id);
        Assert.Equal("visible", story.Text);
    }

    [Fact]
    public async Task GetStories_PrivateOnlyReturnsOwnStories()
    {
        await using var database = new SqliteTestDatabase();
        await using var dbContext = await database.CreateContextAsync();
        var alice = CreateUser("alice");
        var bob = CreateUser("bob");
        var sharedChat = CreateDirectChat(alice, bob);
        var ownStory = CreateStory(alice, "own", StoryVisibility.Private);
        var peerStory = CreateStory(bob, "peer", StoryVisibility.Private);
        dbContext.AddRange(alice, bob, sharedChat, ownStory, peerStory);
        await dbContext.SaveChangesAsync();

        var controller = ControllerTestFixture.CreateStoriesController(dbContext, alice.Id);
        var response = await controller.GetStories(CancellationToken.None);

        var stories = ControllerTestFixture.ValueOf(response);
        var story = Assert.Single(stories);
        Assert.Equal(ownStory.Id, story.Id);
        Assert.Equal("own", story.Text);
    }

    [Fact]
    public async Task GetAvatar_RespectsProfilePhotoContactsVisibility()
    {
        var avatarPath = Path.GetTempFileName();
        await File.WriteAllTextAsync(avatarPath, "avatar");
        try
        {
            await using var database = new SqliteTestDatabase();
            await using var dbContext = await database.CreateContextAsync();
            var alice = CreateUser("alice");
            var bob = CreateUser("bob");
            var charlie = CreateUser("charlie");
            alice.AvatarStoragePath = avatarPath;
            alice.AvatarContentType = "image/png";
            alice.PrivacySettings = new UserPrivacySettings
            {
                UserId = alice.Id,
                User = alice,
                ProfilePhotoVisibility = "contacts"
            };
            var sharedChat = CreateDirectChat(alice, bob);
            dbContext.AddRange(alice, bob, charlie, sharedChat);
            await dbContext.SaveChangesAsync();

            var bobController = ControllerTestFixture.CreateUsersController(dbContext, bob.Id);
            var bobResponse = await bobController.GetAvatar(alice.Id, Path.GetFileName(avatarPath), CancellationToken.None);
            Assert.IsType<PhysicalFileResult>(bobResponse);

            var charlieController = ControllerTestFixture.CreateUsersController(dbContext, charlie.Id);
            var charlieResponse = await charlieController.GetAvatar(alice.Id, Path.GetFileName(avatarPath), CancellationToken.None);
            Assert.IsType<NotFoundResult>(charlieResponse);
        }
        finally
        {
            File.Delete(avatarPath);
        }
    }

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

    private static Chat CreateDirectChat(User first, User second)
    {
        var chat = new Chat
        {
            Id = Guid.NewGuid(),
            Type = ChatType.Direct,
            CreatedById = first.Id,
            CreatedBy = first
        };
        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 Story CreateStory(User author, string text, StoryVisibility visibility) =>
        new()
        {
            Id = Guid.NewGuid(),
            AuthorUserId = author.Id,
            AuthorUser = author,
            Text = text,
            Visibility = visibility,
            CreatedAt = DateTimeOffset.UtcNow.AddMinutes(-5),
            ExpiresAt = DateTimeOffset.UtcNow.AddHours(1)
        };
}
