using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using common;
using Grpc.Core;
using Net.Eagle0.Eagle.Api.Auth;
using UnityEngine;

namespace Auth {
    /// <summary>
    /// Manages OAuth authentication flow using server-mediated polling:
    /// - Opens system browser for OAuth consent
    /// - Polls server for OAuth completion (no deep links needed)
    /// - Token storage and refresh
    /// - Multi-account support
    /// </summary>
    public class OAuthManager : MonoBehaviour {
        public static OAuthManager Instance { get; private set; }

        private AuthClient _authClient;
        private string _currentAuthServiceUrl;
        private string _currentEagleUrl;
        private OAuthProvider _currentLoginProvider;  // Track provider during login
        private string _pendingSessionTransferCode;   // Queued if deep link arrives before auth
                                                      // client ready
        private string _pendingOAuthState;            // Queued if OAuth result arrives before auth
                                                      // client ready

        // Events for UI updates
        public event Action<UserInfo> OnLoginSuccess;
        public event Action<string> OnLoginFailed;
        public event Action OnLogout;
        public event Action<bool> OnNewUserNeedsDisplayName;  // true if new user

        public bool IsAuthenticated => TokenStorage.HasValidToken;
        public string DisplayName => TokenStorage.DisplayName;
        public string UserId => TokenStorage.UserId;

        private void Awake() {
            if (Instance != null && Instance != this) {
                Destroy(gameObject);
                return;
            }

            Instance = this;
            DontDestroyOnLoad(gameObject);

            // Initialize TokenStorage cache from PlayerPrefs on main thread
            // This allows background threads to access tokens safely
            TokenStorage.InitializeCache();

            // AuthClient is created lazily when SetServerUrls is called

            // Register deep link handler for OAuth callback
            Application.deepLinkActivated += OnDeepLinkActivated;

            // On Windows, listen for deep links from other instances via named pipe
            SingleInstanceEnforcer.OnDeepLinkReceived += OnDeepLinkActivated;

            // Check if app was launched via deep link
            if (!string.IsNullOrEmpty(Application.absoluteURL)) {
                OnDeepLinkActivated(Application.absoluteURL);
            }

            // On Windows, URL scheme launches pass the URL as a command line argument
            CheckCommandLineForDeepLink();
        }

        private void CheckCommandLineForDeepLink() {
            var args = System.Environment.GetCommandLineArgs();
            // Args[0] is the executable path, check remaining args for eagle0:// URLs
            for (int i = 1; i < args.Length; i++) {
                if (args[i].StartsWith("eagle0://")) {
                    Debug.Log($"[OAuthManager] Found deep link in command line: {args[i]}");
                    OnDeepLinkActivated(args[i]);
                    break;
                }
            }
        }

        private void OnDeepLinkActivated(string url) {
            Debug.Log($"[OAuthManager] Deep link received: {url}");

            // Handle session transfer deep link: eagle0://auth/session/{code}
            // This is for existing users - immediate login with session transfer code
            const string sessionTransferPrefix = "eagle0://auth/session/";
            if (url.StartsWith(sessionTransferPrefix)) {
                var code = url.Substring(sessionTransferPrefix.Length).TrimEnd('/');
                Debug.Log(
                        $"[OAuthManager] Session transfer deep link received, code length: {code.Length}");
                WindowFocusManager.BringToForeground();
                _ = HandleSessionTransferAsync(code);
                return;
            }

            // Handle OAuth result deep link: eagle0://auth/result/{state}
            // This is for new users - poll CheckOAuthStatus to complete registration
            const string resultPrefix = "eagle0://auth/result/";
            if (url.StartsWith(resultPrefix)) {
                var state = url.Substring(resultPrefix.Length).TrimEnd('/');
                Debug.Log($"[OAuthManager] OAuth result deep link received, state: {state}");
                WindowFocusManager.BringToForeground();
                _ = HandleOAuthResultAsync(state);
                return;
            }

            // Handle eagle0://auth/complete - brings app to foreground after OAuth
            if (url.StartsWith("eagle0://auth")) {
                Debug.Log("[OAuthManager] OAuth callback deep link - bringing app to foreground");
                WindowFocusManager.BringToForeground();
            }
        }

        /// <summary>
        /// Handle session transfer code from invitation deep link.
        /// Exchanges the one-time code for tokens and logs the user in.
        /// </summary>
        private async Task HandleSessionTransferAsync(string code) {
            if (_authClient == null) {
                // Auth client not ready yet - queue the code for later processing
                Debug.Log(
                        "[OAuthManager] Auth client not ready, queuing session transfer code for later");
                _pendingSessionTransferCode = code;
                return;
            }

            try {
                Debug.Log("[OAuthManager] Exchanging session transfer code for tokens...");
                var response = await _authClient.ExchangeSessionTransferCodeAsync(code);

                // Store tokens with actual provider from response
                var providerName = response.User.Provider.ToString().ToLowerInvariant();
                TokenStorage.StoreTokens(
                        response.AccessToken,
                        response.RefreshToken,
                        response.ExpiresAt,
                        response.User.UserId,
                        response.User.DisplayName,
                        providerName,
                        response.User.IsAdmin);

                Debug.Log(
                        $"[OAuthManager] Session transfer successful for {response.User.DisplayName}");

                OnLoginSuccess?.Invoke(response.User);
            } catch (Exception ex) {
                Debug.LogWarning($"[OAuthManager] Session transfer failed: {ex.Message}");
                OnLoginFailed?.Invoke($"Sign-in link expired or invalid. Please sign in manually.");
            }
        }

        /// <summary>
        /// Handle OAuth result deep link for new users.
        /// Polls CheckOAuthStatus to complete registration flow (invitation validation, display
        /// name).
        /// </summary>
        private async Task HandleOAuthResultAsync(string state) {
            if (_authClient == null) {
                // Auth client not ready yet - queue the state for later processing
                Debug.Log("[OAuthManager] Auth client not ready, queuing OAuth state for later");
                _pendingOAuthState = state;
                return;
            }

            try {
                Debug.Log($"[OAuthManager] Checking OAuth status for state: {state}");
                var response = await _authClient.CheckOAuthStatusAsync(state);

                switch (response.Status) {
                    case OAuthStatus.Success:
                        // Store tokens
                        var providerName = response.User.Provider.ToString().ToLowerInvariant();
                        TokenStorage.StoreTokens(
                                response.AccessToken,
                                response.RefreshToken,
                                response.ExpiresAt,
                                response.User.UserId,
                                response.User.DisplayName,
                                providerName,
                                response.User.IsAdmin);

                        // Check if new user needs display name
                        if (response.IsNewUser || string.IsNullOrEmpty(response.User.DisplayName)) {
                            Debug.Log("[OAuthManager] New user needs to set display name");
                            OnNewUserNeedsDisplayName?.Invoke(true);
                        } else {
                            Debug.Log(
                                    $"[OAuthManager] OAuth successful for {response.User.DisplayName}");
                            OnLoginSuccess?.Invoke(response.User);
                        }
                        break;

                    case OAuthStatus.InvitationRequired:
                        Debug.Log("[OAuthManager] Invitation code required for new account");
                        OnLoginFailed?.Invoke(
                                "An invitation code is required to create a new account.");
                        break;

                    case OAuthStatus.Failed:
                        Debug.LogWarning($"[OAuthManager] OAuth failed: {response.ErrorMessage}");
                        OnLoginFailed?.Invoke(response.ErrorMessage);
                        break;

                    case OAuthStatus.Expired:
                        Debug.LogWarning("[OAuthManager] OAuth session expired");
                        OnLoginFailed?.Invoke("Sign-in session expired. Please try again.");
                        break;

                    case OAuthStatus.Pending:
                        // This shouldn't happen since OAuth already completed, but handle it
                        Debug.LogWarning("[OAuthManager] OAuth still pending - unexpected");
                        OnLoginFailed?.Invoke("Sign-in incomplete. Please try again.");
                        break;

                    default:
                        Debug.LogWarning($"[OAuthManager] Unknown OAuth status: {response.Status}");
                        OnLoginFailed?.Invoke("Sign-in failed. Please try again.");
                        break;
                }
            } catch (Exception ex) {
                Debug.LogWarning($"[OAuthManager] OAuth status check failed: {ex.Message}");
                OnLoginFailed?.Invoke($"Sign-in failed: {ex.Message}");
            }
        }

        /// <summary>
        /// Set the server URLs for OAuth and game requests. Call this before any OAuth operations.
        /// </summary>
        /// <param name="authServiceUrl">Go auth service URL, e.g.
        /// "https://prod.eagle0.net:40033"</param> <param name="eagleUrl">Eagle server URL, e.g.
        /// "https://prod.eagle0.net:40032"</param>
        public void SetServerUrls(string authServiceUrl, string eagleUrl) {
            if (_currentAuthServiceUrl == authServiceUrl && _currentEagleUrl == eagleUrl &&
                _authClient != null) {
                return;  // Already configured for these URLs
            }

            _authClient?.Dispose();
            _currentAuthServiceUrl = authServiceUrl;
            _currentEagleUrl = eagleUrl;
            _authClient = new AuthClient(authServiceUrl, eagleUrl);

            // Process any pending session transfer that arrived before auth client was ready
            if (!string.IsNullOrEmpty(_pendingSessionTransferCode)) {
                var code = _pendingSessionTransferCode;
                _pendingSessionTransferCode = null;
                Debug.Log("[OAuthManager] Processing queued session transfer code");
                _ = HandleSessionTransferAsync(code);
            }

            // Process any pending OAuth state that arrived before auth client was ready
            if (!string.IsNullOrEmpty(_pendingOAuthState)) {
                var state = _pendingOAuthState;
                _pendingOAuthState = null;
                Debug.Log("[OAuthManager] Processing queued OAuth state");
                _ = HandleOAuthResultAsync(state);
            }
        }

        private void OnDestroy() {
            Application.deepLinkActivated -= OnDeepLinkActivated;
            SingleInstanceEnforcer.OnDeepLinkReceived -= OnDeepLinkActivated;
            _authClient?.Dispose();
            if (Instance == this) { Instance = null; }
        }

        private void EnsureConfigured() {
            if (_authClient == null) {
                throw new InvalidOperationException(
                        "OAuthManager not configured. Call SetServerUrls() first.");
            }
        }

        /// <summary>
        /// Start OAuth login with specified provider.
        /// In editor: Opens browser and polls for completion.
        /// In standalone: Opens browser, quits app, deep link relaunches with token.
        /// </summary>
        public async Task<CheckOAuthStatusResponse> LoginAsync(OAuthProvider provider) {
            EnsureConfigured();
            _currentLoginProvider = provider;  // Track for later storage

            try {
                // Get OAuth URL and state from server
                var (authUrl, state) = await _authClient.GetOAuthUrlAsync(provider);

                // Open system browser
                Debug.Log($"[OAuthManager] Opening browser for OAuth: {authUrl}");
                Application.OpenURL(authUrl);

#if UNITY_EDITOR
                // In editor: poll for OAuth completion since deep links don't work
                Debug.Log("[OAuthManager] Editor mode - polling for OAuth completion");
                var response = await _authClient.PollForOAuthCompletionAsync(state);
                HandleOAuthResponse(response);
                return response;
#elif UNITY_IOS
                // On iOS, Application.Quit() is a no-op. The app goes to background
                // when the browser opens, and the deep link brings it back.
                // OnDeepLinkActivated will handle the auth callback.
                Debug.Log("[OAuthManager] iOS - waiting for deep link callback from browser");
                return null;
#else
                // On desktop, quit the app. The browser's "Return to Eagle" deep link
                // will relaunch a fresh instance with the auth session code.
                Debug.Log("[OAuthManager] Quitting app - will relaunch via deep link after OAuth");
                Application.Quit();
                return null;
#endif
            } catch (Exception ex) {
                Debug.LogWarning($"[OAuthManager] Login failed: {ex.Message}");
                OnLoginFailed?.Invoke(ex.Message);
                throw;
            }
        }

        /// <summary>
        /// Handle OAuth response from polling (editor mode).
        /// </summary>
        private void HandleOAuthResponse(CheckOAuthStatusResponse response) {
            var providerName = response.User.Provider.ToString().ToLowerInvariant();
            TokenStorage.StoreTokens(
                    response.AccessToken,
                    response.RefreshToken,
                    response.ExpiresAt,
                    response.User.UserId,
                    response.User.DisplayName,
                    providerName,
                    response.User.IsAdmin);

            if (response.IsNewUser || string.IsNullOrEmpty(response.User.DisplayName)) {
                Debug.Log("[OAuthManager] New user needs to set display name");
                OnNewUserNeedsDisplayName?.Invoke(true);
            } else {
                Debug.Log($"[OAuthManager] OAuth successful for {response.User.DisplayName}");
                OnLoginSuccess?.Invoke(response.User);
            }
        }

        /// <summary>
        /// Set display name for new user after OAuth.
        /// </summary>
        public async Task<bool> SetDisplayNameAsync(string displayName) {
            EnsureConfigured();
            var response = await _authClient.SetDisplayNameAsync(displayName);

            if (response.Success) { OnLoginSuccess?.Invoke(response.User); }

            return response.Success;
        }

        /// <summary>
        /// Try to restore session from stored tokens.
        /// Returns true if valid session exists.
        /// Must call SetServerUrls() before this method.
        /// </summary>
        public async Task<bool> TryRestoreSessionAsync() {
            if (_authClient == null) {
                Debug.Log("[OAuthManager] Not configured yet, skipping session restore");
                return false;
            }

            if (!TokenStorage.HasValidToken && !TokenStorage.HasRefreshToken) {
                Debug.Log("[OAuthManager] No stored session");
                return false;
            }

            // If token is expired but we have refresh token, try to refresh
            if (!TokenStorage.HasValidToken && TokenStorage.HasRefreshToken) {
                try {
                    await _authClient.RefreshTokenAsync();
                } catch (Exception ex) {
                    Debug.LogWarning($"[OAuthManager] Token refresh failed: {ex.Message}");
                    TokenStorage.Clear();
                    return false;
                }
            }

            // Validate session with server
            try {
                var response = await _authClient.GetCurrentUserAsync();

                // Check if user still needs to set display name (e.g., closed app before setting
                // it)
                if (string.IsNullOrEmpty(response.User.DisplayName)) {
                    Debug.Log("[OAuthManager] Session restored but user needs display name");
                    OnNewUserNeedsDisplayName?.Invoke(false);  // false = not a brand new user
                    return true;
                }

                OnLoginSuccess?.Invoke(response.User);
                return true;
            } catch (RpcException ex) when (ex.StatusCode == StatusCode.NotFound) {
                // User no longer exists on server - remove the invalid account
                var accountKey = TokenStorage.CurrentAccount?.AccountKey;
                Debug.LogWarning(
                        $"[OAuthManager] User not found on server, removing invalid account: {accountKey}");
                if (accountKey != null) { TokenStorage.RemoveAccount(accountKey); }
                return false;
            } catch (Exception ex) {
                Debug.LogWarning($"[OAuthManager] Session validation failed: {ex.Message}");
                TokenStorage.Clear();
                return false;
            }
        }

        /// <summary>
        /// Refresh access token if needed.
        /// Call this before making API requests.
        /// </summary>
        public async Task EnsureValidTokenAsync() {
            EnsureConfigured();
            if (TokenStorage.NeedsRefresh && TokenStorage.HasRefreshToken) {
                await _authClient.RefreshTokenAsync();
            }
        }

        /// <summary>
        /// Logout - clears current session but preserves stored tokens for quick re-login.
        /// </summary>
        public async Task LogoutAsync() {
            // Notify server of logout if connected
            if (_authClient != null) {
                try {
                    await _authClient.LogoutAsync();
                } catch (Exception ex) {
                    Debug.LogWarning($"[OAuthManager] Server logout failed: {ex.Message}");
                }
            }

            // Clear current account selection but keep tokens stored
            TokenStorage.ClearCurrentAccount();
            OnLogout?.Invoke();
        }

        /// <summary>
        /// Get all stored accounts for display in UI.
        /// </summary>
        public IReadOnlyList<StoredAccount> GetStoredAccounts() {
            return TokenStorage.GetAllAccounts();
        }

        /// <summary>
        /// Select and connect with a stored account.
        /// If token is expired or about to expire, refreshes it first.
        /// </summary>
        public async Task<bool> ConnectWithStoredAccountAsync(StoredAccount account) {
            EnsureConfigured();

            // Select this account as current
            TokenStorage.SelectAccount(account.AccountKey);

            // Check if token needs refresh
            if (!account.HasValidToken || account.NeedsRefresh) {
                if (account.HasRefreshToken) {
                    try {
                        await _authClient.RefreshTokenAsync();
                        Debug.Log($"[OAuthManager] Refreshed token for {account.DisplayName}");
                    } catch (Exception ex) {
                        Debug.LogWarning($"[OAuthManager] Token refresh failed: {ex.Message}");
                        // Token refresh failed - need to re-authenticate
                        // Parse provider from account key
                        var provider = account.Provider.ToLowerInvariant() == "google"
                                               ? OAuthProvider.Google
                                               : OAuthProvider.Discord;
                        OnLoginFailed?.Invoke("Session expired. Please sign in again.");
                        return false;
                    }
                } else {
                    OnLoginFailed?.Invoke("Session expired. Please sign in again.");
                    return false;
                }
            }

            // Validate session with server
            try {
                var response = await _authClient.GetCurrentUserAsync();

                // Check if user still needs to set display name
                if (string.IsNullOrEmpty(response.User.DisplayName)) {
                    Debug.Log("[OAuthManager] Stored account needs display name");
                    OnNewUserNeedsDisplayName?.Invoke(false);
                    return true;
                }

                Debug.Log(
                        $"[OAuthManager] Connected with stored account: {response.User.DisplayName}");
                OnLoginSuccess?.Invoke(response.User);
                return true;
            } catch (RpcException ex) when (ex.StatusCode == StatusCode.NotFound) {
                // User no longer exists on server - remove the invalid account
                Debug.LogWarning(
                        $"[OAuthManager] User not found on server, removing account: {account.AccountKey}");
                TokenStorage.RemoveAccount(account.AccountKey);
                OnLoginFailed?.Invoke("Account no longer exists. Please sign in again.");
                return false;
            } catch (Exception ex) {
                Debug.LogWarning($"[OAuthManager] Session validation failed: {ex.Message}");
                OnLoginFailed?.Invoke("Session invalid. Please sign in again.");
                return false;
            }
        }

        /// <summary>
        /// Remove a stored account permanently.
        /// </summary>
        public void RemoveStoredAccount(StoredAccount account) {
            TokenStorage.RemoveAccount(account.AccountKey);
        }
    }
}
