using DSharpPlus;
using System;
using System.Collections.Generic;
using System.Text;
using DSharpPlus.Entities;
using System.Threading.Tasks;
using System.Linq;
using TomatenMusic.Music.Entitites;
using Microsoft.Extensions.Logging;
using TomatenMusic.Services;
using TomatenMusic.Prompt.Implementation;
using Lavalink4NET.Player;
using Lavalink4NET.Events;
using Lavalink4NET;
using Lavalink4NET.Rest;
using Microsoft.Extensions.DependencyInjection;
using Lavalink4NET.Decoding;

namespace TomatenMusic.Music
{
    public class GuildPlayer : LavalinkPlayer
    {

        ILogger<GuildPlayer> _logger { get; set; }
        public PlayerQueue PlayerQueue { get;} = new PlayerQueue();
        public DiscordClient _client { get; set; }
        public ISpotifyService _spotify { get; set; }
        public IAudioService _audioService { get; set; }

        public bool Autoplay { get; set; } = false;

        public GuildPlayer()
        {
            IServiceProvider serviceProvider = TomatenMusicBot.ServiceProvider;
            _logger = serviceProvider.GetRequiredService<ILogger<GuildPlayer>>();
            var client = serviceProvider.GetRequiredService<DiscordShardedClient>();
            _client = client.GetShard(GuildId);

            _spotify = serviceProvider.GetRequiredService<ISpotifyService>();
            _audioService = serviceProvider.GetRequiredService<IAudioService>();
        }
        public async override Task PlayAsync(LavalinkTrack track, TimeSpan? startTime = null, TimeSpan? endTime = null, bool noReplace = true)
        {

            EnsureConnected();
            EnsureNotDestroyed();

            if (State == PlayerState.NotPlaying)
            {
                PlayerQueue.LastTrack = track;
                await base.PlayAsync(track, startTime, endTime, noReplace);
                _logger.LogInformation("Started playing Track {0} on Guild {1}", track.Title, (await GetGuildAsync()).Name);
            }else
                PlayerQueue.QueueTrack(track);

            QueuePrompt.UpdateFor(GuildId);
        }

        public async Task PlayNowAsync(LavalinkTrack track, TimeSpan? startTime = null, TimeSpan? endTime = null, bool withoutQueuePrepend = false)
        {

            EnsureConnected();
            EnsureNotDestroyed();

            if (!withoutQueuePrepend)
                PlayerQueue.Queue = new Queue<LavalinkTrack>(PlayerQueue.Queue.Prepend(PlayerQueue.LastTrack));

            PlayerQueue.LastTrack = track;
            await base.PlayAsync(track, startTime, endTime);
            _logger.LogInformation("Started playing Track {0} now on Guild {1}", track.Title, (await GetGuildAsync()).Name);


            QueuePrompt.UpdateFor(GuildId);
        }

        public async Task PlayTracksAsync(List<LavalinkTrack> tracks)
        {
            EnsureNotDestroyed();
            EnsureConnected();

            _logger.LogInformation("Started playing TrackList {0} on Guild {1}", tracks.ToString(), (await GetGuildAsync()).Name);

            await PlayerQueue.QueueTracksAsync(tracks);

            if (State == PlayerState.NotPlaying)
            {
                LavalinkTrack nextTrack = PlayerQueue.NextTrack().Track;
                await base.PlayAsync(nextTrack);
            }
            QueuePrompt.UpdateFor(GuildId);
        }
        public async Task PlayTracksNowAsync(IEnumerable<LavalinkTrack> tracks)
        {

            EnsureConnected();
            EnsureNotDestroyed();
            Queue<LavalinkTrack> reversedTracks = new Queue<LavalinkTrack>(tracks);

            LavalinkTrack track = reversedTracks.Dequeue();
            PlayerQueue.LastTrack = track;
            await base.PlayAsync(track);
            _logger.LogInformation("Started playing Track {0} on Guild {1}", track.Title, (await GetGuildAsync()).Name);

            reversedTracks.Reverse();

            foreach (var item in reversedTracks)
            {
                PlayerQueue.Queue = new Queue<LavalinkTrack>(PlayerQueue.Queue.Prepend(PlayerQueue.LastTrack));
            }

            QueuePrompt.UpdateFor(GuildId);
        }

        public async Task PlayPlaylistAsync(ILavalinkPlaylist playlist)
        {
            EnsureNotDestroyed();
            EnsureConnected();

            _logger.LogInformation("Started playing Playlist {0} on Guild {1}", playlist.Name, (await GetGuildAsync()).Name);

            await PlayerQueue.QueuePlaylistAsync(playlist);


            if (State == PlayerState.NotPlaying)
            {
                LavalinkTrack nextTrack = PlayerQueue.NextTrack().Track;
                await base.PlayAsync(nextTrack);
            }
            QueuePrompt.UpdateFor(GuildId);
        }

        public async Task PlayPlaylistNowAsync(ILavalinkPlaylist playlist)
        {
            EnsureConnected();
            EnsureNotDestroyed();
            if (!PlayerQueue.Queue.Any())
                PlayerQueue.CurrentPlaylist = playlist;

            Queue<LavalinkTrack> reversedTracks = new Queue<LavalinkTrack>(playlist.Tracks);

            LavalinkTrack track = reversedTracks.Dequeue();
            PlayerQueue.LastTrack = track;
            await base.PlayAsync(track);
            _logger.LogInformation("Started playing Track {0} on Guild {1}", track.Title, (await GetGuildAsync()).Name);

            reversedTracks.Reverse();

            foreach (var item in reversedTracks)
            {
                PlayerQueue.Queue = new Queue<LavalinkTrack>(PlayerQueue.Queue.Prepend(PlayerQueue.LastTrack));
            }

            QueuePrompt.UpdateFor(GuildId);
        }

        public async Task RewindAsync()
        {
            EnsureNotDestroyed();
            EnsureConnected();
            if (Position.Position.Seconds < 4)
            {
                await ReplayAsync();
                return;
            }

            MusicActionResponse response = PlayerQueue.Rewind();
            
            _logger.LogInformation($"Rewinded Track {CurrentTrack.Title} for Track {response.Track.Title}");
            await base.PlayAsync(response.Track);
            QueuePrompt.UpdateFor(GuildId);
        }

        public async Task SkipAsync()
        {
            EnsureNotDestroyed();
            EnsureConnected();
            MusicActionResponse response = PlayerQueue.NextTrack(true);

            _logger.LogInformation($"Skipped Track {CurrentTrack.Title} for Track {response.Track.Title}");
            await base.PlayAsync(response.Track);
            QueuePrompt.UpdateFor(GuildId);
        }

        public async Task TogglePauseAsync()
        {
            EnsureNotDestroyed();
            EnsureConnected();

            if (State == PlayerState.NotPlaying) throw new InvalidOperationException("Cant pause Song! Nothing is Playing.");


            if (State == PlayerState.Paused)
                await ResumeAsync();
            else
                await PauseAsync();

            QueuePrompt.UpdateFor(GuildId);
        }

        public async Task SetLoopAsync(LoopType type)
        {

            EnsureNotDestroyed();
            EnsureConnected();

            if (State == PlayerState.NotPlaying) throw new InvalidOperationException("Cant change LoopType! Nothing is Playing.");

            _ = PlayerQueue.SetLoopAsync(type);
            QueuePrompt.UpdateFor(GuildId);

        }

        public async Task ShuffleAsync()
        {

            EnsureNotDestroyed();
            EnsureConnected();

            await PlayerQueue.ShuffleAsync();

            QueuePrompt.UpdateFor(GuildId);
        }
        public async override Task ConnectAsync(ulong voiceChannelId, bool selfDeaf = true, bool selfMute = false)
        {
            EnsureNotDestroyed();

            DiscordChannel channel = await _client.GetChannelAsync(voiceChannelId);

            if (channel.Type != ChannelType.Voice && channel.Type != ChannelType.Stage) throw new ArgumentException("The channel Id provided was not a voice channel");

            if (State != PlayerState.NotConnected)
                throw new InvalidOperationException("The Bot is already connected.");

            await base.ConnectAsync(voiceChannelId, selfDeaf, selfMute);

            if (channel.Type == ChannelType.Stage)
            {
                DiscordStageInstance stageInstance = await channel.GetStageInstanceAsync();

                if (stageInstance == null)
                    stageInstance = await channel.CreateStageInstanceAsync("Music");
                await stageInstance.Channel.UpdateCurrentUserVoiceStateAsync(false);
            }

            _logger.LogInformation("Connected to Channel {0} on Guild {1}", channel.Name, channel.Guild.Name);
        }
        public override Task DisconnectAsync()
        {
            _logger.LogInformation("Disconnected from Channel {0} on Guild {1}", VoiceChannelId, GuildId);

            QueuePrompt.InvalidateFor(GuildId);
            return base.DisconnectAsync();
        }

        public override async Task SeekPositionAsync(TimeSpan timeSpan)
        {
            EnsureNotDestroyed();
            EnsureConnected();

            if (State == PlayerState.NotPlaying) throw new InvalidOperationException("Cant change LoopType! Nothing is Playing.");

            if (timeSpan.CompareTo(CurrentTrack.Duration) == 1) throw new ArgumentException("Please specify a TimeSpan shorter than the Track");

            await base.SeekPositionAsync(timeSpan);
            QueuePrompt.UpdateFor(GuildId);
        }
        protected override void Dispose(bool disposing)
        {
            QueuePrompt.InvalidateFor(GuildId);

            base.Dispose(disposing);
        }

        public async override Task OnTrackEndAsync(TrackEndEventArgs eventArgs)
        {
            DisconnectOnStop = false;
            YoutubeService youtube = TomatenMusicBot.ServiceProvider.GetRequiredService<YoutubeService>();
            var oldTrack = CurrentTrack;

            if (eventArgs.Reason != TrackEndReason.Finished)
                return;

            if (eventArgs.MayStartNext)
            {
                try
                {
                    MusicActionResponse response = PlayerQueue.NextTrack();
                    _ = PlayNowAsync(response.Track, withoutQueuePrepend: true);
                }
                catch (Exception ex)
                {
                    if (!Autoplay)
                    {
                        _logger.LogInformation("Track has ended and Queue was Empty... Idling");
                        await base.OnTrackEndAsync(eventArgs);
                        return;
                    }

                    LavalinkTrack newTrack = await youtube.GetRelatedTrackAsync(oldTrack.TrackIdentifier);
                    _logger.LogInformation($"Autoplaying for track {oldTrack.TrackIdentifier} with Track {newTrack.TrackIdentifier}");
                    await PlayNowAsync(newTrack, withoutQueuePrepend: true);

                    /*                   try
                                       {
                                           LavalinkTrack track = await youtube.GetRelatedTrackAsync(eventArgs.TrackIdentifier);
                                           _logger.LogInformation($"Autoplaying for track {eventArgs.TrackIdentifier} with Track {track.TrackIdentifier}");
                                           await PlayAsync(track);
                                       }
                                       catch (Exception ex2)
                                       {
                                           await base.OnTrackEndAsync(eventArgs);
                                       }*/
                }
            }

            
        }

        public async Task<DiscordChannel> GetChannelAsync()
        {
            EnsureConnected();
            EnsureNotDestroyed();
            DiscordGuild guild = await GetGuildAsync();

            return guild.GetChannel((ulong) VoiceChannelId);
        }

        public async Task<DiscordGuild> GetGuildAsync()
        {
            return await _client.GetGuildAsync(GuildId);
        }
        public async Task<bool> AreActionsAllowedAsync(DiscordMember member)
        {
            if (member.VoiceState == null || member.VoiceState.Channel == null)
            {
                return false;
            }

            if (await GetChannelAsync() != null && await GetChannelAsync() != member.VoiceState.Channel)
            {
                return false;
            }

            return true;
        }

    }
}