// Copyright (C) 2021-present sam (starshines.gay)
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

using System.Text;
using Catalogger.Backend.Cache.InMemoryCache;
using Catalogger.Backend.Database.Repositories;
using Catalogger.Backend.Extensions;
using Catalogger.Backend.Services;
using NodaTime.Extensions;
using Remora.Discord.API.Abstractions.Gateway.Events;
using Remora.Discord.API.Abstractions.Objects;
using Remora.Discord.API.Abstractions.Rest;
using Remora.Discord.Extensions.Embeds;
using Remora.Discord.Gateway.Responders;
using Remora.Rest.Core;
using Remora.Results;

namespace Catalogger.Backend.Bot.Responders.Messages;

public class MessageDeleteBulkResponder(
    ILogger logger,
    GuildRepository guildRepository,
    MessageRepository messageRepository,
    WebhookExecutorService webhookExecutor,
    ChannelCache channelCache
) : IResponder<IMessageDeleteBulk>
{
    private readonly ILogger _logger = logger.ForContext<MessageDeleteBulkResponder>();

    public async Task<Result> RespondAsync(IMessageDeleteBulk evt, CancellationToken ct = default)
    {
        var guild = await guildRepository.GetAsync(evt.GuildID);
        if (guild.IsMessageIgnored(evt.ChannelID, null, null))
            return Result.Success;

        var logChannel = webhookExecutor.GetLogChannel(
            guild,
            LogChannelType.MessageDeleteBulk,
            evt.ChannelID
        );
        if (logChannel == null)
        {
            return Result.Success;
        }

        IChannel? rootChannel = null;
        channelCache.TryGet(evt.ChannelID, out var channel);
        if (
            channel is
            {
                Type: ChannelType.AnnouncementThread
                    or ChannelType.PrivateThread
                    or ChannelType.PublicThread
            }
        )
        {
            if (channel.ParentID.TryGet(out var parentId) && parentId != null)
                channelCache.TryGet(parentId.Value, out rootChannel);
        }

        List<string> renderedMessages = [];
        var notFoundMessages = 0;
        var ignoredMessages = 0;

        foreach (var msgId in evt.IDs.Order())
        {
            if (await messageRepository.IsMessageIgnoredAsync(msgId.Value))
            {
                ignoredMessages++;
                continue;
            }

            var msg = await messageRepository.GetMessageAsync(msgId.Value, ct);
            renderedMessages.Add(RenderMessage(msgId, msg));

            if (msg == null)
                notFoundMessages++;
        }

        var output = "Bulk message delete in";
        if (channel != null)
        {
            output += $" #{channel.Name} ({channel.ID})";
            if (rootChannel != null)
                output += $" (thread in #{rootChannel.Name} ({rootChannel.ID})";
        }
        else
        {
            output += $" unknown channel {evt.ChannelID}";
        }

        output += $"\nwith {renderedMessages.Count} messages\n\n";
        output += string.Join("\n", renderedMessages);

        var embed = new EmbedBuilder()
            .WithTitle("Bulk message delete")
            .WithDescription(
                $"""
                {evt.IDs.Count} messages were deleted in <#{evt.ChannelID}>
                ({notFoundMessages} messages not found, {ignoredMessages} messages ignored)
                """
            )
            .WithColour(DiscordUtils.Red)
            .WithCurrentTimestamp();

        await webhookExecutor.SendLogAsync(
            logChannel.Value,
            [embed.Build().GetOrThrow()],
            [
                new FileData(
                    $"bulk-delete-{evt.ChannelID}.txt",
                    new MemoryStream(Encoding.UTF8.GetBytes(output))
                ),
            ]
        );
        return Result.Success;
    }

    private string RenderMessage(Snowflake messageId, MessageRepository.Message? message)
    {
        var timestamp = messageId.Timestamp.ToOffsetDateTime().ToString();

        if (message == null)
        {
            return $"""
                [{timestamp}] Unknown message {messageId}
                --------------------------------------------
                """;
        }

        var builder = new StringBuilder();
        builder.Append($"[{timestamp}] {message.Username} ({message.UserId})\n");
        if (message is { System: not null, Member: not null })
        {
            builder.Append($"PK system: {message.System} | PK member: {message.Member}\n");
        }

        builder.Append("--------------------------------------------\n");
        builder.Append(message.Content);
        builder.Append("\n--------------------------------------------\n");

        return builder.ToString();
    }
}
