using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.SignalR.Client;
using Polly;
using Polly.Retry;
using Refit;
using SharpFontInternal;
using Sledgemapper.Messages;
using Sledgemapper.Shared.Entities;
using TinyMessenger;

namespace Sledgemapper
{
    public class CommunicationManager
    {
        public TinyMessengerHub Messenger { get; }
        public IMapApi Api { get; }
        public HubConnection Connection { get; }

        private readonly ChannelsQueue _queue;
        private readonly AsyncRetryPolicy _retryPolicy;
        public readonly Session SessionData;
        private AuthenticateResponse _authenticateResponse;

        public CommunicationManager(Session sessionData, TinyMessengerHub messenger)
        {
            Messenger = messenger;
            _queue = new ChannelsQueue(Messenger);
#if DEBUG
            var baseAddress = "http://localhost:5000";
            baseAddress = "http://hub.michelescandura.com:5001";
#else
            var baseAddress = "http://hub.michelescandura.com:5001";
#endif

            CheckLogin();

            _retryPolicy = Policy
                .Handle<ApiException>(ex => ex.StatusCode == HttpStatusCode.RequestTimeout)
                .Or<HttpRequestException>()
                .RetryAsync(3);

            //.RetryAsync(Polly.RetrySyntax., async (exception, retryCount) => await Task.Delay(500))


            SessionData = sessionData;
            Connection = new HubConnectionBuilder()
                .WithAutomaticReconnect()
                .WithUrl($"{baseAddress}/SledgemapperHub",
                    options => { options.AccessTokenProvider = () => Task.FromResult(_authenticateResponse.Token); })
                .Build();

            Api = RestService.For<IMapApi>(
                new HttpClient(new AuthenticatedHttpClientHandler(GetToken))
                {
                    BaseAddress = new Uri(baseAddress)
                }
            );

            Connection.On<Session>("UpdateMap", map =>
            {
                SessionData.Map = map.Map;
                SessionData.Walls = map.Walls;
                SessionData.Overlays = map.Overlays;
            });

            Connection.On<Player>("PlayerUpdate", player =>
            {
                var p = SessionData.Players.FirstOrDefault(m => m.UserId == player.UserId);
                if (p != null)
                    p.Position = player.Position;
                else
                    SessionData.Players.Add(player);
            });

            Connection.On<Tile>("DeleteTile", tile => { SessionData.Map.Remove(tile.ToString(), out var _); });

            Connection.On<Wall>("DeleteWall", tile => { SessionData.Walls.Remove(tile.ToString(), out var _); });

            Connection.On<Note>("DeleteNote", tile => { SessionData.Notes.Remove(tile.ToString(), out var _); });

            Connection.On<Overlay>("DeleteOverlay",
                tile => { SessionData.Overlays.Remove(tile.ToString(), out var _); });

            Connection.On<Tile>("NewTile", tile =>
            {
                SessionData.Map.Remove(tile.ToString(), out var _);
                SessionData.Map.TryAdd(tile.ToString(), tile);
            });

            Connection.On<Room>("NewRoom", room =>
            {
                SessionData.Rooms.Remove(room.ToString(), out var _);
                SessionData.Rooms.TryAdd(room.ToString(), room);
            });

            Connection.On<Line>("NewLine", line =>
            {
                SessionData.Lines.Remove(line.ToString(), out var _);
                SessionData.Lines.TryAdd(line.ToString(), line);
            });

            Connection.On("RefreshPlayers", () =>
            {
                if (!string.IsNullOrWhiteSpace(State.Instance.MapName))
                    Connection?.SendAsync("UpdatePosition", State.Instance.MapName, State.Instance.MapId,
                        SessionData.Players.First(p => p.UserId == new Guid(_authenticateResponse.Id)));
            });

            Connection.On<Player>("RemovePlayer", player =>
            {
                var p = SessionData.Players.FirstOrDefault(m => m.UserId == player.UserId);
                if (p != null) SessionData.Players.Remove(p);
            });

            Connection.On<Wall>("NewWall", tile =>
            {
                SessionData.Walls.Remove(tile.ToString(), out var _);
                SessionData.Walls.TryAdd(tile.ToString(), tile);
            });

            Connection.On<Overlay>("NewOverlay", tile =>
            {
                SessionData.Overlays.Remove(tile.ToString(), out var _);
                SessionData.Overlays.TryAdd(tile.ToString(), tile);
            });

            Connection.On<Note>("NewNote", note =>
            {
                //SessionData.Notes.Remove(note.ToString(), out var _);
                SessionData.Notes.AddOrUpdate(note.ToString(), note, (key, oldnote) => note);
            });

            Connection.On<Player>("NewPlayer", player =>
            {
                var p = SessionData.Players.FirstOrDefault(m => m.UserId == player.UserId);
                if (p is null)
                {
                    SessionData.Players.Add(player);
                }
                else
                {
                    p.Color = player.Color;
                    p.Position = player.Position;
                }
            });

            Connection.On<Ping>("Ping", ping => { SessionData.Pings.TryAdd(Guid.NewGuid(), ping); });
        }

        public void Enqueue(BaseMapEntity entity, TileAction action)
        {
            switch (action)
            {
                case TileAction.Add:
                    switch (entity)
                    {
                        case Tile tile:
                            _queue.Enqueue(async () => await Execute(async () =>
                                await Api.NewTile(tile, State.Instance.CampaignId, State.Instance.MapId)
                                    .ConfigureAwait(false)));
                            break;
                        case Overlay overlay:
                            _queue.Enqueue(async () => await Execute(async () =>
                                await Api.NewOverlay(overlay, State.Instance.CampaignId, State.Instance.MapId)
                                    .ConfigureAwait(false)));
                            break;
                        case Wall wall:
                            _queue.Enqueue(async () => await Execute(async () =>
                                await Api.NewWall(wall, State.Instance.CampaignId, State.Instance.MapId)
                                    .ConfigureAwait(false)));
                            break;
                        case Note note:
                            _queue.Enqueue(async () => await Execute(async () =>
                                await Api.NewNote(note, State.Instance.CampaignId, State.Instance.MapId)
                                    .ConfigureAwait(false)));
                            break;
                        case Room room:
                            _queue.Enqueue(async () => await Execute(async () =>
                                await Api.NewRoom(room, State.Instance.CampaignId, State.Instance.MapId)
                                    .ConfigureAwait(false)));
                            break;
                        case Line line:
                            _queue.Enqueue(async () => await Execute(async () =>
                                await Api.NewLine(line, State.Instance.CampaignId, State.Instance.MapId)
                                    .ConfigureAwait(false)));
                            break;
                    }

                    break;

                case TileAction.Delete:
                    switch (entity)
                    {
                        case Tile tile:
                            _queue.Enqueue(async () => await Execute(async () =>
                                await Api.DeleteTile(tile, State.Instance.CampaignId, State.Instance.MapId)
                                    .ConfigureAwait(false)));
                            break;
                        case Overlay overlay:
                            _queue.Enqueue(async () => await Execute(async () =>
                                await Api.DeleteOverlay(overlay, State.Instance.CampaignId, State.Instance.MapId)
                                    .ConfigureAwait(false)));
                            break;
                        case Wall wall:
                            _queue.Enqueue(async () => await Execute(async () =>
                                await Api.DeleteWall(wall, State.Instance.CampaignId, State.Instance.MapId)
                                    .ConfigureAwait(false)));
                            break;
                        case Note note:
                            _queue.Enqueue(async () => await Execute(async () =>
                                await Api.DeleteNote(note, State.Instance.CampaignId, State.Instance.MapId)
                                    .ConfigureAwait(false)));
                            break;
                    }

                    break;
            }
        }

        private async Task Execute(Func<Task> call)
        {
            await Policy
                .Handle<ApiException>(ex => ex.StatusCode == HttpStatusCode.RequestTimeout)
                .Or<HttpRequestException>()
                .RetryForeverAsync()
                //.RetryAsync(Polly.RetrySyntax., async (exception, retryCount) => await Task.Delay(500))
                .ExecuteAsync(async () => await call().ConfigureAwait(false))
                .ConfigureAwait(false);
        }

        private Task<string> GetToken()
        {
            return Task.FromResult(_authenticateResponse?.Token ?? "");
        }

        public async Task<List<Campaign>> GetCampaigns()
        {
            try
            {
                return await _retryPolicy.ExecuteAsync(Api.GetCampaigns);
            }
            catch (Exception e)
            {
                Sentry.SentrySdk.CaptureException(e);
                Messenger.Publish(new ErrorMessage(this, "Error loading campaigns"));
            }

            return null;
        }

        public async Task<List<Session>> GetMaps(Guid campaignId)
        {
            try
            {
                return await _retryPolicy.ExecuteAsync(() => Api.GetMaps(campaignId));
            }
            catch (Exception e)
            {
                Sentry.SentrySdk.CaptureException(e);
                Messenger.Publish(new ErrorMessage(this, "Error loading maps"));
            }

            return null;
        }

        public async Task<List<Player>> GetPlayers(Guid campaignId)
        {
            try
            {
                return await _retryPolicy.ExecuteAsync(() => Api.GetPlayers(campaignId));
            }
            catch (Exception e)
            {
                Sentry.SentrySdk.CaptureException(e);
                Messenger.Publish(new ErrorMessage(this, "Error loading players"));
            }

            return null;
        }

        public async Task<AuthenticateResponse> Login(AuthenticateModel authenticateModel)
        {
            _authenticateResponse = await Policy
                .Handle<ApiException>(ex => ex.StatusCode == HttpStatusCode.RequestTimeout)
                .Or<HttpRequestException>()
                .RetryAsync(3)
                //.RetryAsync(Polly.RetrySyntax., async (exception, retryCount) => await Task.Delay(500))
                .ExecuteAsync(async () => await Api.Authenticate(authenticateModel))
                .ConfigureAwait(false);

            //_authenticateResponse = await Api.Authenticate(authenticateModel).ConfigureAwait(false);
            var data = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(_authenticateResponse));

            Program.helper.SaveUnencryptedTokenCache(JsonSerializer.SerializeToUtf8Bytes(_authenticateResponse));

           

            return _authenticateResponse;
        }

        internal async Task Ping(Tile location)
        {
            if (Connection is { State: HubConnectionState.Connected })
                await Connection.InvokeAsync("Ping", SessionData.SessionId, location);
        }

        public async Task<IMapApi.AuthResult> Register(RegisterModel registerModel)
        {
            var result = await Api.Register(registerModel).ConfigureAwait(false);
            return result;
        }

        public void CheckLogin()
        {
            var data = Program.helper.LoadUnencryptedTokenCache();
            if (data != null && data.Any())
            {
                try
                {
                    _authenticateResponse = JsonSerializer.Deserialize<AuthenticateResponse>(Encoding.UTF8.GetString(data));
                    Messenger.Publish(new LoginSuccesfulMessage(this) { UserName = _authenticateResponse.Username, Initials = _authenticateResponse.Initials });
                }
                catch
                { }
            }
        }
    }
}