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

namespace QtCNETAPI.Services.GatewayService
{
    public class GatewayService : IGatewayService, IAsyncDisposable
    {
        public Room? CurrentRoom { get; private set; }
        public HubConnection? HubConnection { get; private set; }
        public string? GWUri { get; private set; }


        public event EventHandler? OnRoomMessageReceived;
        public event EventHandler? OnRoomUserListReceived;
        public event EventHandler? OnGuestUserJoin;
        public event EventHandler? OnRefreshUserListsReceived;
        public event EventHandler? OnRefreshRoomListReceived;
        public event EventHandler? OnRoomDeleted;
        public event EventHandler? OnRefreshContactsListReceived;
        public event EventHandler? OnClientFunctionReceived;
        public event EventHandler? OnDirectMessageReceived;
        public event EventHandler? OnServerConfigReceived;
        public event EventHandler? OnServerDisconnect;
        public event EventHandler? OnServerReconnecting;
        public event EventHandler? OnServerReconnected;
        public event EventHandler? OnUserForceLogout;

        private IApiService _apiService;
        private LoggingService _loggingService;
        public GatewayService(string gwUri, IApiService apiService, LoggingService loggingService)
        {
            GWUri = gwUri;
            _apiService = apiService;
            _loggingService = loggingService;
        }

        public async Task StartAsync()
        {
            // build connection
            var gwConBuilder = new HubConnectionBuilder()
                .WithAutomaticReconnect()
                .ConfigureLogging((builder) =>
                {
                    builder.AddProvider(new LoggingServiceProvider(_loggingService));

                    if (System.Diagnostics.Debugger.IsAttached) builder.SetMinimumLevel(LogLevel.Debug);
                    else builder.SetMinimumLevel(LogLevel.Error);
                })
                .WithUrl(GWUri!, options =>
                {
                    options.AccessTokenProvider = async () =>
                    {
                        // this should hopefully refresh the session every time the gateway connection is used to prevent connection aborts
                        await _apiService.RefreshSessionIfInvalid();
                        return _apiService.SessionToken;
                    };
                })
                .WithStatefulReconnect();
            HubConnection = gwConBuilder.Build();

            // register events
            HubConnection.On<User, string, Room>("RoomMessage", (user, msg, room) => OnRoomMessageReceived?.Invoke(this, new ServerMessageEventArgs { User = user, Room = room, Message = msg }));
            HubConnection.On<string>("cf", (function) => OnClientFunctionReceived?.Invoke(this, new ClientFunctionEventArgs { Function = function }));
            HubConnection.On<Message, UserInformationDto>("ReceiveDirectMessage", (message, user) => OnDirectMessageReceived?.Invoke(this, new DirectMessageEventArgs { Message = message, User = user }));
            HubConnection.On("RefreshUserLists", () => OnRefreshUserListsReceived?.Invoke(this, EventArgs.Empty));
            HubConnection.On("RefreshRoomList", () => OnRefreshRoomListReceived?.Invoke(this, EventArgs.Empty));
            HubConnection.On("RefreshContactsList", () => OnRefreshContactsListReceived?.Invoke(this, EventArgs.Empty));
            HubConnection.On<ServerConfig>("ReceiveServerConfig", (serverConfig) => OnServerConfigReceived?.Invoke(this, new ServerConfigEventArgs { ServerConfig = serverConfig }));
            HubConnection.On<Room, List<User>>("RoomUserList", (room, userList) => OnRoomUserListReceived?.Invoke(this, new RoomListEventArgs { Room = room, UserList = userList }));
            HubConnection.On<string>("GuestJoin", (username) => OnGuestUserJoin?.Invoke(this, new GuestUserJoinEventArgs { Username  = username }));
            HubConnection.On("RoomDeleted", () => OnRoomDeleted?.Invoke(this, EventArgs.Empty));
            HubConnection.On("ForceSignOut", () => OnUserForceLogout?.Invoke(this, EventArgs.Empty));
            HubConnection.On("UpdateCurrentUser", async () => await _apiService.SetCurrentUser());

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

            // start connection
            try
            {
                await HubConnection.StartAsync();
            }
            catch (HttpRequestException ex)
            {
                _loggingService.LogString($"Unable To Connect To SignalR.\n{ex.Message}\n{ex.StackTrace}");
                return;
            }
        }

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

                HubConnection = null;
                CurrentRoom = null;
            }
        }

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

                HubConnection = null;
                CurrentRoom = null;
            }
        }

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

                HubConnection = null;
                CurrentRoom = null;
            }
        }

        public async Task JoinRoomAsync(Room? room = null)
        {
            if (HubConnection == null || HubConnection.State != HubConnectionState.Connected) throw new InvalidOperationException("Function was called before connection was made.");

            // assume user is trying to join lobby if room is null (does not have db entry)
            room ??= new Room
            {
                Id = "LOBBY",
                Name = "Lobby"
            };

            if (CurrentRoom != null) await HubConnection.SendAsync("LeaveRoom", _apiService.CurrentUser, CurrentRoom);
            await HubConnection.SendAsync("JoinRoom", _apiService.CurrentUser, room);
            CurrentRoom = room;
        }

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

            if (CurrentRoom != null) await HubConnection.SendAsync("LeaveRoom", _apiService.CurrentUser, CurrentRoom);
            CurrentRoom = null;
        }

        public async Task PostMessageAsync(Message message)
        {
            if (HubConnection == null || HubConnection.State != HubConnectionState.Connected) throw new InvalidOperationException("Function was called before connection was made.");

            await HubConnection.SendAsync("SendMessage", _apiService.CurrentUser, message, 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("SendDirectMessage", _apiService.CurrentUser, user, message);
        }

        public async Task UpdateStatus(int status)
        {
            if (HubConnection == null || HubConnection.State != HubConnectionState.Connected) throw new InvalidOperationException("Function was called before connection was made.");

            await HubConnection.SendAsync("UpdateStatus", _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;
        }
    }
}
