using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.VisualBasic.ApplicationServices;
using qtc_net_client_2.ClientModel;
using qtc_net_client_2.Controls;
using qtc_net_client_2.Forms;
using qtc_net_client_2.Properties;
using qtc_net_client_2.Services;
using QtCNETAPI.Dtos.User;
using QtCNETAPI.Events;
using QtCNETAPI.Models;
using QtCNETAPI.Schema;
using QtCNETAPI.Services;
using QtCNETAPI.Services.ApiService;
using QtCNETAPI.Services.GatewayService;
using System.Threading.Tasks;

namespace qtc_net_client_2
{
    public partial class Main : Form
    {
        private IApiService _apiService;
        private IGatewayService _gatewayService;

        private Config _config;
        private ServerConfig? _serverConfig;
        private AudioService AudioService = new();
        private ImageFactory _imgFactory = new();
        private CredentialService _credService = new();
        private LoggingService LoggingService;

        public List<Room> RoomList = [];
        public List<UserInformationDto> UserDirectory = [];
        public List<Contact> Contacts = [];

        private bool FirstMinimize = true;
        public Main(IApiService apiService, IGatewayService gatewayService, Config config, LoggingService loggingService)
        {
            _apiService = apiService;
            _gatewayService = gatewayService;
            _config = config;
            LoggingService = loggingService;

            InitializeComponent();
        }

        private async void frmMain_Load(object sender, EventArgs e)
        {
            flpContacts.Padding = new Padding(8);

            LoggingService.LogString("Main Form Loaded");

            // start request notif blink task
            Thread blinkThread = new Thread(async () => await StartRequestNotifBlankLoop(lblRequestNotif));
            blinkThread.Start();

            // check if client is already logged in
            if (_apiService.CurrentUser == null)
            {
                // not logged in, load the login form
                Login frmLogin = new Login(_apiService);
                var result = frmLogin.ShowDialog();

                if (result == DialogResult.OK)
                    await OnSuccessfulLogin();
            }
        }

        private async void llblSignIn_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
        {
            // just reshow the login dialog lol
            Login frmLogin = new Login(_apiService);
            var result = frmLogin.ShowDialog();

            if (result == DialogResult.OK)
                await OnSuccessfulLogin();
        }

        private async void OnRoomDoubleClicked(object? sender, EventArgs e)
        {
            RoomEntryControl? ctrl = sender as RoomEntryControl;

            string? selectedRoom = ctrl?.RoomName;
            if (selectedRoom != null)
            {
                if (selectedRoom == "Lobby")
                {
                    ChatRoom frmChat = new ChatRoom(_gatewayService, _apiService);
                    frmChat.Show();

                    LoggingService.LogString("User Has Joined Lobby Room");
                    return;
                }

                // join the room
                Room? room = RoomList.FirstOrDefault(e => e.Name == selectedRoom);
                if (room != null)
                {
                    ChatRoom frmChat = new ChatRoom(_gatewayService, _apiService, room);
                    frmChat.Show();

                    LoggingService.LogString($"User Has Joined {room.Name}");
                }
            }
        }

        private void pbUserPfp_Click(object sender, EventArgs e)
        {
            var args = (MouseEventArgs)e;
            if (args.Button == MouseButtons.Right) return;

            Thread t = new Thread(new ThreadStart(async () =>
            {
                using (OpenFileDialog openFileDialog = new OpenFileDialog())
                {
                    openFileDialog.InitialDirectory = Environment.CurrentDirectory;
                    openFileDialog.Title = "Select New Profile Picture";
                    openFileDialog.Filter = "Image Files (*.png, *.jpg)|*.png;*.jpg";

                    if (openFileDialog.ShowDialog() == DialogResult.OK)
                    {
                        // upload to server with api endpoint
                        var res = await _apiService.UpdateUserProfilePic(openFileDialog.FileName);

                        if (res.Success)
                        {
                            // update profile pic in ui
                            var pfpRes = await _apiService.GetUserProfilePic(_apiService.CurrentUser.Id);
                            var cosmeticRes = await GetCosmeticImage(_apiService.CurrentUser.ActiveProfileCosmetic);

                            // TODO - refactor this a bit
                            if (pfpRes.Success && pfpRes.Data != null)
                            {
                                using var pfpMS = new MemoryStream(pfpRes.Data);
                                var pfp = (Bitmap)Image.FromStream(pfpMS);

                                if (cosmeticRes != null)
                                {
                                    using var cosmeticMS = new MemoryStream(cosmeticRes);
                                    var cosmetic = (Bitmap)Image.FromStream(cosmeticMS);

                                    pbUserPfp.Image = _imgFactory.CreateProfileImage(null, pfp, cosmetic);
                                }
                                else pbUserPfp.Image = _imgFactory.CreateProfileImage(null, pfp, null);
                            }
                            else if (cosmeticRes != null)
                            {
                                using var cosmeticMS = new MemoryStream(cosmeticRes);
                                var cosmetic = (Bitmap)Image.FromStream(cosmeticMS);

                                pbUserPfp.Image = _imgFactory.CreateProfileImage(null, null, cosmetic);
                            }
                        }
                    }
                }
            }));

            t.SetApartmentState(ApartmentState.STA);
            t.Start();
        }

        private void llblSignOut_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
        {
            if (MessageBox.Show("Are You Sure You Want To Sign Out?\nThis Deletes Your session.token, Requiring You To Sign In Again", "are you sure...?", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.Yes)
            {
                _credService.DeleteAccessToken();
                Environment.Exit(0);
            }
        }

        private async void llblEditProfile_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
        {
            ProfileEdit frmProfileEdit = new ProfileEdit(_apiService);
            var result = frmProfileEdit.ShowDialog();
            if (result == DialogResult.Yes)
            {
                // update profile pic in ui
                var pfpRes = await _apiService.GetUserProfilePic(_apiService.CurrentUser.Id);
                var cosmeticRes = await GetCosmeticImage(_apiService.CurrentUser.ActiveProfileCosmetic);

                // TODO - refactor this a bit
                if (pfpRes.Success && pfpRes.Data != null)
                {
                    using var pfpMS = new MemoryStream(pfpRes.Data);
                    var pfp = (Bitmap)Image.FromStream(pfpMS);

                    if (cosmeticRes != null)
                    {
                        using var cosmeticMS = new MemoryStream(cosmeticRes);
                        var cosmetic = (Bitmap)Image.FromStream(cosmeticMS);

                        pbUserPfp.Image = _imgFactory.CreateProfileImage(null, pfp, cosmetic);
                    }
                    else pbUserPfp.Image = _imgFactory.CreateProfileImage(null, pfp, null);
                }
                else if (cosmeticRes != null)
                {
                    using var cosmeticMS = new MemoryStream(cosmeticRes);
                    var cosmetic = (Bitmap)Image.FromStream(cosmeticMS);

                    pbUserPfp.Image = _imgFactory.CreateProfileImage(null, null, cosmetic);
                }
            }
        }

        private async void frmMain_Resize(object sender, EventArgs e)
        {
            if (WindowState == FormWindowState.Minimized && _config.MinimizeToTray)
            {
                Hide();
                niMain.Visible = true;
                if (FirstMinimize)
                {
                    niMain.ShowBalloonTip(10,
                    "I'm over here!",
                    "QtC.NET Mimimizes To Tray By Default. To Change This Behaviour, Refer To config.json",
                    ToolTipIcon.Info);
                    FirstMinimize = false;
                }
            } else if (WindowState == FormWindowState.Normal)
            {
                // refresh everything ig (TODO - find a more optimized way of doing this)
                await RefreshUsers();
                await RefreshContactsList();
                await RefreshRoomsList();
                await RefreshStore();
            }
        }

        private void niMain_DoubleClick(object sender, EventArgs e)
        {
            Show();
            WindowState = FormWindowState.Normal;
            niMain.Visible = false;
        }

        private async void frmMain_FormClosed(object sender, FormClosedEventArgs e)
        {
            DialogResult = DialogResult.OK;
            niMain.Visible = false;

            // ensure the gateway stops the connection and disposes properly
            await _gatewayService.StopAsync();
            await _gatewayService.DisposeAsync();

            Environment.Exit(0);
        }

        private async void ctxmChangeStatus_ItemClicked(object sender, ToolStripItemClickedEventArgs e)
        {
            if (e.ClickedItem != null)
            {
                UserStatus userStatus = UserStatus.Unknown;
                switch (e.ClickedItem.Name)
                {
                    case "onlineToolStripMenuItem":
                        userStatus = UserStatus.Online;
                        onlineToolStripMenuItem.Checked = true;
                        awayToolStripMenuItem.Checked = false;
                        doNotDisturbToolStripMenuItem.Checked = false;
                        invisibleToolStripMenuItem.Checked = false;
                        break;
                    case "awayToolStripMenuItem":
                        userStatus = UserStatus.Away;
                        onlineToolStripMenuItem.Checked = false;
                        awayToolStripMenuItem.Checked = true;
                        doNotDisturbToolStripMenuItem.Checked = false;
                        invisibleToolStripMenuItem.Checked = false;
                        break;
                    case "doNotDisturbToolStripMenuItem":
                        userStatus = UserStatus.DoNotDisturb;
                        onlineToolStripMenuItem.Checked = false;
                        awayToolStripMenuItem.Checked = false;
                        doNotDisturbToolStripMenuItem.Checked = true;
                        invisibleToolStripMenuItem.Checked = false;
                        break;
                    case "invisibleToolStripMenuItem":
                        userStatus = UserStatus.Offline;
                        onlineToolStripMenuItem.Checked = false;
                        awayToolStripMenuItem.Checked = false;
                        doNotDisturbToolStripMenuItem.Checked = false;
                        invisibleToolStripMenuItem.Checked = true;
                        break;
                }

                // set it
                await _gatewayService.UpdateStatus((int)userStatus);
            }
        }

        private void btnAddRoom_Click(object sender, EventArgs e)
        {
            CreateRoom createRoom = new CreateRoom(_apiService);
            createRoom.ShowDialog();
        }

        private async void llblClaimSpin_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
        {
            CurrencyJackpotSpinner tokenJackpotSpinner = new CurrencyJackpotSpinner();
            var frmResult = tokenJackpotSpinner.ShowDialog();

            if (frmResult == DialogResult.OK && tokenJackpotSpinner.TokensWon > 0)
            {
                // claim
                var result = await _apiService.AddCurrencyToCurrentUser(tokenJackpotSpinner.TokensWon, true);
                if (result.Success)
                {
                    lblCurrencyAmount.Text = _apiService.CurrentUser.CurrencyAmount.ToString("N0");
                    llblClaimSpin.Visible = false;
                }
                else MessageBox.Show("We Were Unable To Claim Your Prize At This Time. Please Try Again Later.", "Uh Oh.", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        private async void lbUserDirectory_DoubleClick(object sender, EventArgs e)
        {
            if (lvUserDirectory.SelectedItems.Count > 0)
            {
                string? selectedUser = (string?)lvUserDirectory.SelectedItems[lvUserDirectory.SelectedItems.Count - 1].Text;

                if (selectedUser != null)
                {
                    // get user info and open profile dialog
                    var user = UserDirectory.FirstOrDefault(e => e.Username == selectedUser);
                    var res = await _apiService.GetUserInformationAsync(user!.Id);
                    var pfpRes = await _apiService.GetUserProfilePic(user!.Id);

                    // get cosmetic
                    byte[]? cosmeticData = null;
                    if (user.ProfileCosmeticId != 0) cosmeticData = await GetCosmeticImage(user.ProfileCosmeticId);

                    if (pfpRes != null && !pfpRes.Success) LoggingService.LogString($"User Has No Profile Picture Or It Could Not Be Loaded.\n{pfpRes.Message}");

                    if (res.Data != null && res.Success)
                    {
                        LoggingService.LogString($"Opening Profile For User '{res.Data.Username}'");
                        Profile frmProfile = new Profile(res.Data, pfpRes, Contacts, _apiService, _gatewayService, cosmeticData);
                        frmProfile.Show();
                    }
                }
            }
        }

        private void lvGames_DoubleClick(object sender, EventArgs e)
        {
            if (lvGames.SelectedItems.Count > 0)
            {
                string? gameSelected = (string?)lvGames.SelectedItems[lvGames.SelectedItems.Count - 1].Tag;

                switch (gameSelected)
                {
                    case "StockMarketGame":
                        StockMarketGame stockMarketGame = new StockMarketGame(_apiService);
                        stockMarketGame.Show();
                        break;
                    case "GuessTheNumberGame":
                        GuessTheNumber guessTheNumber = new GuessTheNumber(_apiService);
                        guessTheNumber.Show();
                        break;
                    case "TicTacToeGame":
                        TicTacToeGame ticTacToeGame = new TicTacToeGame(_apiService, _config);
                        ticTacToeGame.Show();
                        break;
                }
            }
        }

        private async void refreshToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (tbcMain.SelectedIndex == 0) await RefreshContactsList();
            if (tbcMain.SelectedIndex == 1) await RefreshRoomsList();
            if (tbcMain.SelectedIndex == 2) await RefreshUsers();
        }

        private void pbDonate_Click(object sender, EventArgs e)
        {
            DonationWindow donationWindow = new DonationWindow();
            donationWindow.Show();
        }

        private async Task RefreshStore()
        {
            // get store items
            var storeItems = await _apiService.GetStoreItems();
            if (storeItems != null && storeItems.Success && storeItems.Data != null)
            {
                if (lvStoreItems.Items.Count == storeItems.Data.Count) return;

                if (!IsHandleCreated || IsDisposed)
                    return;

                if (InvokeRequired)
                {
                    Invoke(() => ApplyStoreItems(storeItems.Data));
                }
                else
                {
                    ApplyStoreItems(storeItems.Data);
                }
            }
        }

        private async void ApplyStoreItems(IEnumerable<StoreItem> storeItems)
        {
            ilStoreThumbnails.Images.Clear();
            lvStoreItems.Items.Clear();

            var items = await BuildStoreListViewItemsAsync(storeItems);

            lvStoreItems.BeginUpdate();
            try
            {
                lvStoreItems.Items.AddRange([.. items]);
            }
            finally
            {
                lvStoreItems.EndUpdate();
            }
        }

        private async Task<List<ListViewItem>> BuildStoreListViewItemsAsync(IEnumerable<StoreItem> storeItems)
        {
            var items = new List<ListViewItem>();

            foreach (var item in storeItems)
            {
                await GetAndAddStoreThumbnail(item);

                var lvItem = new ListViewItem
                {
                    Text = item.Name,
                    Name = item.Id.ToString(),
                    ImageKey = item.Id.ToString()
                };

                items.Add(lvItem);
            }

            return items;
        }

        private async void lvStoreItems_DoubleClick(object sender, EventArgs e)
        {
            if (lvStoreItems.SelectedItems.Count > 0)
            {
                string? itemSelected = (string?)lvStoreItems.SelectedItems[lvStoreItems.SelectedItems.Count - 1].Name;
                if (itemSelected != null)
                {
                    // get item
                    var item = await _apiService.GetStoreItem(int.Parse(itemSelected));
                    if (item != null && item.Success && item.Data != null)
                    {
                        StoreItemDisplay storeItemDisplay = new StoreItemDisplay(item.Data, LoggingService, _apiService);
                        var result = storeItemDisplay.ShowDialog();
                        if (result == DialogResult.Yes)
                        {
                            // update pfp in ui
                            var pfpRes = await _apiService.GetUserProfilePic(_apiService.CurrentUser.Id);
                            var cosmeticRes = await GetCosmeticImage(_apiService.CurrentUser.ActiveProfileCosmetic);

                            // TODO - refactor this a bit
                            if (pfpRes.Success && pfpRes.Data != null)
                            {
                                using var pfpMS = new MemoryStream(pfpRes.Data);
                                var pfp = (Bitmap)Image.FromStream(pfpMS);

                                if (cosmeticRes != null)
                                {
                                    using var cosmeticMS = new MemoryStream(cosmeticRes);
                                    var cosmetic = (Bitmap)Image.FromStream(cosmeticMS);

                                    pbUserPfp.Image = _imgFactory.CreateProfileImage(null, pfp, cosmetic);
                                }
                                else pbUserPfp.Image = _imgFactory.CreateProfileImage(null, pfp, null);
                            }
                            else if (cosmeticRes != null)
                            {
                                using var cosmeticMS = new MemoryStream(cosmeticRes);
                                var cosmetic = (Bitmap)Image.FromStream(cosmeticMS);

                                pbUserPfp.Image = _imgFactory.CreateProfileImage(null, null, cosmetic);
                            }
                        }
                    }
                }
            }
        }

        private void addRoomToolStripMenuItem_Click(object sender, EventArgs e)
        {
            CreateRoom createRoom = new CreateRoom(_apiService);
            createRoom.ShowDialog();
        }

        // TODO - redo this
        //private void ctxmAdminRoomList_Opening(object sender, System.ComponentModel.CancelEventArgs e)
        //{
        //    if (lbRooms.SelectedItem == null)
        //        deleteRoomToolStripMenuItem.Enabled = false;
        //    else deleteRoomToolStripMenuItem.Enabled = true;
        //}

        private void ctxmAdminUserList_Opening(object sender, System.ComponentModel.CancelEventArgs e)
        {
            if (lvUserDirectory.SelectedItems.Count > 0)
            {
                string? lvUserDirectoryItemSelected = (string?)lvUserDirectory.SelectedItems[lvUserDirectory.SelectedItems.Count - 1].Text;
                if (lvUserDirectoryItemSelected != null && lvUserDirectoryItemSelected == _apiService.CurrentUser.Username)
                {
                    deleteUserToolStripMenuItem.Enabled = false;
                    adminDirectMessageToolStripMenuItem.Enabled = false;
                    copyUserIDToClipboardToolStripMenuItem.Enabled = true;
                    return;
                }
            }
            else
            {
                deleteUserToolStripMenuItem.Enabled = false;
                adminDirectMessageToolStripMenuItem.Enabled = false;
                copyUserIDToClipboardToolStripMenuItem.Enabled = false;
                return;
            }

            deleteUserToolStripMenuItem.Enabled = true;
            adminDirectMessageToolStripMenuItem.Enabled = true;
            copyUserIDToClipboardToolStripMenuItem.Enabled = true;
        }

        private void copyUserIDToClipboardToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (lvUserDirectory.SelectedItems.Count > 0)
            {
                string? itemSelected = (string?)lvUserDirectory.SelectedItems[lvUserDirectory.SelectedItems.Count - 1].Text;
                if (itemSelected != null)
                {
                    // get user
                    var user = UserDirectory.FirstOrDefault(e => e.Username == itemSelected);
                    if (user != null)
                    {
                        // clipboard requires STA apartment state
                        Thread thread = new(delegate () { Clipboard.SetText(user.Id); });
                        thread.SetApartmentState(ApartmentState.STA);
                        thread.Start();

                        MessageBox.Show("Copied!");
                    }
                }
            }
        }

        // TODO - i have no idea on how to mark a control as selected
        //private async void deleteRoomToolStripMenuItem_Click(object sender, EventArgs e)
        //{
        //    if (lbRooms.SelectedItems.Count > 0)
        //    {
        //        string? itemSelected = (string?)lbRooms.SelectedItems[lbRooms.SelectedItems.Count - 1];
        //        if (itemSelected != null)
        //        {
        //            var dialogResult = MessageBox.Show("Are You Sure You Want To Delete This Room?\nThis will kick everyone currently in the room out.",
        //                "are you sure..?",
        //                MessageBoxButtons.YesNo,
        //                MessageBoxIcon.Question);

        //            if (dialogResult == DialogResult.Yes)
        //            {
        //                // get the room
        //                var room = RoomList.FirstOrDefault(e => e.Name == itemSelected);
        //                if (room != null)
        //                {
        //                    var apiResult = await _apiService.DeleteRoomAsync(room.Id);
        //                    if (apiResult != null && apiResult.Success)
        //                        MessageBox.Show("Deleted!");
        //                    else
        //                        MessageBox.Show("There was an error deleting the room. Try Again?", "", MessageBoxButtons.OK, MessageBoxIcon.Error);
        //                }
        //                else MessageBox.Show("This room is unknown. It may have already been deleted.");
        //            }
        //        }
        //    }
        //}

        private async void deleteUserToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (lvUserDirectory.SelectedItems.Count > 0)
            {
                string? itemSelected = (string?)lvUserDirectory.SelectedItems[lvUserDirectory.SelectedItems.Count - 1].Text;
                if (itemSelected != null)
                {
                    var dialogResult = MessageBox.Show("Are You Sure You Want To Delete This User?\n" +
                        "This should only be done as a last resort. You should take the proper percautions to ensure the user cannot make a new account.\n" +
                        "Proper moderation tools are coming soon.\n" +
                        "Doing this will also log out the user if they are logged in.",
                        "are you sure...?",
                        MessageBoxButtons.YesNo,
                        MessageBoxIcon.Warning);

                    if (dialogResult == DialogResult.Yes)
                    {
                        // get the user
                        var user = UserDirectory.FirstOrDefault(e => e.Username == itemSelected);
                        if (user != null)
                        {
                            var apiResult = await _apiService.DeleteUserById(user.Id);
                            if (apiResult != null && apiResult.Success)
                                MessageBox.Show("User Deleted!");
                            else
                                MessageBox.Show("There was an error deleting the user. Try Again?", "", MessageBoxButtons.OK, MessageBoxIcon.Error);
                        }
                        else MessageBox.Show("This user is unknown. They may have already been deleted.");
                    }
                }
            }
        }

        private async void adminDirectMessageToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (lvUserDirectory.SelectedItems.Count > 0)
            {
                string? itemSelected = (string?)lvUserDirectory.SelectedItems[lvUserDirectory.SelectedItems.Count - 1].Text;
                if (itemSelected != null)
                {
                    // get the user
                    var user = UserDirectory.FirstOrDefault(e => e.Username == itemSelected);
                    if (user != null)
                    {
                        var userDto = await _apiService.GetUserInformationAsync(user.Id);
                        if(userDto != null && userDto.Success && userDto.Data != null)
                        {
                            // immediately start a dm chat with the user, the user will be informed immediately if the user is an admin
                            DirectMessage directMessage = new DirectMessage(_gatewayService, _apiService, userDto.Data, null);
                            directMessage.Show();
                        }
                    }
                }
            }
        }

        private async Task OnSuccessfulLogin()
        {
            // double check
            if (_apiService.CurrentUser != null && _apiService.SessionToken != null)
            {
                LoggingService.LogString($"Logged In As '{_apiService.CurrentUser.Username}'");
                LoggingService.LogString("Starting SignalR Connection...");

                // subscribe to gateway events
                _gatewayService.OnServerReconnecting += _gatewayService_OnServerReconnecting;
                _gatewayService.OnServerReconnected += _gatewayService_OnServerReconnected;
                _gatewayService.OnServerDisconnect += _gatewayService_OnServerDisconnect;
                _gatewayService.OnDirectMessageReceived += _gatewayService_OnDirectMessageReceived;
                _gatewayService.OnRefreshUserListsReceived += _gatewayService_OnRefreshUserListReceived;
                _gatewayService.OnRefreshRoomListReceived += _gatewayService_OnRefreshRoomListReceived;
                _gatewayService.OnRefreshContactsListReceived += _gatewayService_OnRefreshContactsListReceived;
                _gatewayService.OnServerConfigReceived += _gatewayService_OnServerConfigReceived;
                _gatewayService.OnUserForceLogout += _gatewayService_OnUserForceLogout;

                _apiService.OnCurrentUserUpdate += _apiService_OnCurrentUserUpdate;

                // start gateway connection
                await _gatewayService.StartAsync();

                if (_gatewayService.HubConnection != null && _gatewayService.HubConnection.State == Microsoft.AspNetCore.SignalR.Client.HubConnectionState.Connected)
                {
                    LoggingService.LogString("Connected To SignalR Succesfully.");
                    LoggingService.LogString("Building UI...");

                    // we are fully logged in, get current user profile pic and set up ui
                    llblSignIn.Visible = false;
                    lblWelcome.Text = $"Welcome, {_apiService.CurrentUser.Username}";
                    lblCurrencyAmount.Visible = true;
                    pbCurrencyIcon.Visible = true;
                    lblCurrencyAmount.Text = _apiService.CurrentUser.CurrencyAmount.ToString("N0");
                    llblSignOut.Visible = true;
                    llblEditProfile.Visible = true;
                    tbcMain.Enabled = true;

                    await RefreshStore();

                    var pfpRes = await _apiService.GetUserProfilePic(_apiService.CurrentUser.Id);
                    var cosmeticRes = await GetCosmeticImage(_apiService.CurrentUser.ActiveProfileCosmetic);

                    // TODO - refactor this a bit
                    if (pfpRes.Success && pfpRes.Data != null)
                    {
                        using var pfpMS = new MemoryStream(pfpRes.Data);
                        var pfp = (Bitmap)Image.FromStream(pfpMS);

                        if (cosmeticRes != null)
                        {
                            using var cosmeticMS = new MemoryStream(cosmeticRes);
                            var cosmetic = (Bitmap)Image.FromStream(cosmeticMS);

                            pbUserPfp.Image = _imgFactory.CreateProfileImage(null, pfp, cosmetic);
                        }
                        else pbUserPfp.Image = _imgFactory.CreateProfileImage(null, pfp, null);
                    }
                    else if (cosmeticRes != null)
                    {
                        using var cosmeticMS = new MemoryStream(cosmeticRes);
                        var cosmetic = (Bitmap)Image.FromStream(cosmeticMS);

                        pbUserPfp.Image = _imgFactory.CreateProfileImage(null, null, cosmetic);
                    }

                    // set status context menu checked
                    // TODO - figure out more efficient way to do this
                    UserStatus cuStatus = (UserStatus)_apiService.CurrentUser.Status;
                    switch (cuStatus)
                    {
                        case UserStatus.Online:
                            onlineToolStripMenuItem.Checked = true;
                            awayToolStripMenuItem.Checked = false;
                            doNotDisturbToolStripMenuItem.Checked = false;
                            invisibleToolStripMenuItem.Checked = false;
                            break;
                        case UserStatus.Away:
                            onlineToolStripMenuItem.Checked = false;
                            awayToolStripMenuItem.Checked = true;
                            doNotDisturbToolStripMenuItem.Checked = false;
                            invisibleToolStripMenuItem.Checked = false;
                            break;
                        case UserStatus.DoNotDisturb:
                            onlineToolStripMenuItem.Checked = false;
                            awayToolStripMenuItem.Checked = false;
                            doNotDisturbToolStripMenuItem.Checked = true;
                            invisibleToolStripMenuItem.Checked = false;
                            break;
                        case UserStatus.Offline:
                            onlineToolStripMenuItem.Checked = false;
                            awayToolStripMenuItem.Checked = false;
                            doNotDisturbToolStripMenuItem.Checked = false;
                            invisibleToolStripMenuItem.Checked = true;
                            break;
                    }

                    var current = DateTime.UtcNow;
                    if (current > _apiService.CurrentUser.LastCurrencySpin.ToUniversalTime() || _apiService.CurrentUser.LastCurrencySpin == new DateTime()) llblClaimSpin.Visible = true;
                    else llblClaimSpin.Visible = false;

                    if (_config.StartMinimized) WindowState = FormWindowState.Minimized;

                    if (_apiService.CurrentUser.Role == "Admin")
                    {
                        LoggingService.LogString("Current User Is An Admin. Using Admin Context Menu Strips...");
                        lvUserDirectory.ContextMenuStrip = ctxmAdminUserList;
                        flpRooms.ContextMenuStrip = ctxmAdminRoomList;
                    }

                    LoggingService.LogString("Client Ready");
                }
            }
        }

        private async Task RefreshUsers()
        {
            LoggingService.LogString("Refreshing All Users List...");

            if (IsHandleCreated && !IsDisposed)
            {
                await Invoke(async delegate ()
                {
                    // get all users on server
                    var userList = await _apiService.GetAllUsersAsync();

                    if (userList != null && userList.Success && userList.Data != null)
                    {
                        // clear the list
                        lvUserDirectory.Items.Clear();
                        UserDirectory.Clear();

                        // populate list
                        foreach (var user in userList.Data)
                        {
                            lvUserDirectory.Items.Add(user.Username, user.Status);
                            UserDirectory.Add(user);
                        }

                        if (System.Diagnostics.Debugger.IsAttached || _config.EnableDebugLogs)
                            LoggingService.LogModel(userList.Data);
                    }
                });
            }
        }

        private readonly SemaphoreSlim _roomsRefreshLock = new(1, 1);
        private async Task RefreshRoomsList()
        {
            LoggingService.LogString("Refreshing Rooms List...");

            if (IsHandleCreated && !IsDisposed)
            {
                if (!await _roomsRefreshLock.WaitAsync(0))
                    return;

                try
                {
                    var roomsRes = await _apiService.GetAllRoomsAsync();

                    if (!roomsRes.Success || roomsRes.Data == null)
                        return;

                    roomsRes.Data.Add(new Room
                    {
                        Id = "LOBBY",
                        Name = "Lobby"
                    });

                    var uniqueRooms = roomsRes.Data
                        .GroupBy(r => r.Id)
                        .Select(g => g.First())
                        .ToList();

                    if (InvokeRequired)
                    {
                        Invoke(() => ApplyRooms(uniqueRooms));
                    }
                    else
                    {
                        ApplyRooms(uniqueRooms);
                    }
                }
                finally
                {
                    _roomsRefreshLock.Release();
                }
            }
        }

        private void ApplyRooms(List<Room> newRooms)
        {
            RoomList.Clear();
            RoomList.AddRange(newRooms.DistinctBy(r => r.Id));

            var roomsSnap = RoomList.ToList();

            flpRooms.SuspendLayout();
            flpRooms.Controls.Clear();

            foreach (var room in roomsSnap)
            {
                var ctrl = new RoomEntryControl
                {
                    RoomName = room.Name,
                    RoomUserCount = room.UserCount,
                };

                if(room.Id == "LOBBY") 
                    ctrl.HideUserCount = true; // TODO - probably make this work with the lobby room (might need backend work)

                ctrl.Margin = new Padding(0, 4, 0, 4);
                ctrl.Width = flpRooms.ClientSize.Width - flpRooms.Padding.Horizontal;
                ctrl.OnRoomDoubleClicked += OnRoomDoubleClicked;

                flpRooms.Controls.Add(ctrl);
            }

            flpRooms.ResumeLayout(true);
        }

        private readonly SemaphoreSlim _contactsRefreshLock = new(1, 1);
        private async Task RefreshContactsList()
        {
            if (!await _contactsRefreshLock.WaitAsync(0))
                return; // already refreshing, skip

            try
            {
                var contactsRes = await _apiService.GetCurrentUserContacts();
                if (!contactsRes.Success || contactsRes.Data == null)
                    return;

                if (InvokeRequired)
                    Invoke(() => ApplyContacts(contactsRes.Data));
                else
                    ApplyContacts(contactsRes.Data);
            }
            finally
            {
                _contactsRefreshLock.Release();
            }
        }

        private async void ApplyContacts(List<Contact> newContacts)
        {
            lblRequestNotif.Visible =
                newContacts.Any(e =>
                    e.UserId == _apiService.CurrentUser!.Id &&
                    e.UserStatus == Contact.ContactStatus.AwaitingApprovalFromSelf);

            Contacts.Clear();
            Contacts.AddRange(newContacts.DistinctBy(c => c.Id));

            flpContacts.SuspendLayout();
            flpContacts.Controls.Clear();

            var contactsSnap = Contacts.ToList();

            foreach (var contact in contactsSnap)
            {
                var ctrl = await BuildContactControl(contact);
                if (ctrl != null)
                    flpContacts.Controls.Add(ctrl);
            }

            flpContacts.ResumeLayout(true);
        }

        private async Task<ContactEntryControl?> BuildContactControl(Contact contact)
        {
            ServiceResponse<UserInformationDto> user = null!;
            if (contact.OwnerId == _apiService.CurrentUser!.Id)
                user = await _apiService.GetUserInformationAsync(contact.UserId);
            else if (contact.UserId == _apiService.CurrentUser!.Id)
                user = await _apiService.GetUserInformationAsync(contact.OwnerId);

            var ctrl = new ContactEntryControl();

            if (user.Data != null)
            {
                switch (user.Data.Status)
                {
                    case 0:
                        ctrl.StatusColor = Color.Gray;
                        break;
                    case 1:
                        ctrl.StatusColor = Color.LightGreen;
                        break;
                    case 2:
                        ctrl.StatusColor = Color.Gold;
                        break;
                    case 3:
                        ctrl.StatusColor = Color.Red;
                        break;
                }

                if (contact.OwnerId == _apiService.CurrentUser!.Id)
                {
                    switch (contact.OwnerStatus)
                    {
                        case Contact.ContactStatus.AwaitingApprovalFromOther:
                            ctrl.Username = $"{user.Data.Username} [Request Sent]";
                            await AddProfilePicToList(user.Data.Id);
                            ctrl.Avatar = ilProfilePics.Images[user.Data.Id] ?? ilProfilePics.Images[0];
                            break;
                        case Contact.ContactStatus.Accepted:
                            ctrl.Username = user.Data.Username;
                            await AddProfilePicToList(user.Data.Id);
                            ctrl.Avatar = ilProfilePics.Images[user.Data.Id] ?? ilProfilePics.Images[0];
                            break;
                    }
                }
                else if (contact.UserId == _apiService.CurrentUser!.Id)
                {
                    switch (contact.UserStatus)
                    {
                        case Contact.ContactStatus.AwaitingApprovalFromSelf:
                            ctrl.Username = $"{user.Data.Username} [Contact Request]";
                            await AddProfilePicToList(user.Data.Id);
                            ctrl.Avatar = ilProfilePics.Images[user.Data.Id] ?? ilProfilePics.Images[0];
                            AudioService.PlaySoundEffect("sndContactRequest");
                            break;
                        case Contact.ContactStatus.Accepted:
                            ctrl.Username = user.Data.Username;
                            await AddProfilePicToList(user.Data.Id);
                            ctrl.Avatar = ilProfilePics.Images[user.Data.Id] ?? ilProfilePics.Images[0];
                            break;
                    }
                }

                // add the control to the flow panel
                ctrl.Margin = new Padding(0, 4, 0, 4);
                ctrl.Width = flpContacts.ClientSize.Width - flpContacts.Padding.Horizontal;
                ctrl.ContactDoubleClicked += Ctrl_ContactDoubleClicked;

                // return the control
                return ctrl;
            }
            else return default;
        }

        private async void Ctrl_ContactDoubleClicked(object? sender, EventArgs e)
        {
            var ctrl = sender as ContactEntryControl;

            string? selectedUser = ctrl?.Username;

            if (selectedUser != null)
            {
                // split from [ if it exists
                if (selectedUser.Contains('[')) selectedUser = selectedUser.Split('[', options: StringSplitOptions.TrimEntries)[0];

                // get user info and open profile dialog
                var user = UserDirectory.FirstOrDefault(e => e.Username == selectedUser);
                var res = await _apiService.GetUserInformationAsync(user!.Id);
                var pfpRes = await _apiService.GetUserProfilePic(user!.Id);

                // get cosmetic
                byte[]? cosmeticData = null;
                if (user.ProfileCosmeticId != 0)
                {
                    var storeRes = await _apiService.GetStoreItem(user.ProfileCosmeticId);
                    if (storeRes != null && storeRes.Success && storeRes.Data != null)
                    {
                        using var client = new HttpClient();
                        using var response = await client.GetAsync(storeRes.Data.AssetUrl);
                        if (response.IsSuccessStatusCode)
                        {
                            cosmeticData = await response.Content.ReadAsByteArrayAsync();
                        }
                        else LoggingService.LogString($"Could Not Get User Cosmetic.\nStatus Code: {response.StatusCode}");
                    }
                }

                if (pfpRes != null && !pfpRes.Success) LoggingService.LogString($"User Has No Profile Picture Or It Could Not Be Loaded.\n{pfpRes.Message}");

                if (res.Data != null && res.Success)
                {
                    LoggingService.LogString($"Opening Profile For User '{res.Data.Username}'");
                    Profile frmProfile = new Profile(res.Data, pfpRes, Contacts, _apiService, _gatewayService, cosmeticData);
                    frmProfile.Show();
                }
            }
        }

        private async Task StartRequestNotifBlankLoop(Label label)
        {
            while (true)
            {
                if(IsHandleCreated && !IsDisposed)
                {
                    await Invoke(async delegate ()
                    {
                        label.ForeColor = Color.Red;
                        await Task.Delay(500);
                        label.ForeColor = Color.Blue;
                        await Task.Delay(500);
                    });
                }
            }
        }

        private async Task AddProfilePicToList(string userId)
        {
            var result = await _apiService.GetUserProfilePic(userId);
            if (result != null && result.Success && result.Data != null)
            {
                using (var ms = new MemoryStream(result.Data))
                {
                    ilProfilePics.Images.Add(userId, Image.FromStream(ms));
                    ms.Dispose();
                }
            }
            else if (result != null) LoggingService.LogString($"User Has No Profile Picture Or It Could Not Be Loaded.\n{result.Message}");
        }

        private async Task GetAndAddStoreThumbnail(StoreItem item)
        {
            try
            {
                using HttpClient client = new();
                var response = await client.GetAsync(item.ThumbnailUrl);
                if (response != null && response.IsSuccessStatusCode)
                {
                    using var stream = await response.Content.ReadAsStreamAsync();
                    Image image = Image.FromStream(stream);
                    stream.Dispose();
                    ilStoreThumbnails.Images.Add(item.Id.ToString(), image);
                }
                else if (response != null) LoggingService.LogString($"Store Item Thumbnail Could Not Be Loaded Due To Status Code {response.StatusCode}");
                else LoggingService.LogString("Store Item Thumbnail Could Not Be Loaded");
                client.Dispose();
            }
            catch (Exception ex)
            {
                LoggingService.LogString("Store Item Thumbnail Could Not Be Loaded\n" + ex.Message);
            }
        }

        private async Task<byte[]?> GetCosmeticImage(int profileCosmeticId)
        {
            if (profileCosmeticId <= 0) return null;

            var storeRes = await _apiService.GetStoreItem(profileCosmeticId);
            if (storeRes != null && storeRes.Success && storeRes.Data != null)
            {
                using var client = new HttpClient();
                using var response = await client.GetAsync(storeRes.Data.AssetUrl);
                if (response.IsSuccessStatusCode)
                {
                    return await response.Content.ReadAsByteArrayAsync();
                }
                else LoggingService.LogString($"Could Not Get User Cosmetic.\nStatus Code: {response.StatusCode}");
            }

            return null;
        }

        public void RefreshCurrencyCounter()
        {
            if (IsHandleCreated && !IsDisposed)
            {
                Invoke(delegate () { lblCurrencyAmount.Text = _apiService.CurrentUser.CurrencyAmount.ToString("N0"); });
            }
        }

        private async void _gatewayService_OnServerDisconnect(object? sender, EventArgs e)
        {
            LoggingService.LogString("Disconnected From SignalR");

            if (DialogResult == DialogResult.OK) return;

            var args = (ServerConnectionClosedEventArgs)e;
            string? error = string.Empty;
            if (args.Error != null) { error = args.Error.Message; LoggingService.LogString($"{args.Error.Message}\n{args.Error.StackTrace}"); }

            // disconnect can sometimes be caused by an expired JWT token, attempt connection restart
            await _gatewayService.StopAsync();
            await _gatewayService.StartAsync();

            if (_gatewayService.HubConnection != null && _gatewayService.HubConnection.State == Microsoft.AspNetCore.SignalR.Client.HubConnectionState.Connected)
            { BeginInvoke(delegate () { tbcMain.Enabled = true; lblConnectionLost.Visible = false; }); return; }

            ConnectionClosed frmConnectionClosed = new ConnectionClosed();
            var result = frmConnectionClosed.ShowDialog();

            if (result == DialogResult.OK)
            {
            // tell the gateway service to attempt reconnection
            Reconnect:
                if (_gatewayService.HubConnection != null)
                {
                    await _gatewayService.StopAsync();
                    await _gatewayService.StartAsync();

                    if (_gatewayService.HubConnection.State == Microsoft.AspNetCore.SignalR.Client.HubConnectionState.Connected)
                    {
                        Invoke(delegate () { tbcMain.Enabled = true; lblConnectionLost.Visible = false; });
                    }
                    else
                    {
                        // reshow the dialog
                        frmConnectionClosed.StatusLabel.Text = "Couldn't Reconnect. The Server Could Be Down.";
                        var result1 = frmConnectionClosed.ShowDialog();

                        if (result1 == DialogResult.OK) goto Reconnect;
                        else Environment.Exit(0);
                    }
                }
            }
            else Environment.Exit(0);
        }

        private void _gatewayService_OnServerReconnecting(object? sender, EventArgs e)
        {
            var args = (ServerConnectionReconnectingEventArgs)e;

            if (args.Error == null)
                LoggingService.LogString("Server Requested Reconnect. Reconnecting...");
            else
                LoggingService.LogString($"SignalR Reconnecting Due To An Error.\n{args.Error.Message}\n{args.Error.StackTrace}");

            if (IsHandleCreated && !IsDisposed)
            {
                Invoke(delegate ()
                {
                    tbcMain.Enabled = false;
                    lblConnectionLost.Visible = true;
                });
            }
        }

        private async void _gatewayService_OnServerReconnected(object? sender, EventArgs e)
        {
            LoggingService.LogString("SignalR Reconnected");

            if (IsHandleCreated && !IsDisposed)
            {
                Invoke(delegate ()
                {
                    tbcMain.Enabled = true;
                    lblConnectionLost.Visible = false;
                });
            }

            // ensure status is set to whatever the current user was set to
            try
            {
                await _gatewayService.UpdateStatus(_apiService.CurrentUser.Status);
            } catch (InvalidOperationException)
            {
                LoggingService.LogString("Could Not Set Status Back To Online");
            }
        }

        private async void _gatewayService_OnServerConfigReceived(object? sender, EventArgs e)
        {
            var args = (ServerConfigEventArgs)e;

            LoggingService.LogString($"Server Config Received");
            LoggingService.LogModel(args.ServerConfig);

            if (_serverConfig != null) return; // only set server config upon client restart, not during reconnect (preventing log spam)

            if (args.ServerConfig != null)
            {
                _serverConfig = args.ServerConfig;
                if (args.ServerConfig.IsDown)
                {
                    LoggingService.LogString("Server Is Marked As Down");
                    MessageBox.Show($"Sorry, This Server Is Currently Down.\nMessage: {args.ServerConfig.IsDownMessage}\n\nPlease Try Again Later");

                    if (_gatewayService.HubConnection != null && _gatewayService.HubConnection.State == Microsoft.AspNetCore.SignalR.Client.HubConnectionState.Connected)
                    {
                        await _gatewayService.StopAsync();
                        await _gatewayService.DisposeAsync();
                    }
                    Environment.Exit(0);
                }
            }
        }

        private void _gatewayService_OnDirectMessageReceived(object? sender, EventArgs e)
        {
            var args = (DirectMessageEventArgs)e;

            LoggingService.LogString($"Direct Message Received From '{args.User.Username}'");

            DirectMessage? existingForm = (DirectMessage?)Application.OpenForms.Cast<Form>().FirstOrDefault(e => e.Name == "DirectMessage");
            if (existingForm != null && existingForm.User.Id == args.User.Id)
            {
                // we want to just add to its text box
                existingForm.Messages.Add($"{args.User.Username}: {args.Message.Content}");
            }
            else
            {
                // start a new form
                DirectMessage frmDirectMessage = new DirectMessage(_gatewayService, _apiService, args.User, args.Message);
                Task.Run(frmDirectMessage.ShowDialog);
            }
        }

        private async void _gatewayService_OnUserForceLogout(object? sender, EventArgs e)
        {
            MessageBox.Show("Connection To Server Lost. You Were Logged Out.\nTry Signing In Again.\nThe Application Will Now Close.", "oops.", MessageBoxButtons.OK, MessageBoxIcon.Error);

            await _gatewayService.StopAsync();

            if (File.Exists("./session.token")) File.Delete("./session.token");

            Environment.Exit(0); // dont want the user to try and make a new account right away
        }

        private void _apiService_OnCurrentUserUpdate(object? sender, EventArgs e)
        {
            lblWelcome.Text = $"Welcome, {_apiService.CurrentUser.Username}";
            RefreshCurrencyCounter();
        }

        private async void _gatewayService_OnRefreshContactsListReceived(object? sender, EventArgs e) => await RefreshContactsList();
        private async void _gatewayService_OnRefreshRoomListReceived(object? sender, EventArgs e) => await RefreshRoomsList();
        private async void _gatewayService_OnRefreshUserListReceived(object? sender, EventArgs e) => await RefreshUsers();
    }
}
