using Microsoft.AspNetCore.SignalR.Client;
using Polly;
using Refit;
using Sledgemapper.Shared.Entities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;

namespace Sledgemapper
{
    public class CommunicationManager
    {
        public IMapApi Api { get; private set; }
        public HubConnection Connection { get; private set; }
        public readonly Session SessionData;
        private readonly ChannelsQueue Queue = new();
        private AuthenticateResponse _authenticateResponse;

        public CommunicationManager(Session sessionData)
        {
            SessionData = sessionData;
            Connection = new HubConnectionBuilder()
                                       .WithAutomaticReconnect()

                                       .WithUrl("http://localhost:5000/SledgemapperHub", options =>
    {
        options.AccessTokenProvider = () => Task.FromResult(_authenticateResponse.Token);
    })

                                        //     .WithUrl("http://hub.michelescandura.com:5000/SledgemapperHub")
                                        .Build();



            Api = RestService.For<IMapApi>(
              new HttpClient(new AuthenticatedHttpClientHandler(GetToken))
              {
                  BaseAddress = new Uri("http://localhost:5000")
              }
          );

            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<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("RefreshPlayers", () =>
         {
             Connection?.SendAsync("UpdatePosition", SessionData.SessionName, SessionData.SessionId, SessionData.Players.First(p=>p.UserId==int.Parse(_authenticateResponse.Id)));
         });

            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<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;
                }
            });
        }

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

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

        public async Task<AuthenticateResponse> Login(AuthenticateModel authenticateModel)
        {
            _authenticateResponse = await Api.Authenticate(authenticateModel).ConfigureAwait(false);
            return _authenticateResponse;
        }

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

        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, SessionData.SessionName).ConfigureAwait(false)));
                            break;
                        case Overlay overlay:
                            Queue.Enqueue(async () => await Execute(async () => await Api.NewOverlay(overlay, SessionData.SessionName).ConfigureAwait(false)));
                            break;
                        case Wall wall:
                            Queue.Enqueue(async () => await Execute(async () => await Api.NewWall(wall, SessionData.SessionName).ConfigureAwait(false)));
                            break;
                    }
                    break;

                case TileAction.Delete:
                    switch (entity)
                    {
                        case Tile tile:
                            Queue.Enqueue(async () => await Execute(async () => await Api.DeleteTile(tile, SessionData.SessionName).ConfigureAwait(false)));
                            break;
                        case Overlay overlay:
                            Queue.Enqueue(async () => await Execute(async () => await Api.DeleteOverlay(overlay, SessionData.SessionName).ConfigureAwait(false)));
                            break;
                        case Wall wall:
                            Queue.Enqueue(async () => await Execute(async () => await Api.DeleteWall(wall, SessionData.SessionName).ConfigureAwait(false)));
                            break;
                    }
                    break;
            }

        }
    }

    class AuthenticatedHttpClientHandler : HttpClientHandler
    {
        private readonly Func<Task<string>> getToken;

        public AuthenticatedHttpClientHandler(Func<Task<string>> getToken)
        {
            if (getToken == null) throw new ArgumentNullException(nameof(getToken));
            this.getToken = getToken;

            //if (myConfigurationService.VerifySslCertificate == false)
            //{
            ServerCertificateCustomValidationCallback =
                (message, certificate, chain, sslPolicyErrors) => true;
            //}
        }

        protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            // See if the request has an authorize header
            var auth = request.Headers.Authorization;
            if (auth != null)
            {
                var token = await getToken().ConfigureAwait(false);
                request.Headers.Authorization = new AuthenticationHeaderValue(auth.Scheme, token);
            }

            return await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
        }
    }
}
