// 
// Decompiled by Procyon v0.6.0
// 

package com.hypixel.hytale.server.core.auth;

import com.hypixel.hytale.codec.lookup.Priority;
import com.hypixel.hytale.server.core.auth.oauth.OAuthResult;
import java.util.concurrent.TimeUnit;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import com.hypixel.hytale.server.core.auth.oauth.OAuthDeviceFlow;
import java.util.concurrent.CompletableFuture;
import com.hypixel.hytale.server.core.auth.oauth.OAuthBrowserFlow;
import javax.annotation.Nullable;
import com.hypixel.hytale.server.core.ShutdownReason;
import com.hypixel.hytale.server.core.HytaleServer;
import joptsimple.OptionSet;
import joptsimple.OptionSpec;
import java.util.logging.Level;
import com.hypixel.hytale.server.core.Options;
import javax.annotation.Nonnull;
import java.util.concurrent.Executors;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledExecutorService;
import com.hypixel.hytale.server.core.auth.oauth.OAuthClient;
import java.security.cert.X509Certificate;
import java.util.UUID;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import java.time.Instant;
import com.hypixel.hytale.logger.HytaleLogger;

public class ServerAuthManager
{
    private static final HytaleLogger LOGGER;
    private static final int REFRESH_BUFFER_SECONDS = 300;
    private static volatile ServerAuthManager instance;
    private volatile AuthMode authMode;
    private volatile Instant tokenExpiry;
    private final AtomicReference<SessionServiceClient.GameSessionResponse> gameSession;
    private final AtomicReference<IAuthCredentialStore> credentialStore;
    private final Map<UUID, SessionServiceClient.GameProfile> availableProfiles;
    private volatile SessionServiceClient.GameProfile[] pendingProfiles;
    private volatile AuthMode pendingAuthMode;
    private final AtomicReference<X509Certificate> serverCertificate;
    private final UUID serverSessionId;
    private volatile boolean isSingleplayer;
    private OAuthClient oauthClient;
    private volatile SessionServiceClient sessionServiceClient;
    private volatile ProfileServiceClient profileServiceClient;
    private final ScheduledExecutorService refreshScheduler;
    private ScheduledFuture<?> refreshTask;
    private Runnable cancelActiveFlow;
    private volatile String pendingFatalError;
    
    private ServerAuthManager() {
        this.authMode = AuthMode.NONE;
        this.gameSession = new AtomicReference<SessionServiceClient.GameSessionResponse>();
        this.credentialStore = new AtomicReference<IAuthCredentialStore>(new DefaultAuthCredentialStore());
        this.availableProfiles = new ConcurrentHashMap<UUID, SessionServiceClient.GameProfile>();
        this.serverCertificate = new AtomicReference<X509Certificate>();
        this.serverSessionId = UUID.randomUUID();
        this.refreshScheduler = Executors.newSingleThreadScheduledExecutor(r -> {
            final Thread t = new Thread(r, "TokenRefresh");
            t.setDaemon(true);
            return t;
        });
    }
    
    public static ServerAuthManager getInstance() {
        if (ServerAuthManager.instance == null) {
            synchronized (ServerAuthManager.class) {
                if (ServerAuthManager.instance == null) {
                    ServerAuthManager.instance = new ServerAuthManager();
                }
            }
        }
        return ServerAuthManager.instance;
    }
    
    @Nonnull
    public ProfileServiceClient getProfileServiceClient() {
        if (this.profileServiceClient == null) {
            synchronized (this) {
                if (this.profileServiceClient == null) {
                    this.profileServiceClient = new ProfileServiceClient("https://account-data.hytale.com");
                }
            }
        }
        return this.profileServiceClient;
    }
    
    public void initialize() {
        final OptionSet optionSet = Options.getOptionSet();
        if (optionSet == null) {
            ServerAuthManager.LOGGER.at(Level.WARNING).log("Options not parsed, cannot initialize ServerAuthManager");
            return;
        }
        this.oauthClient = new OAuthClient();
        this.isSingleplayer = optionSet.has(Options.SINGLEPLAYER);
        if (this.isSingleplayer && optionSet.has(Options.OWNER_UUID)) {
            final SessionServiceClient.GameProfile ownerProfile = new SessionServiceClient.GameProfile();
            ownerProfile.uuid = optionSet.valueOf(Options.OWNER_UUID);
            ownerProfile.username = (optionSet.has(Options.OWNER_NAME) ? optionSet.valueOf(Options.OWNER_NAME) : null);
            this.credentialStore.get().setProfile(ownerProfile.uuid);
            ServerAuthManager.LOGGER.at(Level.INFO).log("Singleplayer mode, owner: %s (%s)", ownerProfile.username, ownerProfile.uuid);
        }
        boolean hasCliTokens = false;
        String sessionTokenValue = null;
        String identityTokenValue = null;
        if (optionSet.has(Options.SESSION_TOKEN)) {
            sessionTokenValue = optionSet.valueOf(Options.SESSION_TOKEN);
            ServerAuthManager.LOGGER.at(Level.INFO).log("Session token loaded from CLI");
        }
        else {
            final String envToken = System.getenv("HYTALE_SERVER_SESSION_TOKEN");
            if (envToken != null && !envToken.isEmpty()) {
                sessionTokenValue = envToken;
                ServerAuthManager.LOGGER.at(Level.INFO).log("Session token loaded from environment");
            }
        }
        if (optionSet.has(Options.IDENTITY_TOKEN)) {
            identityTokenValue = optionSet.valueOf(Options.IDENTITY_TOKEN);
            ServerAuthManager.LOGGER.at(Level.INFO).log("Identity token loaded from CLI");
        }
        else {
            final String envToken = System.getenv("HYTALE_SERVER_IDENTITY_TOKEN");
            if (envToken != null && !envToken.isEmpty()) {
                identityTokenValue = envToken;
                ServerAuthManager.LOGGER.at(Level.INFO).log("Identity token loaded from environment");
            }
        }
        if (sessionTokenValue != null || identityTokenValue != null) {
            if (this.validateInitialTokens(sessionTokenValue, identityTokenValue)) {
                final SessionServiceClient.GameSessionResponse session = new SessionServiceClient.GameSessionResponse();
                session.sessionToken = sessionTokenValue;
                session.identityToken = identityTokenValue;
                this.gameSession.set(session);
                hasCliTokens = true;
            }
            else {
                this.pendingFatalError = "Token validation failed. Provided tokens may be expired, tampered, or malformed. Remove invalid tokens or provide valid ones.";
                ServerAuthManager.LOGGER.at(Level.SEVERE).log(this.pendingFatalError);
            }
        }
        if (hasCliTokens) {
            if (this.isSingleplayer) {
                this.authMode = AuthMode.SINGLEPLAYER;
                ServerAuthManager.LOGGER.at(Level.INFO).log("Auth mode: SINGLEPLAYER");
            }
            else {
                this.authMode = AuthMode.EXTERNAL_SESSION;
                ServerAuthManager.LOGGER.at(Level.INFO).log("Auth mode: EXTERNAL_SESSION");
            }
            this.parseAndScheduleRefresh();
        }
        else {
            ServerAuthManager.LOGGER.at(Level.INFO).log("No server tokens configured. Use /auth login to authenticate, or provide tokens via CLI/environment.");
        }
        ServerAuthManager.LOGGER.at(Level.INFO).log("Server session ID: %s", this.serverSessionId);
        ServerAuthManager.LOGGER.at(Level.FINE).log("ServerAuthManager initialized - session token: %s, identity token: %s, auth mode: %s", this.hasSessionToken() ? "present" : "missing", this.hasIdentityToken() ? "present" : "missing", this.authMode);
    }
    
    public void checkPendingFatalError() {
        if (this.pendingFatalError == null) {
            return;
        }
        HytaleServer.get().shutdownServer(ShutdownReason.AUTH_FAILED.withMessage(this.pendingFatalError));
    }
    
    public void initializeCredentialStore() {
        final AuthCredentialStoreProvider provider = HytaleServer.get().getConfig().getAuthCredentialStoreProvider();
        this.credentialStore.set(provider.createStore());
        ServerAuthManager.LOGGER.at(Level.INFO).log("Auth credential store: %s", AuthCredentialStoreProvider.CODEC.getIdFor((Class<?>)provider.getClass()));
        final IAuthCredentialStore store = this.credentialStore.get();
        final IAuthCredentialStore.OAuthTokens tokens = store.getTokens();
        if (tokens.isValid()) {
            ServerAuthManager.LOGGER.at(Level.INFO).log("Found stored credentials, attempting to restore session...");
            final AuthResult result = this.createGameSessionFromOAuth(AuthMode.OAUTH_STORE);
            if (result == AuthResult.SUCCESS) {
                ServerAuthManager.LOGGER.at(Level.INFO).log("Session restored from stored credentials");
            }
            else if (result == AuthResult.PENDING_PROFILE_SELECTION) {
                ServerAuthManager.LOGGER.at(Level.INFO).log("Session restored but profile selection required - use /auth select");
            }
            else {
                ServerAuthManager.LOGGER.at(Level.WARNING).log("Failed to restore session from stored credentials");
            }
        }
    }
    
    public void shutdown() {
        this.cancelActiveFlow();
        if (this.refreshTask != null) {
            this.refreshTask.cancel(false);
        }
        this.refreshScheduler.shutdown();
        if (this.isSingleplayer()) {
            final String currentSessionToken = this.getSessionToken();
            if (currentSessionToken != null && !currentSessionToken.isEmpty()) {
                if (this.sessionServiceClient == null) {
                    this.sessionServiceClient = new SessionServiceClient("https://sessions.hytale.com");
                }
                this.sessionServiceClient.terminateSession(currentSessionToken);
            }
        }
    }
    
    public void logout() {
        this.cancelActiveFlow();
        if (this.refreshTask != null) {
            this.refreshTask.cancel(false);
            this.refreshTask = null;
        }
        this.gameSession.set(null);
        this.credentialStore.get().clear();
        this.availableProfiles.clear();
        this.pendingProfiles = null;
        this.pendingAuthMode = null;
        this.tokenExpiry = null;
        this.authMode = AuthMode.NONE;
        ServerAuthManager.LOGGER.at(Level.INFO).log("Server logged out");
    }
    
    @Nullable
    public SessionServiceClient.GameSessionResponse getGameSession() {
        return this.gameSession.get();
    }
    
    public void setGameSession(@Nonnull final SessionServiceClient.GameSessionResponse session) {
        this.gameSession.set(session);
        ServerAuthManager.LOGGER.at(Level.FINE).log("Game session updated");
    }
    
    @Nullable
    public String getIdentityToken() {
        final SessionServiceClient.GameSessionResponse session = this.gameSession.get();
        return (session != null) ? session.identityToken : null;
    }
    
    @Nullable
    public String getSessionToken() {
        final SessionServiceClient.GameSessionResponse session = this.gameSession.get();
        return (session != null) ? session.sessionToken : null;
    }
    
    public boolean hasIdentityToken() {
        final SessionServiceClient.GameSessionResponse session = this.gameSession.get();
        return session != null && session.identityToken != null;
    }
    
    public boolean hasSessionToken() {
        final SessionServiceClient.GameSessionResponse session = this.gameSession.get();
        return session != null && session.sessionToken != null;
    }
    
    @Nullable
    public String getOAuthAccessToken() {
        if (!this.refreshOAuthTokens()) {
            return null;
        }
        final IAuthCredentialStore store = this.credentialStore.get();
        return store.getTokens().accessToken();
    }
    
    public void setServerCertificate(@Nonnull final X509Certificate certificate) {
        this.serverCertificate.set(certificate);
        ServerAuthManager.LOGGER.at(Level.INFO).log("Server certificate set: %s", certificate.getSubjectX500Principal());
    }
    
    @Nullable
    public X509Certificate getServerCertificate() {
        return this.serverCertificate.get();
    }
    
    @Nullable
    public String getServerCertificateFingerprint() {
        final X509Certificate cert = this.serverCertificate.get();
        if (cert == null) {
            return null;
        }
        return CertificateUtil.computeCertificateFingerprint(cert);
    }
    
    @Nonnull
    public UUID getServerSessionId() {
        return this.serverSessionId;
    }
    
    public AuthMode getAuthMode() {
        return this.authMode;
    }
    
    public boolean isSingleplayer() {
        return this.isSingleplayer;
    }
    
    public boolean isOwner(@Nullable final UUID playerUuid) {
        final UUID profileUuid = this.credentialStore.get().getProfile();
        return profileUuid != null && profileUuid.equals(playerUuid);
    }
    
    @Nullable
    public SessionServiceClient.GameProfile getSelectedProfile() {
        final UUID profileUuid = this.credentialStore.get().getProfile();
        if (profileUuid == null) {
            return null;
        }
        return this.availableProfiles.get(profileUuid);
    }
    
    @Nullable
    public Instant getTokenExpiry() {
        return this.tokenExpiry;
    }
    
    public String getAuthStatus() {
        final StringBuilder sb = new StringBuilder();
        sb.append(this.authMode.name());
        if (this.hasSessionToken() && this.hasIdentityToken()) {
            sb.append(" (authenticated)");
        }
        else if (this.hasSessionToken() || this.hasIdentityToken()) {
            sb.append(" (partial)");
        }
        else {
            sb.append(" (no tokens)");
        }
        if (this.tokenExpiry != null) {
            final long secondsRemaining = this.tokenExpiry.getEpochSecond() - Instant.now().getEpochSecond();
            if (secondsRemaining > 0L) {
                sb.append(String.format(" [expires in %dm %ds]", secondsRemaining / 60L, secondsRemaining % 60L));
            }
            else {
                sb.append(" [EXPIRED]");
            }
        }
        return sb.toString();
    }
    
    public CompletableFuture<AuthResult> startFlowAsync(@Nonnull final OAuthBrowserFlow flow) {
        if (this.isSingleplayer) {
            return CompletableFuture.completedFuture(AuthResult.FAILED);
        }
        this.cancelActiveFlow();
        this.cancelActiveFlow = this.oauthClient.startFlow(flow);
        return flow.getFuture().thenApply(res -> {
            switch (res) {
                case SUCCESS: {
                    final IAuthCredentialStore store = this.credentialStore.get();
                    final OAuthClient.TokenResponse tokens = flow.getTokenResponse();
                    store.setTokens(new IAuthCredentialStore.OAuthTokens(tokens.accessToken(), tokens.refreshToken(), Instant.now().plusSeconds(tokens.expiresIn())));
                    return this.createGameSessionFromOAuth(AuthMode.OAUTH_BROWSER);
                }
                case FAILED: {
                    ServerAuthManager.LOGGER.at(Level.WARNING).log("OAuth browser flow failed: %s", flow.getErrorMessage());
                    return AuthResult.FAILED;
                }
                default: {
                    ServerAuthManager.LOGGER.at(Level.WARNING).log("OAuth browser flow completed with unexpected result: %v", res);
                    return AuthResult.FAILED;
                }
            }
        });
    }
    
    public CompletableFuture<AuthResult> startFlowAsync(final OAuthDeviceFlow flow) {
        if (this.isSingleplayer) {
            return CompletableFuture.completedFuture(AuthResult.FAILED);
        }
        this.cancelActiveFlow();
        this.cancelActiveFlow = this.oauthClient.startFlow(flow);
        return flow.getFuture().thenApply(res -> {
            switch (res) {
                case SUCCESS: {
                    final IAuthCredentialStore store = this.credentialStore.get();
                    final OAuthClient.TokenResponse tokens = flow.getTokenResponse();
                    store.setTokens(new IAuthCredentialStore.OAuthTokens(tokens.accessToken(), tokens.refreshToken(), Instant.now().plusSeconds(tokens.expiresIn())));
                    return this.createGameSessionFromOAuth(AuthMode.OAUTH_DEVICE);
                }
                case FAILED: {
                    ServerAuthManager.LOGGER.at(Level.WARNING).log("OAuth device flow failed: %s", flow.getErrorMessage());
                    return AuthResult.FAILED;
                }
                default: {
                    ServerAuthManager.LOGGER.at(Level.WARNING).log("OAuth device flow completed with unexpected result: %v", res);
                    return AuthResult.FAILED;
                }
            }
        });
    }
    
    public CompletableFuture<AuthResult> registerCredentialStore(final IAuthCredentialStore store) {
        if (this.isSingleplayer) {
            return CompletableFuture.completedFuture(AuthResult.FAILED);
        }
        if (this.hasSessionToken() && this.hasIdentityToken()) {
            return CompletableFuture.completedFuture(AuthResult.FAILED);
        }
        this.credentialStore.set(store);
        return CompletableFuture.completedFuture(this.createGameSessionFromOAuth(AuthMode.OAUTH_STORE));
    }
    
    public void swapCredentialStoreProvider(@Nonnull final AuthCredentialStoreProvider provider) {
        final IAuthCredentialStore oldStore = this.credentialStore.get();
        final IAuthCredentialStore newStore = provider.createStore();
        final IAuthCredentialStore.OAuthTokens tokens = oldStore.getTokens();
        if (tokens.isValid()) {
            newStore.setTokens(tokens);
        }
        final UUID profile = oldStore.getProfile();
        if (profile != null) {
            newStore.setProfile(profile);
        }
        this.credentialStore.set(newStore);
        ServerAuthManager.LOGGER.at(Level.INFO).log("Swapped credential store to: %s", provider.getClass().getSimpleName());
    }
    
    public boolean cancelActiveFlow() {
        if (this.cancelActiveFlow != null) {
            this.cancelActiveFlow.run();
            this.cancelActiveFlow = null;
            return true;
        }
        return false;
    }
    
    @Nullable
    public SessionServiceClient.GameProfile[] getPendingProfiles() {
        return this.pendingProfiles;
    }
    
    public boolean hasPendingProfiles() {
        return this.pendingProfiles != null && this.pendingProfiles.length > 0;
    }
    
    public boolean selectPendingProfile(final int index) {
        final SessionServiceClient.GameProfile[] profiles = this.pendingProfiles;
        final AuthMode mode = this.pendingAuthMode;
        if (profiles == null || profiles.length == 0) {
            ServerAuthManager.LOGGER.at(Level.WARNING).log("No pending profiles to select");
            return false;
        }
        if (index < 1 || index > profiles.length) {
            ServerAuthManager.LOGGER.at(Level.WARNING).log("Invalid profile index: %d (valid range: 1-%d)", index, profiles.length);
            return false;
        }
        final SessionServiceClient.GameProfile selected = profiles[index - 1];
        ServerAuthManager.LOGGER.at(Level.INFO).log("Selected profile: %s (%s)", selected.username, selected.uuid);
        return this.completeAuthWithProfile(selected, (mode != null) ? mode : AuthMode.OAUTH_BROWSER);
    }
    
    public boolean selectPendingProfileByUsername(final String username) {
        final SessionServiceClient.GameProfile[] profiles = this.pendingProfiles;
        final AuthMode mode = this.pendingAuthMode;
        if (profiles == null || profiles.length == 0) {
            ServerAuthManager.LOGGER.at(Level.WARNING).log("No pending profiles to select");
            return false;
        }
        for (final SessionServiceClient.GameProfile profile : profiles) {
            if (profile.username != null && profile.username.equalsIgnoreCase(username)) {
                ServerAuthManager.LOGGER.at(Level.INFO).log("Selected profile: %s (%s)", profile.username, profile.uuid);
                return this.completeAuthWithProfile(profile, (mode != null) ? mode : AuthMode.OAUTH_BROWSER);
            }
        }
        ServerAuthManager.LOGGER.at(Level.WARNING).log("No profile found with username: %s", username);
        return false;
    }
    
    public void clearPendingProfiles() {
        this.pendingProfiles = null;
        this.pendingAuthMode = null;
    }
    
    private boolean validateInitialTokens(@Nullable final String sessionToken, @Nullable final String identityToken) {
        if (sessionToken == null && identityToken == null) {
            return false;
        }
        if (this.sessionServiceClient == null) {
            this.sessionServiceClient = new SessionServiceClient("https://sessions.hytale.com");
        }
        final JWTValidator validator = new JWTValidator(this.sessionServiceClient, "https://sessions.hytale.com", "");
        boolean valid = true;
        final OptionSet optionSet = Options.getOptionSet();
        final UUID expectedOwnerUuid = (optionSet != null && optionSet.has(Options.OWNER_UUID)) ? optionSet.valueOf(Options.OWNER_UUID) : null;
        final String expectedOwnerName = (optionSet != null && optionSet.has(Options.OWNER_NAME)) ? optionSet.valueOf(Options.OWNER_NAME) : null;
        if (identityToken != null) {
            final JWTValidator.IdentityTokenClaims claims = validator.validateIdentityToken(identityToken);
            if (claims == null) {
                ServerAuthManager.LOGGER.at(Level.WARNING).log("Identity token validation failed");
                valid = false;
            }
            else if (!claims.hasScope("hytale:server")) {
                ServerAuthManager.LOGGER.at(Level.WARNING).log("Identity token missing required scope: expected %s, got %s", "hytale:server", claims.scope);
                valid = false;
            }
            else {
                if (expectedOwnerUuid != null) {
                    final UUID tokenUuid = claims.getSubjectAsUUID();
                    if (tokenUuid == null || !tokenUuid.equals(expectedOwnerUuid)) {
                        ServerAuthManager.LOGGER.at(Level.WARNING).log("Identity token UUID mismatch: token has %s, expected %s", claims.subject, expectedOwnerUuid);
                        valid = false;
                    }
                }
                if (expectedOwnerName != null && claims.username != null && !claims.username.equals(expectedOwnerName)) {
                    ServerAuthManager.LOGGER.at(Level.WARNING).log("Identity token username mismatch: token has '%s', expected '%s'", claims.username, expectedOwnerName);
                    valid = false;
                }
                if (valid) {
                    ServerAuthManager.LOGGER.at(Level.INFO).log("Identity token validated for %s (%s)", claims.username, claims.subject);
                }
            }
        }
        if (sessionToken != null) {
            final JWTValidator.SessionTokenClaims claims2 = validator.validateSessionToken(sessionToken);
            if (claims2 == null) {
                ServerAuthManager.LOGGER.at(Level.WARNING).log("Session token validation failed");
                valid = false;
            }
            else {
                if (expectedOwnerUuid != null) {
                    final UUID tokenUuid = claims2.getSubjectAsUUID();
                    if (tokenUuid == null || !tokenUuid.equals(expectedOwnerUuid)) {
                        ServerAuthManager.LOGGER.at(Level.WARNING).log("Session token UUID mismatch: token has %s, expected %s", claims2.subject, expectedOwnerUuid);
                        valid = false;
                    }
                }
                if (valid) {
                    ServerAuthManager.LOGGER.at(Level.INFO).log("Session token validated");
                }
            }
        }
        return valid;
    }
    
    private AuthResult createGameSessionFromOAuth(final AuthMode mode) {
        if (!this.refreshOAuthTokens()) {
            ServerAuthManager.LOGGER.at(Level.WARNING).log("No valid OAuth tokens to create game session");
            return AuthResult.FAILED;
        }
        final IAuthCredentialStore store = this.credentialStore.get();
        final String accessToken = store.getTokens().accessToken();
        if (accessToken == null) {
            ServerAuthManager.LOGGER.at(Level.WARNING).log("No access token in credential store");
            return AuthResult.FAILED;
        }
        if (this.sessionServiceClient == null) {
            this.sessionServiceClient = new SessionServiceClient("https://sessions.hytale.com");
        }
        final SessionServiceClient.GameProfile[] profiles = this.sessionServiceClient.getGameProfiles(accessToken);
        if (profiles == null || profiles.length == 0) {
            ServerAuthManager.LOGGER.at(Level.WARNING).log("No game profiles found for this account");
            return AuthResult.FAILED;
        }
        this.availableProfiles.clear();
        for (final SessionServiceClient.GameProfile profile : profiles) {
            this.availableProfiles.put(profile.uuid, profile);
        }
        final SessionServiceClient.GameProfile profile2 = this.tryAutoSelectProfile(profiles);
        if (profile2 == null) {
            this.pendingProfiles = profiles;
            this.pendingAuthMode = mode;
            this.cancelActiveFlow = null;
            ServerAuthManager.LOGGER.at(Level.INFO).log("Multiple profiles available. Use '/auth select <number>' to choose:");
            for (int i = 0; i < profiles.length; ++i) {
                ServerAuthManager.LOGGER.at(Level.INFO).log("  [%d] %s (%s)", i + 1, profiles[i].username, profiles[i].uuid);
            }
            return AuthResult.PENDING_PROFILE_SELECTION;
        }
        return this.completeAuthWithProfile(profile2, mode) ? AuthResult.SUCCESS : AuthResult.FAILED;
    }
    
    private boolean refreshOAuthTokens() {
        return this.refreshOAuthTokens(false);
    }
    
    private boolean refreshOAuthTokens(final boolean force) {
        final IAuthCredentialStore store = this.credentialStore.get();
        final IAuthCredentialStore.OAuthTokens tokens = store.getTokens();
        final Instant expiresAt = tokens.accessTokenExpiresAt();
        if (!force && expiresAt != null && !expiresAt.isBefore(Instant.now().plusSeconds(300L))) {
            return true;
        }
        final String refreshToken = tokens.refreshToken();
        if (refreshToken == null) {
            ServerAuthManager.LOGGER.at(Level.WARNING).log("No refresh token present to refresh OAuth tokens");
            return false;
        }
        ServerAuthManager.LOGGER.at(Level.INFO).log("Refreshing OAuth tokens...");
        final OAuthClient.TokenResponse newTokens = this.oauthClient.refreshTokens(refreshToken);
        if (newTokens == null || !newTokens.isSuccess()) {
            ServerAuthManager.LOGGER.at(Level.WARNING).log("OAuth token refresh failed");
            return false;
        }
        store.setTokens(new IAuthCredentialStore.OAuthTokens(newTokens.accessToken(), newTokens.refreshToken(), Instant.now().plusSeconds(newTokens.expiresIn())));
        return true;
    }
    
    @Nullable
    private SessionServiceClient.GameProfile tryAutoSelectProfile(final SessionServiceClient.GameProfile[] profiles) {
        final OptionSet optionSet = Options.getOptionSet();
        if (optionSet != null && optionSet.has(Options.OWNER_UUID)) {
            final UUID requestedUuid = optionSet.valueOf(Options.OWNER_UUID);
            for (final SessionServiceClient.GameProfile profile : profiles) {
                if (profile.uuid.equals(requestedUuid)) {
                    ServerAuthManager.LOGGER.at(Level.INFO).log("Selected profile from --owner-uuid: %s (%s)", profile.username, profile.uuid);
                    return profile;
                }
            }
            ServerAuthManager.LOGGER.at(Level.WARNING).log("Specified --owner-uuid %s not found in available profiles", requestedUuid);
            return null;
        }
        if (profiles.length == 1) {
            ServerAuthManager.LOGGER.at(Level.INFO).log("Auto-selected profile: %s (%s)", profiles[0].username, profiles[0].uuid);
            return profiles[0];
        }
        final UUID profileUuid = this.credentialStore.get().getProfile();
        if (profileUuid != null) {
            for (final SessionServiceClient.GameProfile profile : profiles) {
                if (profile.uuid.equals(profileUuid)) {
                    ServerAuthManager.LOGGER.at(Level.INFO).log("Auto-selected profile from storage: %s (%s)", profile.username, profile.uuid);
                    return profile;
                }
            }
        }
        return null;
    }
    
    private boolean completeAuthWithProfile(final SessionServiceClient.GameProfile profile, final AuthMode mode) {
        final SessionServiceClient.GameSessionResponse newSession = this.createGameSession(profile.uuid);
        if (newSession == null) {
            ServerAuthManager.LOGGER.at(Level.WARNING).log("Failed to create game session");
            return false;
        }
        this.gameSession.set(newSession);
        this.authMode = mode;
        this.cancelActiveFlow = null;
        this.pendingProfiles = null;
        this.pendingAuthMode = null;
        final Instant effectiveExpiry = this.getEffectiveExpiry(newSession);
        if (effectiveExpiry != null) {
            this.setExpiryAndScheduleRefresh(effectiveExpiry);
        }
        ServerAuthManager.LOGGER.at(Level.INFO).log("Authentication successful! Mode: %s", mode);
        return true;
    }
    
    @Nullable
    private SessionServiceClient.GameSessionResponse createGameSession(final UUID profileUuid) {
        if (this.sessionServiceClient == null) {
            this.sessionServiceClient = new SessionServiceClient("https://sessions.hytale.com");
        }
        if (!this.refreshOAuthTokens()) {
            ServerAuthManager.LOGGER.at(Level.WARNING).log("OAuth token refresh for game session creation failed");
            return null;
        }
        final IAuthCredentialStore store = this.credentialStore.get();
        final String accessToken = store.getTokens().accessToken();
        SessionServiceClient.GameSessionResponse result = this.sessionServiceClient.createGameSession(accessToken, profileUuid);
        if (result == null) {
            ServerAuthManager.LOGGER.at(Level.WARNING).log("Trying force refresh of OAuth tokens because game session creation failed");
            if (!this.refreshOAuthTokens(true)) {
                ServerAuthManager.LOGGER.at(Level.WARNING).log("Force refresh failed");
                return null;
            }
            result = this.sessionServiceClient.createGameSession(accessToken, profileUuid);
            if (result == null) {
                ServerAuthManager.LOGGER.at(Level.WARNING).log("Game session creation with force refreshed tokens failed");
                return null;
            }
        }
        store.setProfile(profileUuid);
        return result;
    }
    
    private void parseAndScheduleRefresh() {
        final SessionServiceClient.GameSessionResponse session = this.gameSession.get();
        final Instant effectiveExpiry = this.getEffectiveExpiry(session);
        if (effectiveExpiry != null) {
            this.setExpiryAndScheduleRefresh(effectiveExpiry);
        }
    }
    
    @Nullable
    private Instant getEffectiveExpiry(@Nullable final SessionServiceClient.GameSessionResponse session) {
        final Instant sessionExpiry = (session != null) ? session.getExpiresAtInstant() : null;
        final Instant identityExpiry = this.parseIdentityTokenExpiry((session != null) ? session.identityToken : this.getIdentityToken());
        if (sessionExpiry != null && identityExpiry != null) {
            return sessionExpiry.isBefore(identityExpiry) ? sessionExpiry : identityExpiry;
        }
        return (sessionExpiry != null) ? sessionExpiry : identityExpiry;
    }
    
    @Nullable
    private Instant parseIdentityTokenExpiry(@Nullable final String idToken) {
        if (idToken == null) {
            return null;
        }
        try {
            final String[] parts = idToken.split("\\.");
            if (parts.length != 3) {
                return null;
            }
            final String payload = new String(Base64.getUrlDecoder().decode(parts[1]), StandardCharsets.UTF_8);
            final JsonObject json = JsonParser.parseString(payload).getAsJsonObject();
            if (json.has("exp")) {
                return Instant.ofEpochSecond(json.get("exp").getAsLong());
            }
        }
        catch (final Exception e) {
            ServerAuthManager.LOGGER.at(Level.WARNING).withCause(e).log("Failed to parse identity token expiry");
        }
        return null;
    }
    
    private void setExpiryAndScheduleRefresh(@Nonnull final Instant expiry) {
        this.tokenExpiry = expiry;
        if (this.refreshTask != null) {
            this.refreshTask.cancel(false);
        }
        final long secondsUntilExpiry = expiry.getEpochSecond() - Instant.now().getEpochSecond();
        if (secondsUntilExpiry <= 300L) {
            return;
        }
        final long refreshDelay = Math.max(secondsUntilExpiry - 300L, 60L);
        ServerAuthManager.LOGGER.at(Level.INFO).log("Token refresh scheduled in %d seconds", refreshDelay);
        this.refreshTask = this.refreshScheduler.schedule(this::doRefresh, refreshDelay, TimeUnit.SECONDS);
    }
    
    private void doRefresh() {
        final String currentSessionToken = this.getSessionToken();
        if (currentSessionToken != null && this.refreshGameSession(currentSessionToken)) {
            return;
        }
        ServerAuthManager.LOGGER.at(Level.INFO).log("Game session refresh failed, attempting OAuth refresh...");
        if (this.refreshGameSessionViaOAuth()) {
            return;
        }
        ServerAuthManager.LOGGER.at(Level.WARNING).log("All refresh attempts failed. Server may lose authentication.");
    }
    
    private boolean refreshGameSession(final String currentSessionToken) {
        ServerAuthManager.LOGGER.at(Level.INFO).log("Refreshing game session with Session Service...");
        if (this.sessionServiceClient == null) {
            this.sessionServiceClient = new SessionServiceClient("https://sessions.hytale.com");
        }
        try {
            final SessionServiceClient.GameSessionResponse response = this.sessionServiceClient.refreshSessionAsync(currentSessionToken).join();
            if (response != null) {
                this.gameSession.set(response);
                final Instant effectiveExpiry = this.getEffectiveExpiry(response);
                if (effectiveExpiry != null) {
                    this.setExpiryAndScheduleRefresh(effectiveExpiry);
                }
                ServerAuthManager.LOGGER.at(Level.INFO).log("Game session refresh successful");
                return true;
            }
        }
        catch (final Exception e) {
            ServerAuthManager.LOGGER.at(Level.WARNING).log("Session Service refresh failed: %s", e.getMessage());
        }
        return false;
    }
    
    private boolean refreshGameSessionViaOAuth() {
        final boolean supported = switch (this.authMode.ordinal()) {
            case 3,  4,  5 -> true;
            default -> false;
        };
        if (!supported) {
            ServerAuthManager.LOGGER.at(Level.WARNING).log("Refresh via OAuth not supported for current Auth Mode");
            return false;
        }
        final UUID currentProfile = this.credentialStore.get().getProfile();
        if (currentProfile == null) {
            ServerAuthManager.LOGGER.at(Level.WARNING).log("No current profile, cannot refresh game session");
            return false;
        }
        final SessionServiceClient.GameSessionResponse newSession = this.createGameSession(currentProfile);
        if (newSession == null) {
            ServerAuthManager.LOGGER.at(Level.WARNING).log("Failed to create new game session");
            return false;
        }
        this.gameSession.set(newSession);
        final Instant effectiveExpiry = this.getEffectiveExpiry(newSession);
        if (effectiveExpiry != null) {
            this.setExpiryAndScheduleRefresh(effectiveExpiry);
        }
        ServerAuthManager.LOGGER.at(Level.INFO).log("New game session created via OAuth refresh");
        return true;
    }
    
    static {
        LOGGER = HytaleLogger.forEnclosingClass();
        AuthCredentialStoreProvider.CODEC.register(Priority.DEFAULT, "Memory", (Class<?>)MemoryAuthCredentialStoreProvider.class, MemoryAuthCredentialStoreProvider.CODEC);
        AuthCredentialStoreProvider.CODEC.register("Encrypted", (Class<?>)EncryptedAuthCredentialStoreProvider.class, (C)EncryptedAuthCredentialStoreProvider.CODEC);
    }
    
    public enum AuthMode
    {
        NONE, 
        SINGLEPLAYER, 
        EXTERNAL_SESSION, 
        OAUTH_BROWSER, 
        OAUTH_DEVICE, 
        OAUTH_STORE;
    }
    
    public enum AuthResult
    {
        SUCCESS, 
        PENDING_PROFILE_SELECTION, 
        FAILED;
    }
}
