using System;
using System.Threading.Tasks;
using Cysharp.Net.Http;
using Grpc.Core;
using Grpc.Core.Interceptors;
using Grpc.Net.Client;
using Net.Eagle0.Eagle.Api.Auth;
using UnityEngine;
using GrpcAuthClient = Net.Eagle0.Eagle.Api.Auth.Auth.AuthClient;

namespace Auth {
    /// <summary>
    /// gRPC client for the Auth service.
    /// Routes OAuth requests (GetOAuthUrl, CheckOAuthStatus, RefreshToken) to the Go auth service.
    /// Routes user requests (SetDisplayName, GetCurrentUser, Logout) to Eagle.
    /// </summary>
    public class AuthClient : IDisposable {
        private const int PollIntervalMs = 2000;   // Poll every 2 seconds
        private const int PollTimeoutMs = 300000;  // 5 minute timeout

        private readonly GrpcAuthClient _authServiceClient;  // Go auth service
        private readonly GrpcAuthClient _eagleClient;        // Eagle server
        private readonly GrpcChannel _authServiceChannel;
        private readonly GrpcChannel _eagleChannel;

        /// <summary>
        /// Create auth client with separate channels for auth service and Eagle.
        /// </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 AuthClient(string authServiceUrl, string eagleUrl) {
            // Auth service channel (no JWT needed for OAuth flow)
            _authServiceChannel = GrpcChannel.ForAddress(
                    authServiceUrl,
                    new GrpcChannelOptions {
                        HttpHandler = new YetAnotherHttpHandler { Http2Only = true },
                        DisposeHttpClient = true
                    });
            _authServiceClient = new GrpcAuthClient(_authServiceChannel);

            // Eagle channel with JWT interceptor for authenticated requests
            _eagleChannel = GrpcChannel.ForAddress(
                    eagleUrl,
                    new GrpcChannelOptions {
                        HttpHandler = new YetAnotherHttpHandler { Http2Only = true },
                        DisposeHttpClient = true
                    });
            CallInvoker eagleInvoker = _eagleChannel.Intercept(new JwtAuthInterceptor());
            _eagleClient = new GrpcAuthClient(eagleInvoker);
        }

        /// <summary>
        /// Get OAuth URL to open in system browser.
        /// Returns both the URL and the state token for polling.
        /// Routed to Go auth service.
        /// </summary>
        /// <param name="provider">OAuth provider (Discord, Google, GitHub, Apple)</param>
        /// <param name="pollOnly">Whether the client will poll instead of accepting a deep
        /// link</param> <returns>Tuple of (URL to open in browser, state token for
        /// polling)</returns>
        public async Task<(string authUrl, string state)> GetOAuthUrlAsync(
                OAuthProvider provider,
                bool pollOnly = false) {
            var request = new GetOAuthUrlRequest { Provider = provider, PollOnly = pollOnly };
            var response = await _authServiceClient.GetOAuthUrlAsync(request);

            Debug.Log($"[AuthClient] Got OAuth URL for {provider}, state={response.State}");
            return (response.AuthUrl, response.State);
        }

        /// <summary>
        /// Check OAuth status once (non-polling). Used when processing deep links
        /// where we know OAuth has already completed.
        /// Routed to Go auth service.
        /// </summary>
        /// <param name="state">State token from OAuth flow</param>
        /// <returns>Response with status, tokens and user info</returns>
        public async Task<CheckOAuthStatusResponse> CheckOAuthStatusAsync(string state) {
            var request = new CheckOAuthStatusRequest { State = state };
            return await _authServiceClient.CheckOAuthStatusAsync(request);
        }

        /// <summary>
        /// Poll for OAuth completion. Blocks until success, failure, or timeout.
        /// The server handles the OAuth callback and token exchange.
        /// Routed to Go auth service.
        /// </summary>
        /// <param name="state">State token from GetOAuthUrlAsync</param>
        /// <returns>Response with tokens and user info on success</returns>
        public async Task<CheckOAuthStatusResponse> PollForOAuthCompletionAsync(string state) {
            var startTime = DateTime.UtcNow;

            while (true) {
                var request = new CheckOAuthStatusRequest { State = state };
                var response = await _authServiceClient.CheckOAuthStatusAsync(request);

                switch (response.Status) {
                    case OAuthStatus.Success:
                        Debug.Log(
                                $"[AuthClient] OAuth successful, user={response.User?.DisplayName}, isNew={response.IsNewUser}");
                        return response;

                    case OAuthStatus.Failed:
                        throw new Exception($"OAuth failed: {response.ErrorMessage}");

                    case OAuthStatus.Expired:
                        throw new Exception("OAuth session expired. Please try again.");

                    case OAuthStatus.InvitationRequired:
                        throw new Exception(
                                "An invitation is required to create a new account. " +
                                "If you have an invitation link, please use it to sign up. " +
                                "If you already have an account, try signing in with the same provider you originally used.");

                    case OAuthStatus.Pending:
                        // Check timeout
                        if ((DateTime.UtcNow - startTime).TotalMilliseconds > PollTimeoutMs) {
                            throw new TimeoutException("OAuth login timed out. Please try again.");
                        }
                        // Wait before polling again
                        await Task.Delay(PollIntervalMs);
                        break;

                    default: throw new Exception($"Unknown OAuth status: {response.Status}");
                }
            }
        }

        /// <summary>
        /// Refresh access token using stored refresh token.
        /// Routed to Go auth service.
        /// </summary>
        /// <returns>New access token and expiry, or null if refresh failed</returns>
        public async Task<RefreshTokenResponse> RefreshTokenAsync() {
            var refreshToken = TokenStorage.RefreshToken;
            if (string.IsNullOrEmpty(refreshToken)) {
                throw new InvalidOperationException("No refresh token available");
            }

            var request = new RefreshTokenRequest { RefreshToken = refreshToken };

            var response = await _authServiceClient.RefreshTokenAsync(request);

            // Update stored access token
            TokenStorage.UpdateAccessToken(response.AccessToken, response.ExpiresAt);

            Debug.Log($"[AuthClient] Token refreshed, expires at {response.ExpiresAt}");
            return response;
        }

        /// <summary>
        /// Set display name for new users.
        /// Requires valid JWT in interceptor.
        /// Routed to Eagle.
        /// </summary>
        public async Task<SetDisplayNameResponse> SetDisplayNameAsync(string displayName) {
            var request = new SetDisplayNameRequest { DisplayName = displayName };
            var response = await _eagleClient.SetDisplayNameAsync(request);

            if (response.Success) {
                TokenStorage.UpdateDisplayName(displayName);
                // Update the access token with the new one that has updated displayName claim
                if (!string.IsNullOrEmpty(response.AccessToken)) {
                    // Token expires in 7 days from now
                    var expiresAt = DateTimeOffset.UtcNow.AddDays(7).ToUnixTimeSeconds();
                    TokenStorage.UpdateAccessToken(response.AccessToken, expiresAt);
                    Debug.Log($"[AuthClient] Display name set to: {displayName}, token updated");
                } else {
                    Debug.Log($"[AuthClient] Display name set to: {displayName}");
                }
            } else {
                Debug.LogWarning(
                        $"[AuthClient] Failed to set display name: {response.ErrorMessage}");
            }

            return response;
        }

        /// <summary>
        /// Get current user info. Validates the stored JWT.
        /// Routed to Eagle.
        /// </summary>
        public async Task<GetCurrentUserResponse> GetCurrentUserAsync() {
            return await _eagleClient.GetCurrentUserAsync(new GetCurrentUserRequest());
        }

        /// <summary>
        /// Logout - invalidates refresh token on server.
        /// Does NOT clear local tokens (OAuthManager handles that decision).
        /// Routed to Eagle.
        /// </summary>
        public async Task LogoutAsync() {
            try {
                await _eagleClient.LogoutAsync(new LogoutRequest());
                Debug.Log("[AuthClient] Server logout successful");
            } catch (Exception ex) {
                Debug.LogWarning($"[AuthClient] Logout RPC failed (may be expected): {ex.Message}");
            }
        }

        /// <summary>
        /// Exchange a session transfer code for tokens.
        /// Used when user clicks the deep link from the invitation download page.
        /// Routed to Go auth service.
        /// </summary>
        public async Task<ExchangeSessionTransferCodeResponse> ExchangeSessionTransferCodeAsync(
                string code) {
            Debug.Log("[AuthClient] Exchanging session transfer code via gRPC...");

            var request = new ExchangeSessionTransferCodeRequest { Code = code };
            var response = await _authServiceClient.ExchangeSessionTransferCodeAsync(request);

            Debug.Log(
                    $"[AuthClient] Session transfer successful: userId={response.User?.UserId}, displayName={response.User?.DisplayName}");
            return response;
        }

        public void Dispose() {
            _authServiceChannel?.Dispose();
            _eagleChannel?.Dispose();
        }
    }
}
