using Ikar.Server.Data;
using Ikar.Server.Data.Entities;
using Ikar.Server.Infrastructure;
using Ikar.Server.Infrastructure.Admin;
using Ikar.Server.Infrastructure.Bots;
using Ikar.Server.Infrastructure.Hubs;
using Ikar.Server.Infrastructure.Storage;
using Ikar.Shared;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.FileProviders;
using Microsoft.EntityFrameworkCore;

namespace Ikar.Server.Tests;

public sealed class AdminUserDeletionTests
{
    [Fact]
    public async Task DeleteUserAsync_UserOwnsBot_RemovesOwnerAndBotUser()
    {
        await using var database = new SqliteTestDatabase();
        await using var dbContext = await database.CreateContextAsync();
        using var tempRoot = new TempDirectory();
        var service = CreateService(dbContext, tempRoot.Path);

        var owner = CreateUser("owner");
        var botUser = CreateUser("helperbot", isBot: true);
        var bot = new Bot
        {
            User = botUser,
            OwnerUser = owner,
            TokenHash = "bot-token"
        };
        var chat = CreateDirectChat(owner, botUser);
        chat.Messages.Add(new Message
        {
            Chat = chat,
            Sender = botUser,
            Text = "bot history",
            SentAt = DateTimeOffset.UtcNow
        });
        dbContext.AddRange(owner, botUser, bot, chat);
        await dbContext.SaveChangesAsync();

        var deleted = await service.DeleteUserAsync(owner.Id, CancellationToken.None);

        Assert.True(deleted);
        Assert.False(await dbContext.Users.AnyAsync(x => x.Id == owner.Id));
        Assert.False(await dbContext.Users.AnyAsync(x => x.Id == botUser.Id));
        Assert.False(await dbContext.Bots.AnyAsync(x => x.Id == bot.Id));
        Assert.False(await dbContext.Chats.AnyAsync(x => x.Id == chat.Id));
    }

    [Fact]
    public async Task DeleteUserAsync_UserHasRestrictingRows_RemovesRowsWithoutThrowing()
    {
        await using var database = new SqliteTestDatabase();
        await using var dbContext = await database.CreateContextAsync();
        using var tempRoot = new TempDirectory();
        var service = CreateService(dbContext, tempRoot.Path);

        var user = CreateUser("deleted");
        var peer = CreateUser("peer");
        var chat = new Chat
        {
            Type = ChatType.Group,
            Title = "Group",
            CreatedBy = peer,
            LastActivityAt = DateTimeOffset.UtcNow
        };
        chat.Members.Add(new ChatMember { Chat = chat, User = user, Role = ChatMemberRole.Member });
        chat.Members.Add(new ChatMember { Chat = chat, User = peer, IsOwner = true, Role = ChatMemberRole.Owner });
        var message = new Message
        {
            Chat = chat,
            Sender = user,
            Text = "remove me",
            SentAt = DateTimeOffset.UtcNow
        };
        chat.Messages.Add(message);
        var inviteLink = new ChatInviteLink
        {
            Chat = chat,
            CreatedBy = user,
            Token = "invite-token"
        };
        var topic = new ChatTopic
        {
            Chat = chat,
            CreatedBy = user,
            Title = "Topic",
            Icon = "chat"
        };
        var moderationEntry = new ModerationLogEntry
        {
            Chat = chat,
            ActorUser = user,
            TargetType = ModerationLogTargetType.Message,
            TargetId = message.Id,
            Action = "message.deleted"
        };
        dbContext.AddRange(user, peer, chat, inviteLink, topic, moderationEntry);
        await dbContext.SaveChangesAsync();

        var deleted = await service.DeleteUserAsync(user.Id, CancellationToken.None);

        Assert.True(deleted);
        Assert.False(await dbContext.Users.AnyAsync(x => x.Id == user.Id));
        Assert.False(await dbContext.Messages.AnyAsync(x => x.Id == message.Id));
        Assert.False(await dbContext.ChatInviteLinks.AnyAsync(x => x.Id == inviteLink.Id));
        Assert.False(await dbContext.ChatTopics.AnyAsync(x => x.Id == topic.Id));
        Assert.False(await dbContext.ModerationLogEntries.AnyAsync(x => x.Id == moderationEntry.Id));
        Assert.True(await dbContext.Users.AnyAsync(x => x.Id == peer.Id));
        Assert.True(await dbContext.Chats.AnyAsync(x => x.Id == chat.Id));
    }

    private static AdminService CreateService(IkarDbContext dbContext, string contentRootPath)
    {
        var attachmentStorageService = new AttachmentStorageService(new TestWebHostEnvironment(contentRootPath));
        return new AdminService(
            dbContext,
            new TestWebHostEnvironment(contentRootPath),
            new ServerRuntimeState(DateTimeOffset.UtcNow),
            attachmentStorageService,
            new BotDeletionService(dbContext, attachmentStorageService),
            new NoopHubContext<MessengerHub>());
    }

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

    private static Chat CreateDirectChat(User first, User second)
    {
        var chat = new Chat
        {
            Type = ChatType.Direct,
            CreatedBy = first,
            LastActivityAt = DateTimeOffset.UtcNow
        };
        chat.Members.Add(new ChatMember { Chat = chat, User = first, IsOwner = true, Role = ChatMemberRole.Owner });
        chat.Members.Add(new ChatMember { Chat = chat, User = second });
        return chat;
    }

    private sealed class TempDirectory : IDisposable
    {
        public TempDirectory()
        {
            Path = System.IO.Path.Combine(System.IO.Path.GetTempPath(), $"ikar-admin-delete-{Guid.NewGuid():N}");
            Directory.CreateDirectory(Path);
        }

        public string Path { get; }

        public void Dispose()
        {
            if (Directory.Exists(Path))
            {
                Directory.Delete(Path, recursive: true);
            }
        }
    }

    private sealed class TestWebHostEnvironment(string contentRootPath) : IWebHostEnvironment
    {
        public string ApplicationName { get; set; } = "Ikar.Server.Tests";
        public IFileProvider ContentRootFileProvider { get; set; } = new PhysicalFileProvider(contentRootPath);
        public string ContentRootPath { get; set; } = contentRootPath;
        public string EnvironmentName { get; set; } = "Test";
        public string WebRootPath { get; set; } = contentRootPath;
        public IFileProvider WebRootFileProvider { get; set; } = new PhysicalFileProvider(contentRootPath);
    }
}
