using Ikar.Server.Data;
using Ikar.Server.Data.Entities;
using Ikar.Server.Infrastructure.Storage;
using Ikar.Shared;
using Microsoft.EntityFrameworkCore;

namespace Ikar.Server.Infrastructure.Bots;

public sealed class BotDeletionService(
    IkarDbContext dbContext,
    AttachmentStorageService attachmentStorageService)
{
    public async Task<BotDeletionResult?> DeleteBotAsync(
        Guid botId,
        Guid? ownerUserId,
        CancellationToken cancellationToken)
    {
        var bot = await dbContext.Bots
            .Include(x => x.User)
                .ThenInclude(x => x.Sessions)
            .Include(x => x.User)
                .ThenInclude(x => x.PushDevices)
            .SingleOrDefaultAsync(
                x => x.Id == botId && (!ownerUserId.HasValue || x.OwnerUserId == ownerUserId.Value),
                cancellationToken);
        if (bot is null)
        {
            return null;
        }

        var result = new BotDeletionResult(bot.Id, bot.UserId, bot.User.Username);
        var botUserId = bot.UserId;
        var attachmentPaths = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

        var ownedBotIds = await dbContext.Bots
            .AsNoTracking()
            .Where(x => x.OwnerUserId == botUserId && x.Id != bot.Id)
            .Select(x => x.Id)
            .ToListAsync(cancellationToken);
        foreach (var ownedBotId in ownedBotIds)
        {
            await DeleteBotAsync(ownedBotId, ownerUserId: null, cancellationToken);
        }

        var directChats = await dbContext.Chats
            .Include(x => x.Members)
            .Include(x => x.Messages)
                .ThenInclude(x => x.Attachments)
            .Where(x => x.Type == ChatType.Direct && x.Members.Any(member => member.UserId == botUserId))
            .ToListAsync(cancellationToken);
        var directChatIds = directChats.Select(x => x.Id).ToHashSet();
        foreach (var chat in directChats)
        {
            CollectAttachmentPaths(chat.Messages.SelectMany(x => x.Attachments), attachmentPaths);
            dbContext.Chats.Remove(chat);
        }

        var botMessages = await dbContext.Messages
            .Include(x => x.Attachments)
            .Where(x => x.SenderId == botUserId && !directChatIds.Contains(x.ChatId))
            .ToListAsync(cancellationToken);
        CollectAttachmentPaths(botMessages.SelectMany(x => x.Attachments), attachmentPaths);
        dbContext.Messages.RemoveRange(botMessages);

        var memberships = await dbContext.ChatMembers
            .Where(x => x.UserId == botUserId && !directChatIds.Contains(x.ChatId))
            .ToListAsync(cancellationToken);
        dbContext.ChatMembers.RemoveRange(memberships);

        var storyAttachmentPaths = await dbContext.StoryAttachments
            .Where(x => x.Story.AuthorUserId == botUserId)
            .Select(x => x.StoragePath)
            .ToListAsync(cancellationToken);
        foreach (var path in storyAttachmentPaths)
        {
            if (!string.IsNullOrWhiteSpace(path))
            {
                attachmentPaths.Add(path);
            }
        }

        var moderationEntries = await dbContext.ModerationLogEntries
            .Where(x => x.ActorUserId == botUserId)
            .ToListAsync(cancellationToken);
        dbContext.ModerationLogEntries.RemoveRange(moderationEntries);

        var inviteLinks = await dbContext.ChatInviteLinks
            .Where(x => x.CreatedById == botUserId)
            .ToListAsync(cancellationToken);
        dbContext.ChatInviteLinks.RemoveRange(inviteLinks);

        var topics = await dbContext.ChatTopics
            .Where(x => x.CreatedById == botUserId)
            .ToListAsync(cancellationToken);
        dbContext.ChatTopics.RemoveRange(topics);

        var botCommands = await dbContext.BotCommands
            .Where(x => x.BotId == bot.Id)
            .ToListAsync(cancellationToken);
        var botUpdates = await dbContext.BotUpdates
            .Where(x => x.BotId == bot.Id)
            .ToListAsync(cancellationToken);
        dbContext.BotUpdates.RemoveRange(botUpdates);
        dbContext.BotCommands.RemoveRange(botCommands);
        dbContext.UserSessions.RemoveRange(bot.User.Sessions);
        dbContext.PushDevices.RemoveRange(bot.User.PushDevices);
        dbContext.Bots.Remove(bot);
        dbContext.Users.Remove(bot.User);

        await dbContext.SaveChangesAsync(cancellationToken);
        foreach (var path in attachmentPaths)
        {
            attachmentStorageService.DeleteIfExists(path);
        }

        attachmentStorageService.DeleteIfExists(bot.User.AvatarStoragePath);
        return result;
    }

    private static void CollectAttachmentPaths(IEnumerable<MessageAttachment> attachments, ISet<string> target)
    {
        foreach (var path in attachments
                     .Select(x => x.StoragePath)
                     .Where(x => !string.IsNullOrWhiteSpace(x)))
        {
            target.Add(path);
        }
    }
}

public sealed record BotDeletionResult(Guid BotId, Guid BotUserId, string Username);
