using DSharpPlus.Entities;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using System.Linq;
using Microsoft.Extensions.Logging;
using TomatenMusic.Prompt.Option;
using TomatenMusic.Util;
using DSharpPlus.Exceptions;
using Microsoft.Extensions.DependencyInjection;
using DSharpPlus;

namespace TomatenMusic.Prompt.Model
{
    abstract class DiscordPromptBase
    {
        public static List<DiscordPromptBase> ActivePrompts { get; } = new List<DiscordPromptBase>();

        public PromptState State { get; protected set; }
        public DiscordMessage Message { get; private set; }
        public DiscordInteraction Interaction { get; private set; }
        public List<IPromptOption> Options { get; protected set; } = new List<IPromptOption>();
        public DiscordClient _client { get; set; }
        public DiscordPromptBase LastPrompt { get; protected set; }
        public System.Timers.Timer TimeoutTimer { get; set; }

        protected ILogger<DiscordPromptBase> _logger { get; set; }

        protected EventId eventId = new EventId(16, "Prompts");

        protected DiscordPromptBase(DiscordPromptBase lastPrompt)
        {
            LastPrompt = lastPrompt;
            Options = new List<IPromptOption>();
            IServiceProvider serviceProvider = TomatenMusicBot.ServiceProvider;

            _logger = serviceProvider.GetRequiredService<ILogger<DiscordPromptBase>>();
 

            if (lastPrompt != null)
            {
                Options.Add(new ButtonPromptOption
                {
                    Style = DSharpPlus.ButtonStyle.Danger,
                    Row = 5,
                    Emoji = new DiscordComponentEmoji("↩️"),
                    Run = async (args, sender, option) =>
                    {
                        _ = BackAsync();
                    }
                });
            }

            Options.Add(new ButtonPromptOption
            {
                Style = DSharpPlus.ButtonStyle.Danger,
                Row = 5,
                Emoji = new DiscordComponentEmoji("❌"),
                Run = async (args, sender, option) =>
                {
                    _ = InvalidateAsync();
                }
            });

        }

        public async Task InvalidateAsync(bool withEdit = true, bool destroyHistory = false)
        {
            foreach (var option in Options)
                option.UpdateMethod = (prompt) =>
                {
                    prompt.Disabled = true;
                    return Task.FromResult<IPromptOption>(prompt);
                };

            if (withEdit)
                await EditMessageAsync(new DiscordWebhookBuilder().WithContent("This Prompt is invalid!"));
            ActivePrompts.Remove(this);
            if (destroyHistory)
            {
                if (LastPrompt != null)
                    await LastPrompt.InvalidateAsync(false);
                await EditMessageAsync(new DiscordWebhookBuilder().WithContent("This Prompt is invalid!"));
            }

            if (State == PromptState.INVALID)
                return;
            State = PromptState.INVALID;
            

            _client.ComponentInteractionCreated -= Discord_ComponentInteractionCreated;

        }

        public async Task SendAsync(DiscordChannel channel)
        {
            if (State == PromptState.INVALID)
                return;

            IServiceProvider serviceProvider = TomatenMusicBot.ServiceProvider;
            var client = serviceProvider.GetRequiredService<DiscordShardedClient>();
            _client = client.GetShard( (ulong) channel.GuildId);

            _client.ComponentInteractionCreated += Discord_ComponentInteractionCreated;
            ActivePrompts.Add(this);
            AddGuids();
            DiscordMessageBuilder builder = await GetMessageAsync();
            builder = await AddComponentsAsync(builder);


            Message = await builder.SendAsync(channel);
            State = PromptState.OPEN;
        }

        public async Task SendAsync(DiscordInteraction interaction, bool ephemeral = false)
        {
            if (State == PromptState.INVALID)
                return;

            IServiceProvider serviceProvider = TomatenMusicBot.ServiceProvider;
            var client = serviceProvider.GetRequiredService<DiscordShardedClient>();
            _client = client.GetShard((ulong)interaction.GuildId);

            _client.ComponentInteractionCreated += Discord_ComponentInteractionCreated;
            ActivePrompts.Add(this);

            AddGuids();
            DiscordFollowupMessageBuilder builder = await GetFollowupMessageAsync();
            builder = await AddComponentsAsync(builder);
            builder.AsEphemeral(ephemeral);

            Interaction = interaction;
            Message = await interaction.CreateFollowupMessageAsync(builder);
            State = PromptState.OPEN;

            long timeoutTime = (Interaction.CreationTimestamp.ToUnixTimeMilliseconds() + 900000) - DateTimeOffset.Now.ToUnixTimeMilliseconds();

            if (TimeoutTimer != null)
                TimeoutTimer.Close();

            TimeoutTimer = new System.Timers.Timer(timeoutTime);
            TimeoutTimer.Elapsed += OnTimeout;
            TimeoutTimer.AutoReset = false;
            TimeoutTimer.Start();
        }

        public async Task UseAsync(DiscordMessage message)
        {
            if (State == PromptState.INVALID)
                return;

            IServiceProvider serviceProvider = TomatenMusicBot.ServiceProvider;
            var client = serviceProvider.GetRequiredService<DiscordShardedClient>();
            _client = client.GetShard((ulong)message.Channel.GuildId);

            _client.ComponentInteractionCreated += Discord_ComponentInteractionCreated;
            ActivePrompts.Add(this);

            AddGuids();
            DiscordWebhookBuilder builder = await GetWebhookMessageAsync();

            await EditMessageAsync(builder);
            State = PromptState.OPEN;

        }

        public async Task UseAsync(DiscordInteraction interaction, DiscordMessage message)
        {
            if (State == PromptState.INVALID)
                return;

            IServiceProvider serviceProvider = TomatenMusicBot.ServiceProvider;
            var client = serviceProvider.GetRequiredService<DiscordShardedClient>();
            _client = client.GetShard((ulong)interaction.GuildId);

            _client.ComponentInteractionCreated += Discord_ComponentInteractionCreated;

            ActivePrompts.Add(this);
            AddGuids();
            DiscordWebhookBuilder builder = await GetWebhookMessageAsync();
            Interaction = interaction;
            Message = message;
            await EditMessageAsync(builder);
            State = PromptState.OPEN;

            long timeoutTime = (Interaction.CreationTimestamp.ToUnixTimeMilliseconds() + 900000) - DateTimeOffset.Now.ToUnixTimeMilliseconds();

            if (TimeoutTimer != null)
                TimeoutTimer.Close();

            TimeoutTimer = new System.Timers.Timer(timeoutTime);
            TimeoutTimer.Elapsed += OnTimeout;
            TimeoutTimer.AutoReset = false;
            TimeoutTimer.Start();
            
        }

        private void OnTimeout(object? sender, System.Timers.ElapsedEventArgs e)
        {
            _ = InvalidateAsync();
        }

        private void AddGuids()
        {
            foreach (var item in Options)
            {
                item.CustomID = RandomUtil.GenerateGuid();
                if (item is SelectMenuPromptOption)
                {
                    SelectMenuPromptOption menuItem = (SelectMenuPromptOption)item;
                    foreach (var option in menuItem.Options)
                    {
                        option.CustomID = RandomUtil.GenerateGuid();
                    }
                }

            }
            //this.Options = options;
        }

        protected abstract Task<DiscordComponent> GetComponentAsync(IPromptOption option);

        protected abstract Task<DiscordMessageBuilder> GetMessageAsync();

        private async Task<DiscordFollowupMessageBuilder> GetFollowupMessageAsync()
        {
            DiscordMessageBuilder oldBuilder = await GetMessageAsync();

            return new DiscordFollowupMessageBuilder()
                .WithContent(oldBuilder.Content)
                .AddEmbeds(oldBuilder.Embeds);
                
        }
        private async Task<DiscordWebhookBuilder> GetWebhookMessageAsync()
        {
            DiscordMessageBuilder oldBuilder = await GetMessageAsync();

            return new DiscordWebhookBuilder()
                .WithContent(oldBuilder.Content)
                .AddEmbeds(oldBuilder.Embeds);

        }

        public async Task UpdateAsync()
        {
           if (State == PromptState.INVALID)
                return;
           await EditMessageAsync(await GetWebhookMessageAsync());
           
        }

        private async Task UpdateOptionsAsync()
        {
            List<IPromptOption> options = new List<IPromptOption>();
            foreach (var option in this.Options)
                options.Add(await option.UpdateMethod.Invoke(option));
            this.Options = options;
        }

        protected async Task Discord_ComponentInteractionCreated(DSharpPlus.DiscordClient sender, DSharpPlus.EventArgs.ComponentInteractionCreateEventArgs e)
        {
            if (State == PromptState.INVALID)
                return;

            foreach (var option in Options)
            {
                if (option.CustomID == e.Id)
                {

                    await e.Interaction.CreateResponseAsync(DSharpPlus.InteractionResponseType.DeferredMessageUpdate);
                    _ = option.Run.Invoke(e, sender, option);
                }
            }
        }

        public async Task EditMessageAsync(DiscordWebhookBuilder builder)
        {
            try
            {
                if (Interaction != null)
                {
                    await AddComponentsAsync(builder);
                    try
                    {
                        Message = await Interaction.EditFollowupMessageAsync(Message.Id, builder);
                    }catch (Exception e)
                    {
                        Message = await Interaction.EditOriginalResponseAsync(builder);
                    }

                }
                else
                {
                    DiscordMessageBuilder msgbuilder = new DiscordMessageBuilder()
                        .AddEmbeds(builder.Embeds)
                        .WithContent(builder.Content);
                    await AddComponentsAsync(msgbuilder);
                    Message = await Message.ModifyAsync(msgbuilder);
                }
            }catch (BadRequestException e)
            {
                _logger.LogError(e.Errors);
            }

        }

        protected async Task<DiscordMessageBuilder> AddComponentsAsync(DiscordMessageBuilder builder)
        {
            await UpdateOptionsAsync();
            builder.ClearComponents();

            List<DiscordComponent> row1 = new List<DiscordComponent>(5);
            List<DiscordComponent> row2 = new List<DiscordComponent>(5);
            List<DiscordComponent> row3 = new List<DiscordComponent>(5);
            List<DiscordComponent> row4 = new List<DiscordComponent>(5);
            List<DiscordComponent> row5 = new List<DiscordComponent>(5);

            foreach (var option in Options)
            {
                switch (option.Row)
                {
                    case 1:
                        row1.Add(await GetComponentAsync(option));
                        break;
                    case 2:
                        row2.Add(await GetComponentAsync(option));
                        break;
                    case 3:
                        row3.Add(await GetComponentAsync(option));
                        break;
                    case 4:
                        row4.Add(await GetComponentAsync(option));
                        break;
                    case 5:
                        row5.Add(await GetComponentAsync(option));
                        break;
                    default:
                        throw new ArgumentException("Invalid Row! Must be between 1 and 5", "Row");
                }
            }
            if (row1.Count != 0)
            {
                builder.AddComponents(row1);
            }
            if (row2.Count != 0)
            {
                builder.AddComponents(row2);
            }
            if (row3.Count != 0)
            {
                builder.AddComponents(row3);
            }
            if (row4.Count != 0)
            {
                builder.AddComponents(row4);
            }
            if (row5.Count != 0)
            {
                builder.AddComponents(row5);
            }
            return builder;
        }

        protected async Task<DiscordFollowupMessageBuilder> AddComponentsAsync(DiscordFollowupMessageBuilder builder)
        {
            await UpdateOptionsAsync();
            builder.ClearComponents();

            List<DiscordComponent> row1 = new List<DiscordComponent>(5);
            List<DiscordComponent> row2 = new List<DiscordComponent>(5);
            List<DiscordComponent> row3 = new List<DiscordComponent>(5);
            List<DiscordComponent> row4 = new List<DiscordComponent>(5);
            List<DiscordComponent> row5 = new List<DiscordComponent>(5);

            foreach (var option in Options)
            {
                switch (option.Row)
                {
                    case 1:
                        row1.Add(await GetComponentAsync(option));
                        break;
                    case 2:
                        row2.Add(await GetComponentAsync(option));
                        break;
                    case 3:
                        row3.Add(await GetComponentAsync(option));
                        break;
                    case 4:
                        row4.Add(await GetComponentAsync(option));
                        break;
                    case 5:
                        row5.Add(await GetComponentAsync(option));
                        break;
                    default:
                        throw new ArgumentException("Invalid Row! Must be between 1 and 5", "Row");
                }
            }
            if (row1.Count != 0)
            {
                builder.AddComponents(row1);
            }
            if (row2.Count != 0)
            {
                builder.AddComponents(row2);
            }
            if (row3.Count != 0)
            {
                builder.AddComponents(row3);
            }
            if (row4.Count != 0)
            {
                builder.AddComponents(row4);
            }
            if (row5.Count != 0)
            {
                builder.AddComponents(row5);
            }
            return builder;
        }

        protected async Task<DiscordWebhookBuilder> AddComponentsAsync(DiscordWebhookBuilder builder)
        {
            await UpdateOptionsAsync();
            builder.ClearComponents();

            List<DiscordComponent> row1 = new List<DiscordComponent>(5);
            List<DiscordComponent> row2 = new List<DiscordComponent>(5);
            List<DiscordComponent> row3 = new List<DiscordComponent>(5);
            List<DiscordComponent> row4 = new List<DiscordComponent>(5);
            List<DiscordComponent> row5 = new List<DiscordComponent>(5);

            foreach (var option in Options)
            {
                switch (option.Row)
                {
                    case 1:
                        row1.Add(await GetComponentAsync(option));
                        break;
                    case 2:
                        row2.Add(await GetComponentAsync(option));
                        break;
                    case 3:
                        row3.Add(await GetComponentAsync(option));
                        break;
                    case 4:
                        row4.Add(await GetComponentAsync(option));
                        break;
                    case 5:
                        row5.Add(await GetComponentAsync(option));
                        break;
                    default:
                        throw new ArgumentException("Invalid Row! Must be between 1 and 5", "Row");
                }
            }
            if (row1.Count != 0)
            {
                builder.AddComponents(row1);
            }
            if (row2.Count != 0)
            {
                builder.AddComponents(row2);
            }
            if (row3.Count != 0)
            {
                builder.AddComponents(row3);
            }
            if (row4.Count != 0)
            {
                builder.AddComponents(row4);
            }
            if (row5.Count != 0)
            {
                builder.AddComponents(row5);
            }
            return builder;
        }

        public async Task BackAsync()
        {

            if (LastPrompt == null)
                return;
            _client.ComponentInteractionCreated -= LastPrompt.Discord_ComponentInteractionCreated;

            await InvalidateAsync(false);
            if (Interaction == null)
                await LastPrompt.UseAsync(Message);
            else
                await LastPrompt.UseAsync(Interaction, Message);
        }

        public void AddOption(IPromptOption option)
        {
            Options.Add(option);
        }

    }
}