// 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 Catalogger.Backend.Cache;
using Catalogger.Backend.Cache.InMemoryCache;
using Catalogger.Backend.Database.Repositories;
using Catalogger.Backend.Extensions;
using Catalogger.Backend.Services;
using Remora.Discord.API.Abstractions.Gateway.Events;
using Remora.Discord.Extensions.Embeds;
using Remora.Discord.Gateway.Responders;
using Remora.Results;

namespace Catalogger.Backend.Bot.Responders.Members;

public class GuildMemberRemoveResponder(
    ILogger logger,
    GuildRepository guildRepository,
    IMemberCache memberCache,
    RoleCache roleCache,
    UserCache userCache,
    AuditLogCache auditLogCache,
    WebhookExecutorService webhookExecutor
) : IResponder<IGuildMemberRemove>
{
    private readonly ILogger _logger = logger.ForContext<GuildMemberAddResponder>();

    public async Task<Result> RespondAsync(IGuildMemberRemove evt, CancellationToken ct = default)
    {
        try
        {
            var embed = new EmbedBuilder()
                .WithTitle("Member left")
                .WithAuthor(evt.User.Tag(), iconUrl: evt.User.AvatarUrl())
                .WithColour(DiscordUtils.Orange)
                .WithDescription($"<@{evt.User.ID}>")
                .WithFooter($"ID: {evt.User.ID}")
                .WithCurrentTimestamp();

            var guildConfig = await guildRepository.GetAsync(evt.GuildID);

            var member = await memberCache.TryGetAsync(evt.GuildID, evt.User.ID);
            if (member == null)
            {
                _logger.Information(
                    "Guild member {UserId} in {GuildId} left but wasn't in the cache, sending limited embed",
                    evt.User.ID,
                    evt.GuildID
                );
                webhookExecutor.QueueLog(
                    guildConfig,
                    LogChannelType.GuildMemberRemove,
                    embed.Build().GetOrThrow()
                );
                return Result.Success;
            }

            embed.Description +=
                $"\njoined <t:{member.JoinedAt.ToUnixTimeSeconds()}>\n({member.JoinedAt.Prettify()} ago)";

            // get the member's roles, sort them, and turn them into mentions
            var guildRoles = roleCache.GuildRoles(evt.GuildID);
            var roles = guildRoles.Sorted(member.Roles).ToList();

            var roleMentions = "";
            foreach (var (idx, role) in roles.Select((r, i) => (i, r)))
            {
                if (roleMentions.Length > 900)
                {
                    roleMentions += $"\n(too many roles to list, showing {idx}/{roles.Count})";
                    break;
                }

                roleMentions += $"<@&{role.ID}>";
                if (idx != roles.Count - 1)
                    roleMentions += ", ";
            }

            embed.AddField("Roles", roleMentions, inline: false);

            webhookExecutor.QueueLog(
                guildConfig,
                LogChannelType.GuildMemberRemove,
                embed.Build().GetOrThrow()
            );

            // Check for a kick audit log event. We don't get a separate "kick" event so we have to check this manually
            await Task.Delay(2000, ct);
            if (!auditLogCache.TryGetKick(evt.GuildID, evt.User.ID, out var actionData))
            {
                return Result.Success;
            }

            var kick = new EmbedBuilder()
                .WithTitle("User kicked")
                .WithAuthor(evt.User.Tag(), iconUrl: evt.User.AvatarUrl())
                .WithColour(DiscordUtils.Red)
                .WithCurrentTimestamp()
                .WithDescription($"<@{evt.User.ID}>");

            kick.AddField(
                "Responsible moderator",
                await userCache.TryFormatUserAsync(actionData.ModeratorId)
            );
            kick.AddField("Reason", actionData.Reason ?? "No reason given");

            webhookExecutor.QueueLog(
                guildConfig,
                LogChannelType.GuildMemberKick,
                kick.Build().GetOrThrow()
            );

            return Result.Success;
        }
        finally
        {
            await memberCache.RemoveAsync(evt.GuildID, evt.User.ID);
            await memberCache.TryRemoveMemberNameAsync(evt.GuildID, evt.User.Username);
        }
    }
}
