// Project Makoto
// Copyright (C) 2024  Fortunevale
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU 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

namespace ProjectMakoto.Commands;

internal sealed class EmojiStealerCommand : BaseCommand
{
    public override Task ExecuteCommand(SharedCommandContext ctx, Dictionary<string, object> arguments)
    {
        return Task.Run(async () =>
        {
            DiscordMessage bMessage;

            if (arguments?.ContainsKey("message") ?? false)
            {
                bMessage = (DiscordMessage)arguments["message"];
            }
            else
            {
                switch (ctx.CommandType)
                {
                    case Enums.CommandType.PrefixCommand:
                    {
                        if (ctx.OriginalCommandContext.Message.ReferencedMessage is not null)
                        {
                            bMessage = ctx.OriginalCommandContext.Message.ReferencedMessage;
                        }
                        else
                        {
                            this.SendSyntaxError();
                            return;
                        }

                        break;
                    }
                    default:
                        throw new ArgumentException("Message expected");
                }
            }

            if (await ctx.DbUser.Cooldown.WaitForModerate(ctx))
                return;

            HttpClient client = new();

            var embed = new DiscordEmbedBuilder
            {
                Description = this.GetString(this.t.Commands.Utility.EmojiStealer.DownloadingPre, true),
            }.AsLoading(ctx);
            _ = await this.RespondOrEdit(embed);

            Dictionary<ulong, EmojiEntry> SanitizedEmoteList = new();
            MemoryStream zipFileStream = new();
            var FinishedInteraction = false;

            var Emotes = bMessage.Content.GetEmotes();

            foreach (var b in Emotes)
                SanitizedEmoteList.Add(b.Item1, new EmojiEntry
                {
                    Name = b.Item2,
                    Animated = b.Item3,
                    EntryType = EmojiType.EMOJI
                });

            if (Emotes.Count == 0 && (bMessage.Stickers is null || bMessage.Stickers.Count == 0))
            {
                embed.Description = this.GetString(this.t.Commands.Utility.EmojiStealer.NoEmojis, true);
                _ = await this.RespondOrEdit(embed.AsError(ctx));
                return;
            }

            var guid = Guid.NewGuid().ToString().MakeValidFileName();

            try
            {
                if (SanitizedEmoteList.Count > 0)
                {
                    embed.Description = this.GetString(this.t.Commands.Utility.EmojiStealer.DownloadingEmojis, true, new TVar("Count", SanitizedEmoteList.Count));
                    _ = await this.RespondOrEdit(embed);

                    foreach (var b in SanitizedEmoteList.ToList())
                    {
                        try
                        {
                            var EmoteStream = await client.GetStreamAsync($"https://cdn.discordapp.com/emojis/{b.Key}.{(b.Value.Animated ? "gif" : "png")}");

                            var NameExists = "";
                            var NameExistsInt = 1;

                            var Name = $"{b.Value.Name}{NameExists}.{(b.Value.Animated ? "gif" : "png")}".MakeValidFileName('_');

                            while (SanitizedEmoteList.Any(x => x.Value.Data.Name == Name))
                            {
                                NameExistsInt++;
                                NameExists = $" ({NameExistsInt})";

                                Name = $"{b.Value.Name}{NameExists}.{(b.Value.Animated ? "gif" : "png")}".MakeValidFileName('_');
                            }

                            b.Value.Data.Name = Name;
                            EmoteStream.CopyTo(b.Value.Data.Stream);
                            b.Value.Data.Stream.Position = 0;
                        }
                        catch (Exception ex)
                        {
                            Log.Error(ex, "Failed to download an emote");

                            _ = SanitizedEmoteList.Remove(b.Key);
                        }
                    }
                }

                if (bMessage.Stickers.Count > 0)
                {
                    embed.Description = this.GetString(this.t.Commands.Utility.EmojiStealer.DownloadingStickers, true, new TVar("Count", bMessage.Stickers.GroupBy(x => x.Url).Select(x => x.First()).Count()));
                    _ = await this.RespondOrEdit(embed);

                    foreach (var b in bMessage.Stickers.GroupBy(x => x.Url).Select(x => x.First()))
                    {
                        var newEntry = new EmojiEntry
                        {
                            Animated = false,
                            Name = b.Name,
                            Description = b.Description,
                            Emoji = "🤖".UnicodeToEmoji(),
                            StickerFormat = b.FormatType,
                            EntryType = EmojiType.STICKER
                        };

                        try
                        {
                            var StickerStream = await client.GetStreamAsync(b.Url);

                            var NameExists = "";
                            var NameExistsInt = 1;

                            var Name = $"{b.Name}{NameExists}.png".MakeValidFileName('_');

                            while (SanitizedEmoteList.Any(x => x.Value.Data.Name == Name))
                            {
                                NameExistsInt++;
                                NameExists = $" ({NameExistsInt})";

                                Name = $"{newEntry.Name}{NameExists}.png".MakeValidFileName('_');
                            }

                            newEntry.Data.Name = Name;
                            StickerStream.CopyTo(newEntry.Data.Stream);
                            newEntry.Data.Stream.Position = 0;
                        }
                        catch (Exception ex)
                        {
                            Log.Error(ex, "Failed to download an emote");

                            _ = SanitizedEmoteList.Remove(b.Id);
                        }

                        SanitizedEmoteList.Add(b.Id, newEntry);
                    }
                }

                if (SanitizedEmoteList.Count == 0)
                {
                    embed.Description = this.GetString(this.t.Commands.Utility.EmojiStealer.NoSuccessfulDownload, true);
                    _ = await this.RespondOrEdit(embed.AsError(ctx));

                    return;
                }

                var emojiText = "";

                if (SanitizedEmoteList.Any(x => x.Value.EntryType == EmojiType.EMOJI))
                    emojiText += this.GetString(this.t.Commands.Utility.EmojiStealer.Emoji);

                if (SanitizedEmoteList.Any(x => x.Value.EntryType == EmojiType.STICKER))
                    emojiText += $"{(emojiText.Length > 0 ? $" & {this.GetString(this.t.Commands.Utility.EmojiStealer.Sticker)}" : this.GetString(this.t.Commands.Utility.EmojiStealer.Sticker))}";

                embed.Description = this.GetString(this.t.Commands.Utility.EmojiStealer.ReceivePrompt, true, new TVar("Type", emojiText));
                _ = embed.AsAwaitingInput(ctx);

                var IncludeStickers = false;

                if (!SanitizedEmoteList.Any(x => x.Value.EntryType == EmojiType.EMOJI))
                    IncludeStickers = true;

                var IncludeStickersButton = new DiscordButtonComponent((IncludeStickers ? ButtonStyle.Success : ButtonStyle.Danger), "ToggleStickers", this.GetString(this.t.Commands.Utility.EmojiStealer.ToggleStickers), !SanitizedEmoteList.Any(x => x.Value.EntryType == EmojiType.EMOJI), new DiscordComponentEmoji(DiscordEmoji.FromGuildEmote(ctx.Client, (ulong)(IncludeStickers ? 970278964755038248 : 970278964079767574))));

                var AddToServerButton = new DiscordButtonComponent(ButtonStyle.Success, "AddToServer", this.GetString(this.t.Commands.Utility.EmojiStealer.AddEmojisToServer), !ctx.Member.Permissions.HasPermission(Permissions.ManageGuildExpressions), new DiscordComponentEmoji(DiscordEmoji.FromUnicode("➕")));
                var ZipPrivateMessageButton = new DiscordButtonComponent(ButtonStyle.Primary, "ZipPrivateMessage", this.GetString(this.t.Commands.Utility.EmojiStealer.DirectMessageZip), false, new DiscordComponentEmoji(DiscordEmoji.FromUnicode("🖥")));
                var SinglePrivateMessageButton = new DiscordButtonComponent(ButtonStyle.Primary, "SinglePrivateMessage", this.GetString(this.t.Commands.Utility.EmojiStealer.DirectMessageSingle), false, new DiscordComponentEmoji(DiscordEmoji.FromUnicode("📱")));

                var SendHereButton = new DiscordButtonComponent(ButtonStyle.Secondary, "SendHere", this.GetString(this.t.Commands.Utility.EmojiStealer.CurrentChatZip), !(ctx.Member.Permissions.HasPermission(Permissions.AttachFiles)), new DiscordComponentEmoji(DiscordEmoji.FromUnicode("💬")));

                var builder = new DiscordMessageBuilder().AddEmbed(embed);

                if (SanitizedEmoteList.Any(x => x.Value.EntryType == EmojiType.STICKER))
                    _ = builder.AddComponents(IncludeStickersButton);

                _ = builder.AddComponents(new List<DiscordComponent> { AddToServerButton, ZipPrivateMessageButton, SinglePrivateMessageButton, SendHereButton });

                _ = await this.RespondOrEdit(builder);

                CancellationTokenSource cancellationTokenSource = new();

                ctx.Client.ComponentInteractionCreated += RunInteraction;

                _ = Task.Delay(60000, cancellationTokenSource.Token).ContinueWith(x =>
                {
                    if (x.IsCompletedSuccessfully)
                    {
                        ctx.Client.ComponentInteractionCreated -= RunInteraction;
                        FinishedInteraction = true;

                        this.ModifyToTimedOut();
                    }
                });

                async Task RunInteraction(DiscordClient s, ComponentInteractionCreateEventArgs e)
                {
                    _ = Task.Run(async () =>
                    {
                        try
                        {
                            if (e.Message?.Id == ctx.ResponseMessage.Id && e.User.Id == ctx.User.Id)
                            {
                                cancellationTokenSource.Cancel();
                                cancellationTokenSource = new();

                                _ = Task.Delay(60000, cancellationTokenSource.Token).ContinueWith(x =>
                                {
                                    if (x.IsCompletedSuccessfully)
                                    {
                                        ctx.Client.ComponentInteractionCreated -= RunInteraction;

                                        this.ModifyToTimedOut();
                                    }
                                });

                                _ = e.Interaction.CreateResponseAsync(InteractionResponseType.DeferredMessageUpdate);

                                if (e.GetCustomId() == AddToServerButton.CustomId)
                                {
                                    ctx.Client.ComponentInteractionCreated -= RunInteraction;
                                    cancellationTokenSource.Cancel();

                                    if (!ctx.Member.Permissions.HasPermission(Permissions.ManageGuildExpressions))
                                    {
                                        this.SendPermissionError(Permissions.ManageGuildExpressions);
                                        return;
                                    }

                                    if (!ctx.CurrentMember.Permissions.HasPermission(Permissions.ManageGuildExpressions))
                                    {
                                        this.SendOwnPermissionError(Permissions.ManageGuildExpressions);
                                        return;
                                    }

                                    var DiscordWarning = false;

                                    embed.Description = this.GetString(this.t.Commands.Utility.EmojiStealer.AddEmojisToServerLoading, true,
                                        new TVar("Min", 0),
                                        new TVar("Max", (IncludeStickers ? SanitizedEmoteList.Count : SanitizedEmoteList.Where(x => x.Value.EntryType == EmojiType.EMOJI).Count())));
                                    _ = embed.AsLoading(ctx);
                                    _ = await this.RespondOrEdit(new DiscordMessageBuilder().AddEmbed(embed));

                                    for (var i = 0; i < SanitizedEmoteList.Count; i++)
                                    {
                                        try
                                        {
                                            Task task;

                                            switch (SanitizedEmoteList.ElementAt(i).Value.EntryType)
                                            {
                                                case EmojiType.STICKER:
                                                {
                                                    var sticker = SanitizedEmoteList.ElementAt(i).Value;

                                                    task = ctx.Guild.CreateStickerAsync(sticker.Name, sticker.Description ?? sticker.Name, sticker.Emoji, sticker.Data.Stream, sticker.StickerFormat);

                                                    var WaitSeconds = 0;

                                                    while (task.Status == TaskStatus.WaitingForActivation)
                                                    {
                                                        WaitSeconds++;

                                                        if (WaitSeconds > 10 && !DiscordWarning)
                                                        {
                                                            embed.Description = this.GetString(this.t.Commands.Utility.EmojiStealer.AddStickersToServerLoading, true,
                                                                new TVar("Min", 0),
                                                                new TVar("Max", (IncludeStickers ? SanitizedEmoteList.Count : SanitizedEmoteList.Where(x => x.Value.EntryType == EmojiType.EMOJI).Count()))) +
                                                                                $"\n{this.GetString(this.t.Commands.Utility.EmojiStealer.AddToServerLoadingNotice)}";
                                                            _ = await this.RespondOrEdit(embed);

                                                            DiscordWarning = true;
                                                        }
                                                        await Task.Delay(1000);
                                                    }

                                                    if (task.IsFaulted)
                                                        throw task.Exception.InnerException;
                                                    break;
                                                }
                                                case EmojiType.EMOJI:
                                                {
                                                    var emoji = SanitizedEmoteList.ElementAt(i);

                                                    task = ctx.Guild.CreateEmojiAsync(SanitizedEmoteList.ElementAt(i).Value.Name, emoji.Value.Data.Stream);

                                                    var WaitSeconds = 0;

                                                    while (task.Status == TaskStatus.WaitingForActivation)
                                                    {
                                                        WaitSeconds++;

                                                        if (WaitSeconds > 10 && !DiscordWarning)
                                                        {
                                                            embed.Description = this.GetString(this.t.Commands.Utility.EmojiStealer.AddEmojisToServerLoading, true,
                                                                new TVar("Min", 0),
                                                                new TVar("Max", (IncludeStickers ? SanitizedEmoteList.Count : SanitizedEmoteList.Where(x => x.Value.EntryType == EmojiType.EMOJI).Count()))) +
                                                                                $"\n{this.GetString(this.t.Commands.Utility.EmojiStealer.AddToServerLoadingNotice)}";
                                                            _ = await this.RespondOrEdit(embed);

                                                            DiscordWarning = true;
                                                        }
                                                        await Task.Delay(1000);
                                                    }

                                                    if (task.IsFaulted)
                                                        throw task.Exception.InnerException;
                                                    break;
                                                }
                                                default:
                                                    throw new NotImplementedException();
                                            }
                                        }
                                        catch (DisCatSharp.Exceptions.BadRequestException ex)
                                        {
                                            var regex = Regex.Match(ex.WebResponse.Response.Replace("\\", ""), "((\"code\": )(\\d*))");

                                            if (regex.Groups[3].Value == "30008")
                                            {
                                                embed.Description = this.GetString(this.t.Commands.Utility.EmojiStealer.NoMoreRoom, true, new TVar("Count", i));
                                                _ = embed.AsError(ctx);
                                                _ = await this.RespondOrEdit(embed);
                                                return;
                                            }
                                            else
                                                throw;
                                        }
                                    }

                                    embed.Description = this.GetString(this.t.Commands.Utility.EmojiStealer.SuccessAdded, true,
                                        new TVar("Count", (IncludeStickers ? SanitizedEmoteList.Count : SanitizedEmoteList.Where(x => x.Value.EntryType == EmojiType.EMOJI).Count())));
                                    _ = embed.AsSuccess(ctx);
                                    _ = await this.RespondOrEdit(embed);
                                    return;
                                }
                                else if (e.GetCustomId() == SinglePrivateMessageButton.CustomId)
                                {
                                    ctx.Client.ComponentInteractionCreated -= RunInteraction;
                                    cancellationTokenSource.Cancel();

                                    embed.Description = this.GetString(this.t.Commands.Utility.EmojiStealer.SendingDm, true, new TVar("Type", emojiText));
                                    _ = embed.AsLoading(ctx);
                                    _ = await this.RespondOrEdit(new DiscordMessageBuilder().AddEmbed(embed));

                                    try
                                    {
                                        var totalCount = IncludeStickers ? SanitizedEmoteList.Count : SanitizedEmoteList.Where(x => x.Value.EntryType == EmojiType.EMOJI).Count();

                                        for (var i = 0; i < SanitizedEmoteList.Count; i++)
                                        {
                                            if (!IncludeStickers)
                                                if (SanitizedEmoteList.ElementAt(i).Value.EntryType != EmojiType.EMOJI)
                                                    continue;

                                            var current = SanitizedEmoteList.ElementAt(i);
                                            _ = current.Value.Data.Stream.Seek(0, SeekOrigin.Begin);

                                            var currentFilename = $"{current.Value.Name}.{(current.Value.Animated == true ? "gif" : "png")}";

                                            _ = await ctx.User.SendMessageAsync(new DiscordMessageBuilder()
                                                .WithContent($"`{i + 1}/{totalCount}` `{currentFilename}`")
                                                .WithFile($"{currentFilename}", current.Value.Data.Stream));
                                            await Task.Delay(1000);
                                        }

                                        _ = await ctx.User.SendMessageAsync(new DiscordMessageBuilder().WithContent(this.GetString(this.t.Commands.Utility.EmojiStealer.SuccessDm, new TVar("Type", emojiText))));
                                    }
                                    catch (DisCatSharp.Exceptions.UnauthorizedException)
                                    {
                                        this.SendDmError();
                                        return;
                                    }
                                    catch (Exception)
                                    {
                                        throw;
                                    }

                                    embed.Description = this.GetString(this.t.Commands.Utility.EmojiStealer.SuccessDmMain, true,
                                        new TVar("Count", (IncludeStickers ? SanitizedEmoteList.Count : SanitizedEmoteList.Where(x => x.Value.EntryType == EmojiType.EMOJI).Count())),
                                        new TVar("Type", emojiText));
                                    _ = await this.RespondOrEdit(embed.AsSuccess(ctx));
                                    return;
                                }
                                else if (e.GetCustomId() == ZipPrivateMessageButton.CustomId || e.GetCustomId() == SendHereButton.CustomId)
                                {
                                    ctx.Client.ComponentInteractionCreated -= RunInteraction;
                                    cancellationTokenSource.Cancel();

                                    embed.Description = this.GetString(this.t.Commands.Utility.EmojiStealer.PreparingZip, true);
                                    _ = await this.RespondOrEdit(new DiscordMessageBuilder().AddEmbed(embed.AsLoading(ctx)));

                                    using (var archive = new ZipArchive(zipFileStream, ZipArchiveMode.Create, true))
                                    {
                                        for (var i = 0; i < SanitizedEmoteList.Count; i++)
                                        {
                                            if (!IncludeStickers)
                                                if (SanitizedEmoteList.ElementAt(i).Value.EntryType != EmojiType.EMOJI)
                                                    continue;

                                            var current = SanitizedEmoteList.ElementAt(i);
                                            var newEntry = archive.CreateEntry(current.Value.Data.Name);
                                            using (var entryStream = newEntry.Open())
                                                await current.Value.Data.Stream.CopyToAsync(entryStream);
                                        }
                                    }

                                    _ = zipFileStream.Seek(0, SeekOrigin.Begin);

                                    if (e.GetCustomId() == ZipPrivateMessageButton.CustomId)
                                    {
                                        embed.Description = this.GetString(this.t.Commands.Utility.EmojiStealer.SendingZipDm, true);
                                        _ = await this.RespondOrEdit(embed);

                                        try
                                        {
                                            _ = zipFileStream.Seek(0, SeekOrigin.Begin);
                                            _ = await ctx.User.SendMessageAsync(new DiscordMessageBuilder().WithFile($"Emojis.zip", zipFileStream).WithContent(this.GetString(this.t.Commands.Utility.EmojiStealer.SuccessDm, new TVar("Type", emojiText))));
                                        }
                                        catch (DisCatSharp.Exceptions.UnauthorizedException)
                                        {
                                            this.SendDmError();
                                            return;
                                        }
                                        catch (Exception)
                                        {
                                            throw;
                                        }

                                        embed.Description = this.GetString(this.t.Commands.Utility.EmojiStealer.SuccessDmMain, true,
                                            new TVar("Count", (IncludeStickers ? SanitizedEmoteList.Count : SanitizedEmoteList.Where(x => x.Value.EntryType == EmojiType.EMOJI).Count())),
                                            new TVar("Type", emojiText));
                                        _ = await this.RespondOrEdit(embed.AsSuccess(ctx));
                                    }
                                    else if (e.GetCustomId() == SendHereButton.CustomId)
                                    {
                                        if (!ctx.Member.Permissions.HasPermission(Permissions.AttachFiles))
                                        {
                                            this.SendPermissionError(Permissions.AttachFiles);
                                            return;
                                        }

                                        embed.Description = this.GetString(this.t.Commands.Utility.EmojiStealer.SendingZipChat, true);
                                        _ = await this.RespondOrEdit(embed);

                                        embed.Description = this.GetString(this.t.Commands.Utility.EmojiStealer.SuccessChat, true,
                                            new TVar("Count", (IncludeStickers ? SanitizedEmoteList.Count : SanitizedEmoteList.Where(x => x.Value.EntryType == EmojiType.EMOJI).Count())),
                                            new TVar("Type", emojiText));

                                        _ = zipFileStream.Seek(0, SeekOrigin.Begin);
                                        _ = await this.RespondOrEdit(new DiscordMessageBuilder().WithFile($"Emotes.zip", zipFileStream).AddEmbed(embed.AsSuccess(ctx)));
                                    }
                                    return;
                                }
                                else if (e.GetCustomId() == IncludeStickersButton.CustomId)
                                {
                                    IncludeStickers = !IncludeStickers;

                                    if (!IncludeStickers)
                                    {
                                        if (!SanitizedEmoteList.Any(x => x.Value.EntryType == EmojiType.EMOJI))
                                            IncludeStickers = true;
                                    }

                                    IncludeStickersButton = new DiscordButtonComponent((IncludeStickers ? ButtonStyle.Success : ButtonStyle.Danger), "ToggleStickers", this.GetString(this.t.Commands.Utility.EmojiStealer.ToggleStickers), !SanitizedEmoteList.Any(x => x.Value.EntryType == EmojiType.EMOJI), new DiscordComponentEmoji(DiscordEmoji.FromGuildEmote(ctx.Client, (ulong)(IncludeStickers ? 970278964755038248 : 970278964079767574))));
                                    AddToServerButton = new DiscordButtonComponent(ButtonStyle.Success, "AddToServer", (IncludeStickers ? this.GetString(this.t.Commands.Utility.EmojiStealer.AddEmojisAndStickerToServer) : this.GetString(this.t.Commands.Utility.EmojiStealer.AddEmojisToServer)), !ctx.Member.Permissions.HasPermission(Permissions.ManageGuildExpressions), new DiscordComponentEmoji(DiscordEmoji.FromUnicode("➕")));

                                    var builder = new DiscordMessageBuilder().AddEmbed(embed);

                                    if (SanitizedEmoteList.Any(x => x.Value.EntryType == EmojiType.STICKER))
                                        _ = builder.AddComponents(IncludeStickersButton);

                                    _ = builder.AddComponents(new List<DiscordComponent> { AddToServerButton, ZipPrivateMessageButton, SinglePrivateMessageButton, SendHereButton });

                                    _ = await this.RespondOrEdit(builder);
                                }
                            }
                        }
                        finally
                        {
                            if (e.GetCustomId() != IncludeStickersButton.CustomId)
                                FinishedInteraction = true;
                        }
                    }).Add(ctx.Bot, ctx);
                }
            }
            finally
            {
                while (!FinishedInteraction)
                    await Task.Delay(1000);

                try
                { await zipFileStream.DisposeAsync(); }
                catch { }
                foreach (var b in SanitizedEmoteList)
                    try
                    { await b.Value.Data.Stream.DisposeAsync(); }
                    catch { }
            }
        });
    }
}