using Discord;
using Discord.Commands;
using Discord.WebSocket;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using NadekoBot.Services;

namespace NadekoBot.Extensions
{
    public static class IMessageChannelExtensions
    {
        public static Task<IUserMessage> EmbedAsync(this IMessageChannel ch, IEmbedBuilder embed, string msg = "")
            => ch.SendMessageAsync(msg, embed: embed.Build(),
                options: new RequestOptions() { RetryMode  = RetryMode.AlwaysRetry });

        // this is a huge problem, because now i don't have
        // access to embed builder service
        // as this is an extension of the message channel
        public static Task<IUserMessage> SendErrorAsync(this IMessageChannel ch, IEmbedBuilderService eb, string title, string error, string url = null, string footer = null)
        {
            var embed = eb.Create()
                .WithErrorColor()
                .WithDescription(error)
                .WithTitle(title);

            if (url != null && Uri.IsWellFormedUriString(url, UriKind.Absolute))
                embed.WithUrl(url);
                    
            if (!string.IsNullOrWhiteSpace(footer))
                embed.WithFooter(footer);
            
            return ch.SendMessageAsync("", embed: embed.Build());
        }

        public static Task<IUserMessage> SendErrorAsync(this IMessageChannel ch, IEmbedBuilderService eb, string error)
            => ch.SendMessageAsync("",
                embed: eb.Create()
                    .WithErrorColor()
                    .WithDescription(error)
                    .Build());

        public static Task<IUserMessage> SendPendingAsync(this IMessageChannel ch, IEmbedBuilderService eb, string message)
            => ch.SendMessageAsync("", embed: eb.Create()
                .WithPendingColor()
                .WithDescription(message)
                .Build());
        
        public static Task<IUserMessage> SendConfirmAsync(this IMessageChannel ch, IEmbedBuilderService eb, string title, string text, string url = null, string footer = null)
        {
            var embed = eb.Create().WithOkColor().WithDescription(text)
                .WithTitle(title);
            
            if (url != null && Uri.IsWellFormedUriString(url, UriKind.Absolute))
                embed.WithUrl(url);
            
            if (!string.IsNullOrWhiteSpace(footer))
                embed.WithFooter(footer);
            
            return ch.SendMessageAsync("", embed: embed.Build());
        }

        public static Task<IUserMessage> SendConfirmAsync(this IMessageChannel ch, IEmbedBuilderService eb, string text)
            => ch.SendMessageAsync("", embed: eb.Create()
                .WithOkColor()
                .WithDescription(text)
                .Build());

        public static Task<IUserMessage> SendTableAsync<T>(this IMessageChannel ch, string seed, IEnumerable<T> items, Func<T, string> howToPrint, int columns = 3)
        {
            return ch.SendMessageAsync($@"{seed}```css
{string.Join("\n", items.Chunk(columns)
                        .Select(ig => string.Concat(ig.Select(el => howToPrint(el)))))}
```");
        }

        public static Task<IUserMessage> SendTableAsync<T>(this IMessageChannel ch, IEnumerable<T> items, Func<T, string> howToPrint, int columns = 3) =>
            ch.SendTableAsync("", items, howToPrint, columns);
        
        private static readonly IEmote arrow_left = new Emoji("⬅");
        private static readonly IEmote arrow_right = new Emoji("➡");

        public static Task SendPaginatedConfirmAsync(this ICommandContext ctx,
            int currentPage, Func<int, IEmbedBuilder> pageFunc, int totalElements,
            int itemsPerPage, bool addPaginatedFooter = true)
            => ctx.SendPaginatedConfirmAsync(currentPage,
                (x) => Task.FromResult(pageFunc(x)), totalElements, itemsPerPage, addPaginatedFooter);
        /// <summary>
        /// danny kamisama
        /// </summary>
        public static async Task SendPaginatedConfirmAsync(this ICommandContext ctx, int currentPage, 
            Func<int, Task<IEmbedBuilder>> pageFunc, int totalElements, int itemsPerPage, bool addPaginatedFooter = true)
        {
            var embed = await pageFunc(currentPage).ConfigureAwait(false);

            var lastPage = (totalElements - 1) / itemsPerPage;
            
            var canPaginate = true;
            var sg = ctx.Guild as SocketGuild;
            if (sg is not null && !sg.CurrentUser.GetPermissions((IGuildChannel) ctx.Channel).AddReactions)
                canPaginate = false;
            
            if (!canPaginate)
                embed.WithFooter("⚠️ AddReaction permission required for pagination.");
            else if (addPaginatedFooter)
                embed.AddPaginatedFooter(currentPage, lastPage);

            var msg = await ctx.Channel.EmbedAsync(embed).ConfigureAwait(false) as IUserMessage;

            if (lastPage == 0 || !canPaginate)
                return;

            await msg.AddReactionAsync(arrow_left).ConfigureAwait(false);
            await msg.AddReactionAsync(arrow_right).ConfigureAwait(false);

            await Task.Delay(2000).ConfigureAwait(false);

            var lastPageChange = DateTime.MinValue;

            async Task changePage(SocketReaction r)
            {
                try
                {
                    if (r.UserId != ctx.User.Id)
                        return;
                    if (DateTime.UtcNow - lastPageChange < TimeSpan.FromSeconds(1))
                        return;
                    if (r.Emote.Name == arrow_left.Name)
                    {
                        if (currentPage == 0)
                            return;
                        lastPageChange = DateTime.UtcNow;
                        var toSend = await pageFunc(--currentPage).ConfigureAwait(false);
                        if (addPaginatedFooter)
                            toSend.AddPaginatedFooter(currentPage, lastPage);
                        await msg.ModifyAsync(x => x.Embed = toSend.Build()).ConfigureAwait(false);
                    }
                    else if (r.Emote.Name == arrow_right.Name)
                    {
                        if (lastPage > currentPage)
                        {
                            lastPageChange = DateTime.UtcNow;
                            var toSend = await pageFunc(++currentPage).ConfigureAwait(false);
                            if (addPaginatedFooter)
                                toSend.AddPaginatedFooter(currentPage, lastPage);
                            await msg.ModifyAsync(x => x.Embed = toSend.Build()).ConfigureAwait(false);
                        }
                    }
                }
                catch (Exception)
                {
                    //ignored
                }
            }

            using (msg.OnReaction((DiscordSocketClient)ctx.Client, changePage, changePage))
            {
                await Task.Delay(30000).ConfigureAwait(false);
            }

            try
            {
                if(msg.Channel is ITextChannel && ((SocketGuild)ctx.Guild).CurrentUser.GuildPermissions.ManageMessages)
                {
                    await msg.RemoveAllReactionsAsync().ConfigureAwait(false);
                }
                else
                {
                    await Task.WhenAll(msg.Reactions.Where(x => x.Value.IsMe)
                        .Select(x => msg.RemoveReactionAsync(x.Key, ctx.Client.CurrentUser)));
                }
            }
            catch
            {
                // ignored
            }
        }

        public static Task OkAsync(this ICommandContext ctx)
            => ctx.Message.AddReactionAsync(new Emoji("✅"));
        
        public static Task ErrorAsync(this ICommandContext ctx)
            => ctx.Message.AddReactionAsync(new Emoji("❌"));
        
        public static Task WarningAsync(this ICommandContext ctx)
            => ctx.Message.AddReactionAsync(new Emoji("⚠️"));
    }
}
