using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Auth;
using common;
using common.GUIUtils;
using common.WhatsNew;
using eagle;
using Eagle0.Tutorial;
using Grpc.Core;
using Grpc.Core.Interceptors;
using Net.Eagle0.Eagle.Api;
using Net.Eagle0.Eagle.Api.Auth;
using TMPro;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
using Object = System.Object;

public class AuthInterceptor : Interceptor {
    private readonly string headerName = "Authorization";
    private string headerValue;

    public static string HeaderValue(string name, string password) {
        String toEncode = name + ":" + password;
        return Convert.ToBase64String(Encoding.UTF8.GetBytes(toEncode));
    }

    public AuthInterceptor(String name, String password) {
        headerValue = "Basic " + HeaderValue(name, password);
    }
    public override TResponse BlockingUnaryCall<TRequest, TResponse>(
            TRequest request,
            ClientInterceptorContext<TRequest, TResponse> context,
            BlockingUnaryCallContinuation<TRequest, TResponse> continuation) {
        return continuation(request, ContextWithHeaders(context));
    }
    public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(
            TRequest request,
            ClientInterceptorContext<TRequest, TResponse> context,
            AsyncUnaryCallContinuation<TRequest, TResponse> continuation) {
        return continuation(request, ContextWithHeaders(context));
    }
    public override AsyncServerStreamingCall<TResponse>
    AsyncServerStreamingCall<TRequest, TResponse>(
            TRequest request,
            ClientInterceptorContext<TRequest, TResponse> context,
            AsyncServerStreamingCallContinuation<TRequest, TResponse> continuation) {
        return continuation(request, ContextWithHeaders(context));
    }
    public override AsyncClientStreamingCall<TRequest, TResponse>
    AsyncClientStreamingCall<TRequest, TResponse>(
            ClientInterceptorContext<TRequest, TResponse> context,
            AsyncClientStreamingCallContinuation<TRequest, TResponse> continuation) {
        return continuation(ContextWithHeaders(context));
    }
    public override AsyncDuplexStreamingCall<TRequest, TResponse>
    AsyncDuplexStreamingCall<TRequest, TResponse>(
            ClientInterceptorContext<TRequest, TResponse> context,
            AsyncDuplexStreamingCallContinuation<TRequest, TResponse> continuation) {
        return continuation(ContextWithHeaders(context));
    }

    private ClientInterceptorContext<TRequest, TResponse> ContextWithHeaders<TRequest, TResponse>(
            ClientInterceptorContext<TRequest, TResponse> original)
            where TRequest : class
            where TResponse : class {
        var headers = new Metadata();
        headers.Add(headerName, headerValue);

        return new ClientInterceptorContext<TRequest, TResponse>(
                original.Method,
                original.Host,
                original.Options.WithHeaders(headers));
    }
};

public class ConnectionHandler : MonoBehaviour, ILobbySubscriber, IDisposable {
    public TMP_Dropdown resolutionDropdown;

    [Header("OAuth UI")]
    public GameObject oauthPanel;
    public Button discordLoginButton;
    public Button googleLoginButton;
    public Button githubLoginButton;
    public Button appleLoginButton;
    public Button steamLoginButton;
    public Button twitchLoginButton;
    public TextMeshProUGUI oauthStatusText;

    [Header("Stored Accounts")]
    public GameObject storedAccountsContainer;
    public StoredAccountButton storedAccountButtonPrefab;
    public Sprite discordProviderIcon;
    public Sprite googleProviderIcon;
    public Sprite githubProviderIcon;
    public Sprite appleProviderIcon;
    public Sprite steamProviderIcon;
    public Sprite twitchProviderIcon;

    [Header("Status Display")]
    public TextMeshProUGUI connectionStatusText;

    [Header("Connection Panel Environment")]
    [Tooltip("Environment dropdown in connection panel (fallback if lobby unreachable)")]
    public TMP_Dropdown connectionEnvironmentDropdown;

    public GameObject connectionPanel;
    public GameObject connectionBackgroundLayer;
    public GameObject gameSelectionPanel;
    public GameObject customBattlePanel;
    public ErrorHandler errorHandler;

    [Header("Lobby Controls")]
    public Button logoutButton;
    public Button customBattleButton;
    public Button cancelCustomBattleButton;
    public TMP_Dropdown lobbyEnvironmentDropdown;
    public TextMeshProUGUI lobbyUserText;
    public TextMeshProUGUI lobbyStatusText;

    public GameObject runningGamesListArea;
    public GameObject runningGamesListItemPrefab;

    public GameObject availableGamesListArea;
    public GameObject availableGamesListItemPrefab;
    public GameObject waitingGamesListItemPrefab;
    public GameObject createGameListItemPrefab;

    public DropGameConfirmationPanel dropGameConfirmationPanel;

    public Canvas connectionCanvas;
    public Canvas eagleCanvas;
    public GameObject shardokContainer;
    public CustomBattleHandler customBattleHandler;

    List<GameInfo> fetchedRunningGames;
    List<AvailableLeader> fetchedNewGameLeaders;
    int fetchedMaxPlayerCount;
    private EagleConnection eagleConnection;
    private PersistentClientConnection _persistentClientConnection;
    private HttpClient _httpClient;
    private bool listen = false;
    private CancellationTokenSource _cancellationTokenSource;
    private readonly Object pendingReplyLock = new Object();
    private int _connectedEnvironmentIndex = -1;  // -1 means not connected
    private List<StoredAccountButton> _storedAccountButtons = new();
    private bool _exitedFullscreenForNewUser = false;  // Track if we exited fullscreen for login
    private FullScreenMode _previousFullScreenMode;    // Store mode to restore after login
    private int _previousWidth;                        // Store resolution to restore after login
    private int _previousHeight;

    const String BaseDomain = "eagle0.net";
    const String EnvironmentKey = "environmentKey";

    // Environment options: index 0 = prod., index 1 = qa., index 2 = (none)
    private static readonly List<string> EnvironmentOptions = new() { "prod.", "qa.", "" };
    private static readonly List<string> EnvironmentDisplayNames =
            new() { "prod.", "qa.", "(none)" };

    /// <summary>
    /// Returns the connected environment name ("prod." or "qa.") or null if not connected.
    /// </summary>
    public string ConnectedEnvironmentName =>
            _connectedEnvironmentIndex >= 0 &&
                            _connectedEnvironmentIndex < EnvironmentDisplayNames.Count
                    ? EnvironmentDisplayNames[_connectedEnvironmentIndex]
                    : null;

    private string GetUrlFromEnvironment() {
        int envIndex = PlayerPrefs.GetInt(EnvironmentKey, 0);
        string prefix = EnvironmentOptions[envIndex];
        return prefix + BaseDomain;
    }

    /// <summary>
    /// Get full URL with scheme for the selected environment (Eagle server).
    /// Remote servers use HTTPS, could be extended for local HTTP.
    /// </summary>
    private string GetEagleUrl() { return "https://" + GetUrlFromEnvironment(); }

    /// <summary>
    /// Get full URL with scheme for the Go auth service.
    /// Always uses prod auth service - OAuth tokens work across all environments.
    /// </summary>
    private string GetAuthServiceUrl() { return "https://prod.eagle0.net:40033"; }

    public void ReceiveLobbyUpdate(LobbyResponse lobbyResponse) {
        _handleLobbyResponse(lobbyResponse);
    }

    void Start() {
        var currentResolution = Screen.currentResolution;
        var resolutions = Screen.resolutions;

        resolutionDropdown.ClearOptions();
        resolutionDropdown.AddOptions(resolutions.Select(r => $"{r.width} x {r.height}").ToList());
        resolutionDropdown.value = resolutions.ToList().IndexOf(currentResolution);

        connectionCanvas.gameObject.SetActive(true);
        eagleCanvas.gameObject.SetActive(false);
        shardokContainer.SetActive(false);

        connectionCanvas.enabled = true;
        connectionPanel.gameObject.SetActive(true);
        if (connectionBackgroundLayer != null) { connectionBackgroundLayer.SetActive(true); }
        gameSelectionPanel.gameObject.SetActive(false);
        customBattlePanel.gameObject.SetActive(false);

        errorHandler.gameObject.SetActive(true);

        // Initialize OAuth UI
        SetupOAuthUI();

        // Initialize connection panel environment dropdown
        SetupConnectionEnvironmentDropdown();

        // Initialize Lobby UI (logout button, etc.)
        SetupLobbyUI();

        // Initialize status text
        UpdateConnectionStatus();

        // Configure bug report webhook
        BugReportPanelController.SetWebhookUrl(
                "https://discord.com/api/webhooks/1468260773187944448/PdjrzE3CXc1m58VbubUugvJwXDSKdkgaRI4GjgHcxMDW5D4L-JkDaeziLRnCgr972H6J");

        // Try to restore existing OAuth session
        TryRestoreSession();
    }

    private void SetupConnectionEnvironmentDropdown() {
        if (connectionEnvironmentDropdown == null) return;

        // Only show environment dropdown in Unity Editor; always use prod in builds
        if (!Application.isEditor) {
            connectionEnvironmentDropdown.gameObject.SetActive(false);
            PlayerPrefs.SetInt(EnvironmentKey, 0);  // Force prod
            return;
        }

        connectionEnvironmentDropdown.ClearOptions();
        connectionEnvironmentDropdown.AddOptions(EnvironmentDisplayNames);
        connectionEnvironmentDropdown.value = PlayerPrefs.GetInt(EnvironmentKey, 0);
        connectionEnvironmentDropdown.onValueChanged.AddListener(OnConnectionEnvironmentChanged);
    }

    private void OnConnectionEnvironmentChanged(int newEnvironmentIndex) {
        // Just save the preference - it will be used on next connection attempt
        PlayerPrefs.SetInt(EnvironmentKey, newEnvironmentIndex);
        Debug.Log(
                $"[ConnectionHandler] Environment changed to {EnvironmentDisplayNames[newEnvironmentIndex]}");
    }

    private void SetupOAuthUI() {
        // Set up OAuth button click handlers for new sign-ins
        if (discordLoginButton != null) {
            discordLoginButton.onClick.AddListener(
                    () => OnOAuthLoginClicked(OAuthProvider.Discord));
        }
        if (googleLoginButton != null) {
            googleLoginButton.onClick.AddListener(() => OnOAuthLoginClicked(OAuthProvider.Google));
        }
        if (githubLoginButton != null) {
            githubLoginButton.onClick.AddListener(() => OnOAuthLoginClicked(OAuthProvider.Github));
        }
        if (appleLoginButton != null) {
            appleLoginButton.onClick.AddListener(() => OnOAuthLoginClicked(OAuthProvider.Apple));
        }
        if (steamLoginButton != null) {
            steamLoginButton.onClick.AddListener(() => OnOAuthLoginClicked(OAuthProvider.Steam));
        }
        if (twitchLoginButton != null) {
            twitchLoginButton.onClick.AddListener(() => OnOAuthLoginClicked(OAuthProvider.Twitch));
        }

        // Subscribe to OAuthManager events
        if (OAuthManager.Instance != null) {
            OAuthManager.Instance.OnLoginSuccess += OnOAuthLoginSuccess;
            OAuthManager.Instance.OnLoginFailed += OnOAuthLoginFailed;
            OAuthManager.Instance.OnLogout += OnOAuthLogout;
        }

        // Show auth panel with stored accounts
        ShowAuthPanel();
    }

    private void ShowAuthPanel() {
        // Show OAuth panel
        if (oauthPanel != null) oauthPanel.SetActive(true);

        // Sync connection environment dropdown with saved preference
        if (connectionEnvironmentDropdown != null) {
            connectionEnvironmentDropdown.onValueChanged.RemoveListener(
                    OnConnectionEnvironmentChanged);
            connectionEnvironmentDropdown.value = PlayerPrefs.GetInt(EnvironmentKey, 0);
            connectionEnvironmentDropdown.onValueChanged.AddListener(
                    OnConnectionEnvironmentChanged);
        }

        // Refresh stored account buttons
        RefreshStoredAccountButtons();

        // Show sign-in tutorial for new users who have no stored accounts
        var accounts = OAuthManager.Instance?.GetStoredAccounts();
        if (accounts == null || accounts.Count == 0) {
            TutorialManager.Instance?.OnGameEvent("auth_panel_shown");
            // Show hint for users coming from invitation flow
            if (oauthStatusText != null) {
                oauthStatusText.text =
                        "Came from an invitation? Check your browser for a 'Complete Sign-In' button.";
            }
            // Exit fullscreen so user can see browser with the sign-in link
            if (Screen.fullScreen) {
                _exitedFullscreenForNewUser = true;
                _previousFullScreenMode = Screen.fullScreenMode;
                _previousWidth = Screen.width;
                _previousHeight = Screen.height;
                Screen.fullScreen = false;
                Debug.Log(
                        $"[ConnectionHandler] Exited fullscreen for new user sign-in flow (was {_previousWidth}x{_previousHeight})");
            }
        } else {
            // Clear any previous hint
            if (oauthStatusText != null) { oauthStatusText.text = ""; }
        }
    }

    private void RefreshStoredAccountButtons() {
        // Clear ALL children of the container
        // Disable immediately (Destroy is deferred to end of frame)
        if (storedAccountsContainer != null) {
            foreach (Transform child in storedAccountsContainer.transform) {
                child.gameObject.SetActive(false);
                Destroy(child.gameObject);
            }
        }
        _storedAccountButtons.Clear();

        if (storedAccountsContainer == null || storedAccountButtonPrefab == null) return;

        // Get stored accounts
        var accounts = OAuthManager.Instance?.GetStoredAccounts();
        if (accounts == null || accounts.Count == 0) return;

        // Create button for each stored account
        foreach (var account in accounts) {
            var buttonObj =
                    Instantiate(storedAccountButtonPrefab, storedAccountsContainer.transform);
            buttonObj.transform.localScale = Vector3.one;

            // Set button text (just display name, icon shows provider)
            buttonObj.label.text = account.DisplayName;

            var providerImage = buttonObj.icon;
            if (providerImage != null) {
                var providerLower = account.Provider.ToLowerInvariant();
                providerImage.sprite = providerLower switch { "google" => googleProviderIcon,
                                                              "github" => githubProviderIcon,
                                                              "apple" => appleProviderIcon,
                                                              "steam" => steamProviderIcon,
                                                              "twitch" => twitchProviderIcon,
                                                              _ => discordProviderIcon };
            }

            // Set click handler for sign-in
            var button = buttonObj.GetComponent<Button>();
            if (button != null) {
                var accountCopy = account;  // Capture for lambda
                button.onClick.AddListener(() => OnStoredAccountClicked(accountCopy));
            }

            // Set click handler for delete button
            if (buttonObj.deleteButton != null) {
                var accountCopy = account;  // Capture for lambda
                buttonObj.deleteButton.onClick.AddListener(
                        () => OnDeleteStoredAccount(accountCopy));
            }

            _storedAccountButtons.Add(buttonObj);
        }
    }

    private async void OnStoredAccountClicked(StoredAccount account) {
        // Configure OAuthManager with URLs
        OAuthManager.Instance.SetServerUrls(GetAuthServiceUrl(), GetEagleUrl());

        if (oauthStatusText != null) {
            oauthStatusText.text = $"Connecting as {account.DisplayName}...";
        }

        var success = await OAuthManager.Instance.ConnectWithStoredAccountAsync(account);
        if (success) {
            // OnLoginSuccess will handle connection to lobby
        } else {
            // Failed - might need re-auth, refresh the buttons
            RefreshStoredAccountButtons();
        }
    }

    private void OnDeleteStoredAccount(StoredAccount account) {
        Debug.Log($"[ConnectionHandler] Deleting stored account: {account.DisplayName}");
        OAuthManager.Instance?.RemoveStoredAccount(account);
        RefreshStoredAccountButtons();
    }

    private void SetupLobbyUI() {
        if (customBattleButton != null) { customBattleButton.onClick.AddListener(CustomBattle); }
        if (cancelCustomBattleButton != null) {
            cancelCustomBattleButton.onClick.AddListener(CancelCustomBattle);
        }

        // Set up environment dropdown (only in Unity Editor)
        if (lobbyEnvironmentDropdown != null) {
            if (!Application.isEditor) {
                lobbyEnvironmentDropdown.gameObject.SetActive(false);
            } else {
                lobbyEnvironmentDropdown.ClearOptions();
                lobbyEnvironmentDropdown.AddOptions(EnvironmentDisplayNames);
                lobbyEnvironmentDropdown.value = PlayerPrefs.GetInt(EnvironmentKey, 0);
                lobbyEnvironmentDropdown.onValueChanged.AddListener(OnLobbyEnvironmentChanged);
            }
        }
    }

    private void UpdateLobbyStatusDisplays() {
        // Update environment dropdown selection
        if (lobbyEnvironmentDropdown != null && _connectedEnvironmentIndex >= 0) {
            // Temporarily remove listener to avoid triggering reconnect
            lobbyEnvironmentDropdown.onValueChanged.RemoveListener(OnLobbyEnvironmentChanged);
            lobbyEnvironmentDropdown.value = _connectedEnvironmentIndex;
            lobbyEnvironmentDropdown.onValueChanged.AddListener(OnLobbyEnvironmentChanged);
        }

        // Show current user from OAuth
        if (lobbyUserText != null) { lobbyUserText.text = TokenStorage.DisplayName ?? ""; }
    }

    private void OnLobbyEnvironmentChanged(int newEnvironmentIndex) {
        if (newEnvironmentIndex == _connectedEnvironmentIndex) return;

        Debug.Log(
                $"[ConnectionHandler] Switching environment from {ConnectedEnvironmentName} to {EnvironmentDisplayNames[newEnvironmentIndex]}");

        // Disconnect from current environment
        _persistentClientConnection?.Dispose();
        eagleConnection?.Dispose();
        _httpClient?.Dispose();

        _persistentClientConnection = null;
        eagleConnection = null;
        _httpClient = null;

        // Update environment index and reconnect
        _connectedEnvironmentIndex = newEnvironmentIndex;
        PlayerPrefs.SetInt(EnvironmentKey, newEnvironmentIndex);

        // Clear current game lists while reconnecting
        foreach (Transform row in runningGamesListArea.transform) { Destroy(row.gameObject); }
        foreach (Transform row in availableGamesListArea.transform) { Destroy(row.gameObject); }

        // Show loading indicator
        if (lobbyStatusText != null) { lobbyStatusText.text = "Switching environment..."; }

        // Reconnect to new environment
        _createConnection();
        RequestMaps();
        StartListeningForLobbyUpdates();
    }

    public async void OnLogoutClicked() {
        Debug.Log("[ConnectionHandler] Logout button clicked");

        // Clear OAuth tokens
        if (OAuthManager.Instance != null) { await OAuthManager.Instance.LogoutAsync(); }

        // Disconnect from server
        _persistentClientConnection?.Dispose();
        eagleConnection?.Dispose();
        _httpClient?.Dispose();
        _cancellationTokenSource?.Dispose();

        _persistentClientConnection = null;
        eagleConnection = null;
        _httpClient = null;
        _cancellationTokenSource = null;
        _connectedEnvironmentIndex = -1;

        // Return to connection screen
        gameSelectionPanel.SetActive(false);
        connectionPanel.SetActive(true);
        if (connectionBackgroundLayer != null) { connectionBackgroundLayer.SetActive(true); }

        // Re-initialize the auth UI
        ShowAuthPanel();
        UpdateConnectionStatus();
    }

    private async void TryRestoreSession() {
        if (OAuthManager.Instance == null) return;

        // Configure OAuthManager with auth service and Eagle URLs
        OAuthManager.Instance.SetServerUrls(GetAuthServiceUrl(), GetEagleUrl());

        if (oauthStatusText != null) { oauthStatusText.text = "Checking for existing session..."; }

        // TryRestoreSessionAsync() fires OnLoginSuccess if successful, which triggers
        // ConnectWithOAuth() via the OnOAuthLoginSuccess handler. We only need to clear
        // the status text if restore failed (user not logged in).
        var restored = await OAuthManager.Instance.TryRestoreSessionAsync();
        if (!restored && oauthStatusText != null) { oauthStatusText.text = ""; }
    }

    private async void OnOAuthLoginClicked(OAuthProvider provider) {
        // Configure OAuthManager with auth service and Eagle URLs (user may have changed dropdown)
        OAuthManager.Instance.SetServerUrls(GetAuthServiceUrl(), GetEagleUrl());

        if (oauthStatusText != null) {
            oauthStatusText.text = $"Opening browser for {provider} login...";
        }

        try {
            await OAuthManager.Instance.LoginAsync(provider);
            // OnLoginSuccess or OnNewUserNeedsDisplayName will be called
        } catch (Exception ex) {
            Debug.LogWarning($"OAuth login failed: {ex.Message}");
            if (oauthStatusText != null) { oauthStatusText.text = $"Login failed: {ex.Message}"; }
        }
    }

    private void OnOAuthLoginSuccess(UserInfo user) {
        MainQueue.Q.Enqueue(() => {
            if (oauthStatusText != null) { oauthStatusText.text = $"Welcome, {user.DisplayName}!"; }
            // Restore fullscreen if we exited it for the new user sign-in flow
            if (_exitedFullscreenForNewUser) {
                _exitedFullscreenForNewUser = false;
                Screen.SetResolution(_previousWidth, _previousHeight, _previousFullScreenMode);
                Debug.Log(
                        $"[ConnectionHandler] Restored fullscreen: {_previousWidth}x{_previousHeight} {_previousFullScreenMode}");
            }

            // Show what's new for returning users
            WhatsNewManager.Instance?.CheckAndShow();

            ConnectWithOAuth();
        });
    }

    private void OnOAuthLoginFailed(string error) {
        MainQueue.Q.Enqueue(() => {
            if (oauthStatusText != null) { oauthStatusText.text = $"Login failed: {error}"; }
        });
    }

    private void OnOAuthLogout() {
        MainQueue.Q.Enqueue(() => {
            ShowAuthPanel();
            if (oauthStatusText != null) { oauthStatusText.text = ""; }
        });
    }

    private void ConnectWithOAuth() { _internalConnectEagle(); }

    private float _statusUpdateTimer = 0f;
    private const float StatusUpdateInterval = 0.5f;

    void Update() {
        // Only update status periodically and when connection panel is visible
        if (!connectionPanel.activeInHierarchy) return;

        _statusUpdateTimer += Time.deltaTime;
        if (_statusUpdateTimer >= StatusUpdateInterval) {
            _statusUpdateTimer = 0f;
            UpdateConnectionStatus();
        }
    }

    private void UpdateConnectionStatus() {
        if (connectionStatusText == null) return;

        // No connection yet - show nothing
        if (_persistentClientConnection == null) {
            connectionStatusText.text = "";
            return;
        }

        var circuitState = _persistentClientConnection.CircuitBreaker.CurrentState;
        var connState = _persistentClientConnection.CurrentState;

        // Circuit breaker takes precedence
        if (circuitState == eagle.ConnectionCircuitBreaker.State.Open) {
            var nextTest = _persistentClientConnection.CircuitBreaker.NextTestAttempt;
            if (nextTest.HasValue) {
                var timeUntilTest = nextTest.Value - DateTime.UtcNow;
                if (timeUntilTest.TotalSeconds > 0) {
                    int seconds = Math.Min(10, (int)Math.Ceiling(timeUntilTest.TotalSeconds));
                    connectionStatusText.text =
                            $"<color=red>●</color> Server unavailable. Retry in {seconds}s";
                    return;
                }
            }
            connectionStatusText.text = "<color=red>●</color> Server unavailable";
            return;
        }

        if (circuitState == eagle.ConnectionCircuitBreaker.State.HalfOpen) {
            connectionStatusText.text = "<color=yellow>●</color> Testing connection...";
            return;
        }

        // Normal connection states
        connectionStatusText.text = connState switch {
            eagle.ConnectionState.Connected => "<color=green>●</color> Connected",
            eagle.ConnectionState.Connecting => "<color=yellow>●</color> Connecting...",
            eagle.ConnectionState.Disconnected => "<color=red>●</color> Disconnected",
            eagle.ConnectionState.Reconnecting => GetReconnectingText(),
            eagle.ConnectionState.SubscriptionPending => "<color=yellow>●</color> Subscribing...",
            _ => ""
        };
    }

    private string GetReconnectingText() {
        var nextAttempt = _persistentClientConnection?.NextReconnectAttempt;
        if (!nextAttempt.HasValue) return "<color=yellow>●</color> Reconnecting...";

        var timeUntilRetry = nextAttempt.Value - DateTime.UtcNow;
        if (timeUntilRetry.TotalSeconds <= 0) { return "<color=yellow>●</color> Reconnecting..."; }

        int seconds = Math.Min(10, (int)Math.Ceiling(timeUntilRetry.TotalSeconds));
        return $"<color=orange>●</color> Retry in {seconds}s";
    }

    public void EditorButtonClicked() { SceneManager.LoadScene("Map Editor"); }

    public void ConnectEagle() { _internalConnectEagle(); }

    public void CustomBattle() { _internalCustomBattle(); }

    public void CancelCustomBattle() {
        customBattlePanel.SetActive(false);
        shardokContainer.SetActive(false);
        connectionCanvas.gameObject.SetActive(true);
        gameSelectionPanel.SetActive(true);
        StartListeningForLobbyUpdates();
    }

    public void OnApplicationQuit() { Dispose(); }

    public void Dispose() {
        listen = false;

        // Cancel any ongoing operations using cancellation token
        _cancellationTokenSource?.Cancel();

        _cancellationTokenSource?.Dispose();
        _cancellationTokenSource = null;

        _httpClient?.Dispose();
        _httpClient = null;
        _persistentClientConnection?.Dispose();
        _persistentClientConnection = null;
        eagleConnection?.Dispose();
        eagleConnection = null;

        _connectedEnvironmentIndex = -1;
    }

    public void OnResolutionChanged(int dropdownIndex) {
        var newResolution = Screen.resolutions[dropdownIndex];
        Screen.SetResolution(
                newResolution.width,
                newResolution.height,
                FullScreenMode.FullScreenWindow,
                newResolution.refreshRateRatio);
    }

    private void _handleLobbyResponse(LobbyResponse lobbyResponse) {
        fetchedRunningGames = new List<GameInfo>();
        fetchedRunningGames.AddRange(lobbyResponse.RunningGames);

        fetchedNewGameLeaders = new List<AvailableLeader>();
        fetchedNewGameLeaders.AddRange(lobbyResponse.NewGameOptions.AvailableLeaders);

        fetchedMaxPlayerCount = lobbyResponse.NewGameOptions.MaxSupportedPlayers;

        MainQueue.Q.Enqueue(() => {
            ResourceFetcher.headshotFetcher.Prefetch(
                    fetchedNewGameLeaders.Select(a => a.ImagePath));

            connectionPanel.gameObject.SetActive(false);
            if (connectionBackgroundLayer != null) { connectionBackgroundLayer.SetActive(false); }
            gameSelectionPanel.gameObject.SetActive(true);

            // Update lobby status displays
            UpdateLobbyStatusDisplays();

            // Set up running games table
            foreach (Transform row in runningGamesListArea.transform) { Destroy(row.gameObject); }
            foreach (var runningGame in lobbyResponse.RunningGames) {
                var listItem = Instantiate(
                        runningGamesListItemPrefab,
                        runningGamesListArea.transform,
                        true);
                listItem.transform.localScale = new Vector3 { x = 1.0f, y = 1.0f, z = 1.0f };
                var runningGameItem = listItem.GetComponent<RunningGameItem>();
                runningGameItem.SetRunningGame(
                        runningGame.GameId,
                        runningGame.Leader,
                        runningGame.LastPlayedTimestampMillis);
                runningGameItem.GoCallback = this.SelectEagleGame;
                runningGameItem.DropCallback = this.DropGame;
            }

            // Set up available/waiting games table
            foreach (Transform row in availableGamesListArea.transform) { Destroy(row.gameObject); }

            foreach (var waitingGame in lobbyResponse.WaitingGames) {
                var listItem = Instantiate(
                        waitingGamesListItemPrefab,
                        availableGamesListArea.transform,
                        true);
                listItem.transform.localScale = new Vector3 { x = 1.0f, y = 1.0f, z = 1.0f };
                listItem.GetComponent<WaitingGameItem>().SetWaitingGame(
                        waitingGame.GameId,
                        string.Format(
                                "{0} / {1}",
                                waitingGame.CurrentHumanPlayerCount,
                                waitingGame.MaxHumanPlayerCount),
                        waitingGame.SelectedLeader);
            }

            var createGameItem =
                    Instantiate(createGameListItemPrefab, availableGamesListArea.transform, true);
            // var singlePlayerListItem = Instantiate(availableGamesListItemPrefab);
            createGameItem.transform.localScale = new Vector3 { x = 1.0f, y = 1.0f, z = 1.0f };
            createGameItem.GetComponent<CreateGameItem>().SetCreationOptions(
                    fetchedNewGameLeaders,
                    fetchedMaxPlayerCount);
            createGameItem.GetComponent<CreateGameItem>().CreateCallback = this.CreateGame;

            foreach (var availableGame in lobbyResponse.AvailableGames) {
                var listItem = Instantiate(
                        availableGamesListItemPrefab,
                        availableGamesListArea.transform,
                        true);
                listItem.transform.localScale = new Vector3 { x = 1.0f, y = 1.0f, z = 1.0f };
                var availableGameItem = listItem.GetComponent<AvailableGameItem>();
                availableGameItem.SetAvailableNewGame(
                        availableGame.GameId,
                        string.Format(
                                "{0} / {1}",
                                availableGame.CurrentHumanPlayerCount,
                                availableGame.MaxHumanPlayerCount),
                        availableGame.AvailableLeaders.ToList(),
                        availableGame.MaxHumanPlayerCount);
                availableGameItem.JoinCallback = this.JoinGame;
                availableGameItem.DropCallback = this.DropGame;
            }

            // Clear loading indicator now that lobby is populated
            if (lobbyStatusText != null) { lobbyStatusText.text = ""; }

            // First-run experience: check if this is truly the first time
            // If user has no running games and has never completed onboarding,
            // auto-create a 1-player game for them
            if (!_hasAutoCreatedFirstGame && ShouldAutoCreateFirstGame(lobbyResponse)) {
                _hasAutoCreatedFirstGame = true;
                Debug.Log(
                        "[ConnectionHandler] First launch detected - auto-creating 1-player game");
                AutoCreateFirstGame();
            } else {
                // Trigger lobby tutorial for users who haven't seen it
                TutorialManager.Instance?.OnGameEvent("lobby_entered");
            }
        });
    }

    // Track whether we've already auto-created a first game this session
    private bool _hasAutoCreatedFirstGame = false;

    /// <summary>
    /// Check if we should auto-create a first game for a new user.
    /// Returns true if: no running games, and onboarding not completed.
    /// </summary>
    private bool ShouldAutoCreateFirstGame(LobbyResponse lobbyResponse) {
        // Check if user has any running games
        if (lobbyResponse.RunningGames.Count > 0) return false;

        // Check if tutorial system indicates this is a first-time user
        var tutorialManager = TutorialManager.Instance;
        if (tutorialManager == null) return false;

        // If onboarding is complete, they've played before
        if (tutorialManager.State.OnboardingCompleted) return false;

        // Check if they've already seen the lobby tutorial
        if (tutorialManager.State.HasCompletedTutorial("lobby_intro")) return false;

        return true;
    }

    /// <summary>
    /// Auto-create and launch a 7-faction game for first-time users.
    /// </summary>
    private void AutoCreateFirstGame() {
        if (fetchedNewGameLeaders == null || fetchedNewGameLeaders.Count == 0) {
            Debug.LogWarning("[ConnectionHandler] Cannot auto-create game - no leaders available");
            return;
        }

        // Pick a random leader
        var randomIndex = new System.Random().Next(fetchedNewGameLeaders.Count);
        var leaderTextId = fetchedNewGameLeaders[randomIndex].NameTextId;

        // Create a 7-faction game (1 human + 6 AI) - same as lobby default
        Debug.Log($"[ConnectionHandler] Auto-creating 7-faction game with leader {leaderTextId}");
        CreateGame(leaderTextId, 1, 7);
    }

    private void StartListeningForLobbyUpdates() {
        lock (pendingReplyLock) {
            _persistentClientConnection.SetLobbySubscriber(this);
            _persistentClientConnection.SendUpdateStreamRequestAsync(
                    new UpdateStreamRequest { EnterLobbyRequest = new EnterLobbyRequest() });
        }
    }

    private void _createConnection() {
        _connectedEnvironmentIndex = PlayerPrefs.GetInt(EnvironmentKey, 0);

        // Dispose existing connections before creating new ones
        _httpClient?.Dispose();
        _persistentClientConnection?.Dispose();
        eagleConnection?.Dispose();
        _cancellationTokenSource?.Dispose();

        // Initialize cancellation token for thread management
        _cancellationTokenSource = new CancellationTokenSource();

        string url = GetUrlFromEnvironment();

        // OAuth JWT-based connection
        if (!TokenStorage.HasValidToken) {
            Debug.LogError("[ConnectionHandler] No valid token available for connection");
            return;
        }

        eagleConnection = EagleConnection.CreateWithJwt(url);

        _httpClient = new HttpClient();
        _httpClient.DefaultRequestHeaders.Authorization =
                new AuthenticationHeaderValue("Bearer", TokenStorage.AccessToken);

        _persistentClientConnection = new PersistentClientConnection(
                eagleConnection.EagleGrpcClient,
                eagleConnection.credentials,
                EagleConnection.EagleCancellationToken);
        _persistentClientConnection.OnChannelDead = () => {
            // Queue to main thread to avoid threading issues
            MainQueue.Q.Enqueue(() => {
                Debug.Log("[ConnectionHandler] Channel dead, recreating connection");
                _createConnection();
            });
        };
        _persistentClientConnection.OnCommandError = (errorMessage) => {
            // Queue to main thread to display error via ErrorHandler
            MainQueue.Q.Enqueue(() => {
                Debug.LogError($"[ConnectionHandler] Command rejected by server: {errorMessage}");
            });
        };
        _persistentClientConnection.Connect();

        errorHandler.PersistentClientConnection = _persistentClientConnection;
        ResourceFetcher.SetUpConnection(_httpClient);
    }

    private void _internalCustomBattle() {
        // Connection already exists when called from lobby
        _persistentClientConnection.SetLobbySubscriber(this);

        SetCustomBattleActive(true);

        gameSelectionPanel.SetActive(false);
        customBattlePanel.SetActive(true);
    }

    private void RequestMaps() {
        var mapNames = HexMapStorage.Instance.MapNames();
        var mapInfos = mapNames.Select(mapName => {
                                   var existingMap = HexMapStorage.Instance.WithName(mapName);
                                   return new HexMapsRequest.Types.OneMapRequestInfo {
                                       MapName = mapName,
                                       MapHash = HexMapStorage.Instance.HashForName(mapName)
                                   };
                               })
                               .ToList();

        var hexMapsRequest = new HexMapsRequest();
        hexMapsRequest.MapInfos.AddRange(mapInfos);

        _persistentClientConnection.SendUpdateStreamRequestAsync(
                new UpdateStreamRequest { HexMapsRequest = hexMapsRequest });
    }

    private void _internalConnectEagle() {
        SetLobbyActive(true);
        connectionCanvas.enabled = true;

        _createConnection();

        // If connection creation failed (e.g., no valid token), don't proceed
        if (_persistentClientConnection == null) {
            Debug.LogError("[ConnectionHandler] Connection creation failed, cannot proceed");
            return;
        }

        try {
            RequestMaps();
            StartListeningForLobbyUpdates();
        } catch (RpcException e) {
            MainQueue.Q.Enqueue(() => { errorHandler.Add(e); });
        } catch (Exception e) {
            MainQueue.Q.Enqueue(() => { errorHandler.Add(e); });
        }
    }

    private void JoinGame(long gameId, string leaderName) {
        var game = _internalJoinEagleGame(gameId, leaderName);
    }

    private void DropGame(long gameId) {
        if (dropGameConfirmationPanel != null) {
            dropGameConfirmationPanel.OnConfirm = ConfirmDropGame;
            dropGameConfirmationPanel.Show(gameId, "Are you sure you want to leave this game?");
        } else {
            // Fallback if no confirmation panel is configured
            ConfirmDropGame(gameId);
        }
    }

    private void ConfirmDropGame(long gameId) { _internalDropGame(gameId); }

    private void CreateGame(string leaderNameTextId, int humanPlayerCount, int totalPlayerCount) {
        // Track multiplayer status for tutorial system
        TutorialManager.IsMultiplayerGame = humanPlayerCount > 1;
        var game = _internalCreateGame(leaderNameTextId, humanPlayerCount, totalPlayerCount);
    }

    public void QuitButtonClicked() { Application.Quit(); }

    public void HandleJoinGameResponse(JoinGameResponse response) {
        try {
            switch (response.Result) {
                case JoinGameResult.SuccessJoinGameResult: break;

                default:
                    // FIXME: display something
                    break;
            }
        } catch (Exception e) { Console.WriteLine(e.ToString()); }
    }

    public void HandleCreateGameResponse(CreateGameResponse response) {
        try {
            switch (response.Result) {
                case JoinGameResult.SuccessJoinGameResult:
                    SelectEagleGame(response.GameId);
                    // Automatically start the game if it's one player?
                    break;

                default:
                    // FIXME: display something
                    break;
            }
        } catch (Exception e) { Console.WriteLine(e.ToString()); }
    }

    public void HandleCustomBattleResponse(CustomBattleResponse response) {
        try {
            customBattleHandler.HandleCustomBattleResponse(response);
            customBattleHandler.SetActive(eagleConnection, _persistentClientConnection);
        } catch (Exception e) { Console.WriteLine(e.ToString()); }
    }

    private async Task<bool>
    _internalCreateGame(string desiredLeaderTextId, int humanPlayerCount, int totalPlayerCount) {
        try {
            return await _persistentClientConnection.SendUpdateStreamRequestAsync(
                    new UpdateStreamRequest {
                        CreateGameRequest =
                                new CreateGameRequest {
                                    DesiredLeaderTextId = desiredLeaderTextId,
                                    TotalPlayerCount = totalPlayerCount,
                                    HumanPlayerCount = humanPlayerCount
                                }
                    });
        } catch (WebException e) {
            Debug.Log(String.Format("Caught exception in BeginGetResponse {0}", e.Message));
            SetLobbyActive(true);
            return false;
        }
    }

    private async Task<bool> _internalJoinEagleGame(long gameId, string leaderNameTextId) {
        try {
            return await _persistentClientConnection.SendUpdateStreamRequestAsync(
                    new UpdateStreamRequest {
                        JoinGameRequest =
                                new JoinGameRequest {
                                    GameId = gameId,
                                    DesiredLeaderTextId = leaderNameTextId
                                }
                    });
        } catch (WebException e) {
            Debug.Log(String.Format("Caught exception in BeginGetResponse {0}", e.Message));
            SetLobbyActive(true);
            return false;
        }
    }

    private async Task<bool> _internalDropGame(long gameId) {
        try {
            return await _persistentClientConnection.SendUpdateStreamRequestAsync(
                    new UpdateStreamRequest {
                        DropGameRequest = new DropGameRequest { GameId = gameId }
                    });
        } catch (WebException e) {
            Debug.Log(String.Format("Caught exception in DropGame {0}", e.Message));
            return false;
        }
    }

    private void SetLobbyActive(bool active) {
        runningGamesListArea.SetActive(active);
        availableGamesListArea.SetActive(active);
    }

    private void SetCustomBattleActive(bool active) {
        customBattleHandler.SetActive(eagleConnection, _persistentClientConnection);
    }

    private void SelectEagleGame(long gameId) {
        var gameInfo = fetchedRunningGames.Find(gi => gi.GameId == gameId);

        if (gameInfo != null) {
            SetLobbyActive(false);
            eagleCanvas.GetComponent<EagleGameController>().SetUpGame(
                    gameInfo.GameId,
                    gameInfo.FactionId,
                    _persistentClientConnection,
                    ConnectedEnvironmentName);
            connectionCanvas.enabled = false;
            connectionCanvas.gameObject.SetActive(false);
            eagleCanvas.gameObject.SetActive(true);
        }
    }
}
