using Microsoft.AspNetCore.SignalR.Client;
using QtCNETAPI.Dtos.User;
using QtCNETAPI.Events;
using QtCNETAPI.Models;
using QtCNETAPI.Services.ApiService;

namespace QtCNETAPI.Services.GatewayService
{
    public class GatewayService : IGatewayService
    {
        internal string gwBaseUri = "127.0.0.1";

        public Room? CurrentRoom { get; private set; }
        public bool InLobby { get; private set; }
        public HubConnection? HubConnection { get; private set; }


        public event EventHandler OnServerMessageReceived;
        public event EventHandler OnClientFunctionReceived;
        public event EventHandler OnDirectMessageReceived;
        public event EventHandler OnServerConfigReceived;
        public event EventHandler OnServerDisconnect;
        public event EventHandler OnServerReconnecting;
        public event EventHandler OnServerReconnected;

        private IApiService _apiService;

        public GatewayService(string GWUrl, IApiService apiService)
        {
            gwBaseUri = GWUrl;
            _apiService = apiService;
        }

        public async Task StartAsync()
        {
            // just to be safe (it doesn't load the server since it shouldn't request a new one unless its actually expired)
            await _apiService.RefreshSessionIfInvalid();

            // build connection
            var gwConBuilder = new HubConnectionBuilder()
                .WithAutomaticReconnect()
                .WithUrl(gwBaseUri, options =>
                {
                    options.AccessTokenProvider = () => Task.FromResult(_apiService.SessionToken);
                });
            HubConnection = gwConBuilder.Build();

            // start connection
            await HubConnection.StartAsync();

            // register events
            HubConnection.On<string>("rm", (serverMessage) => OnServerMessageReceived?.Invoke(this, new ServerMessageEventArgs{Message = serverMessage}));
            HubConnection.On<string>("cf", (function) => OnClientFunctionReceived?.Invoke(this, new ClientFunctionEventArgs{Function = function}));
            HubConnection.On<Message, UserInformationDto>("rdm", (message, user) => OnDirectMessageReceived?.Invoke(this, new DirectMessageEventArgs { Message = message, User = user }));
            HubConnection.On<ServerConfig>("rc", (serverConfig) => OnServerConfigReceived?.Invoke(this, new ServerConfigEventArgs{ServerConfig = serverConfig}));

            HubConnection.Closed += HubConnection_Closed;
            HubConnection.Reconnecting += HubConnection_Reconnecting;
            HubConnection.Reconnected += HubConnection_Reconnected;

            if (HubConnection != null && HubConnection.State == HubConnectionState.Connected)
                await HubConnection.SendAsync("l", _apiService.CurrentUser);
        }

        public async Task StopAsync()
        {
            if (HubConnection != null && HubConnection.State == HubConnectionState.Connected)
            {
                await HubConnection.StopAsync();
            }
        }

        public async Task DisposeAsync()
        {
            if (HubConnection != null && HubConnection.State == HubConnectionState.Disconnected)
            {
                await HubConnection.DisposeAsync();

                HubConnection = null;
                CurrentRoom = null;
            }
        }

        public async Task JoinLobbyAsync()
        {
            await _apiService.RefreshSessionIfInvalid();

            if (HubConnection == null || HubConnection.State != HubConnectionState.Connected) throw new InvalidOperationException("Function was called before connection was made.");

            await HubConnection.SendAsync("jl", _apiService.CurrentUser);
            InLobby = true;
            CurrentRoom = null;
        }

        public async Task JoinRoomAsync(Room room)
        {
            await _apiService.RefreshSessionIfInvalid();

            if (HubConnection == null || HubConnection.State != HubConnectionState.Connected) throw new InvalidOperationException("Function was called before connection was made.");

            if (InLobby == true)
            {
                await HubConnection.SendAsync("ll", _apiService.CurrentUser);
                InLobby = false;
            }
            else if (CurrentRoom != null)
            {
                await HubConnection.SendAsync("lr", _apiService.CurrentUser, CurrentRoom);
            }

            await HubConnection.SendAsync("jr", _apiService.CurrentUser, room);
            CurrentRoom = room;
        }

        public async Task LeaveRoomAsync()
        {
            await _apiService.RefreshSessionIfInvalid();

            if (HubConnection == null || HubConnection.State != HubConnectionState.Connected) throw new InvalidOperationException("Function was called before connection was made.");

            if (InLobby)
            {
                await HubConnection.SendAsync("ll", _apiService.CurrentUser);
                InLobby = false;
            }
            else
            {
                await HubConnection.SendAsync("lr", _apiService.CurrentUser, CurrentRoom);
                CurrentRoom = null;
            }
        }

        public async Task RefreshContactsForUser(UserInformationDto user)
        {
            await _apiService.RefreshSessionIfInvalid();

            if (HubConnection == null || HubConnection.State != HubConnectionState.Connected) throw new InvalidOperationException("Function was called before connection was made.");

            await HubConnection.SendAsync("rcl", user, _apiService.CurrentUser);
        }

        public async Task PostMessageAsync(Message message)
        {
            await _apiService.RefreshSessionIfInvalid();

            if (HubConnection == null || HubConnection.State != HubConnectionState.Connected) throw new InvalidOperationException("Function was called before connection was made.");

            await HubConnection.SendAsync("s", _apiService.CurrentUser, message, InLobby, CurrentRoom);
        }

        public async Task SendDirectMessageAsync(UserInformationDto user, Message message)
        {
            await _apiService.RefreshSessionIfInvalid();

            if (HubConnection == null || HubConnection.State != HubConnectionState.Connected) throw new InvalidOperationException("Function was called before connection was made.");

            await HubConnection.SendAsync("sdm", _apiService.CurrentUser, user, message);
        }

        public async Task UpdateStatus(int status)
        {
            await _apiService.RefreshSessionIfInvalid();

            if (HubConnection == null || HubConnection.State != HubConnectionState.Connected) throw new InvalidOperationException("Function was called before connection was made.");

            await HubConnection.SendAsync("us", _apiService.CurrentUser, status);
        }


        private Task HubConnection_Closed(Exception? arg)
        {
            OnServerDisconnect?.Invoke(this, new ServerConnectionClosedEventArgs { Error = arg });
            return Task.CompletedTask;
        }

        private Task HubConnection_Reconnecting(Exception? arg)
        {
            OnServerReconnecting?.Invoke(this, new ServerConnectionReconnectingEventArgs { Error = arg });
            return Task.CompletedTask;
        }

        private Task HubConnection_Reconnected(string? arg)
        {
            OnServerReconnected?.Invoke(this, EventArgs.Empty);
            return Task.CompletedTask;
        }
    }
}
