package org.pac4j.core.logout.handler;

import org.pac4j.core.context.WebContext;
import org.pac4j.core.context.session.SessionStore;
import org.pac4j.core.profile.factory.ProfileManagerFactoryAware;
import org.pac4j.core.store.GuavaStore;
import org.pac4j.core.store.Store;
import org.pac4j.core.util.CommonHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Optional;
import java.util.concurrent.TimeUnit;

/**
 * Default logout handler.
 *
 * @author Jerome Leleu
 * @since 2.0.0
 */
public class DefaultLogoutHandler extends ProfileManagerFactoryAware implements LogoutHandler {

    protected final Logger logger = LoggerFactory.getLogger(getClass());

    private Store<String, Object> store = new GuavaStore<>(10000, 30, TimeUnit.MINUTES);

    private boolean destroySession;

    public DefaultLogoutHandler() {}

    public DefaultLogoutHandler(final Store<String, Object> store) {
        this.store = store;
    }

    @Override
    public void recordSession(final WebContext context, final SessionStore sessionStore, final String key) {
        if (sessionStore == null) {
            logger.error("No session store available for this web context");
        } else {
            final var sessionId = sessionStore.getSessionId(context, true).get();
            final var optTrackableSession = sessionStore.getTrackableSession(context);

            if (optTrackableSession.isPresent()) {
                final var trackableSession = optTrackableSession.get();
                logger.debug("key: {} -> trackableSession: {}", key, trackableSession);
                logger.debug("sessionId: {}", sessionId);
                store.set(key, trackableSession);
                store.set(sessionId, key);
            } else {
                logger.debug("No trackable session for the current session store: {}", sessionStore);
            }
        }
    }

    @Override
    public void destroySessionFront(final WebContext context, final SessionStore sessionStore, final String key) {
        if (sessionStore == null) {
            logger.error("No session store available for this web context");
        } else {
            final Optional<String> optCurrentSessionId = sessionStore.getSessionId(context, false);

            if (optCurrentSessionId.isPresent()) {
                store.remove(key);
                final String currentSessionId = optCurrentSessionId.get();
                logger.debug("currentSessionId: {}", currentSessionId);
                final var sessionToKey = (String) store.get(currentSessionId).orElse(null);
                logger.debug("-> key: {}", key);
                store.remove(currentSessionId);

                if (CommonHelper.areEquals(key, sessionToKey)) {
                    destroy(context, sessionStore, "front");
                } else {
                    logger.error("The user profiles (and session) can not be destroyed for the front channel logout because the provided "
                        + "key is not the same as the one linked to the current session");
                }
            } else {
                logger.warn("no session for front channel logout => trying back channel logout");
                destroySessionBack(context, sessionStore, key);
            }
        }
    }

    protected void destroy(final WebContext context, final SessionStore sessionStore, final String channel) {
        // remove profiles
        final var manager = getProfileManager(context, sessionStore);
        manager.removeProfiles();
        logger.debug("{} channel logout call: destroy the user profiles", channel);
        // and optionally the web session
        if (destroySession) {
            logger.debug("destroy the whole session");
            final var invalidated = sessionStore.destroySession(context);
            if (!invalidated) {
                logger.error("The session has not been invalidated");
            }
        }
    }

    @Override
    public void destroySessionBack(final WebContext context, final SessionStore sessionStore, final String key) {
        final var optTrackableSession = store.get(key);
        logger.debug("key: {} -> trackableSession: {}", key, optTrackableSession);
        if (!optTrackableSession.isPresent()) {
            logger.error("No trackable session found for back channel logout. Either the session store does not support to track session "
                + "or it has expired from the store and the store settings must be updated (expired data)");
        } else {
            store.remove(key);

            // renew context with the original session store
            if (sessionStore == null) {
                logger.error("No session store available for this web context");
            } else {
                final var optNewSessionStore = sessionStore
                    .buildFromTrackableSession(context, optTrackableSession.get());
                if (optNewSessionStore.isPresent()) {
                    final var newSessionStore = optNewSessionStore.get();
                    logger.debug("newSesionStore: {}", newSessionStore);
                    final var sessionId = newSessionStore.getSessionId(context, true).get();
                    logger.debug("remove sessionId: {}", sessionId);
                    store.remove(sessionId);

                    destroy(context, newSessionStore, "back");
                } else {
                    logger.error("The session store should be able to build a new session store from the tracked session");
                }
            }
        }
    }

    @Override
    public void renewSession(final String oldSessionId, final WebContext context, final SessionStore sessionStore) {
        final Optional optKey = store.get(oldSessionId);
        logger.debug("oldSessionId: {} -> key: {}", oldSessionId, optKey);
        if (optKey.isPresent()) {
            final var key = (String) optKey.get();
            store.remove(key);
            store.remove(oldSessionId);
            recordSession(context, sessionStore, key);
        }
    }

    public Store<String, Object> getStore() {
        return store;
    }

    public void setStore(final Store<String, Object> store) {
        this.store = store;
    }

    public boolean isDestroySession() {
        return destroySession;
    }

    public void setDestroySession(final boolean destroySession) {
        this.destroySession = destroySession;
    }

    @Override
    public String toString() {
        return CommonHelper.toNiceString(this.getClass(), "store", store, "destroySession", destroySession);
    }
}

@@ -1218,33 +1218,36 @@
       final var 
-s
+optS
 essionId = sessi
@@ -1273,32 +1273,260 @@
 d(context, true)
+;%0A            if (optSessionId.isEmpty()) %7B%0A                logger.warn(%22No session identifier retrieved although the session creation has been requested%22);%0A            %7D else %7B%0A                final var sessionId = optSessionId
 .get();%0A        
@@ -1521,32 +1521,36 @@
 ();%0A            
+ 
+   
 final var optTra
@@ -1617,24 +1617,28 @@
             
+    
 if (optTrack
@@ -1672,32 +1672,36 @@
                 
+  
+  
 final var tracka
@@ -1732,32 +1732,36 @@
 eSession.get();%0A
+    
                 
@@ -1840,32 +1840,36 @@
                 
+   
+ 
 logger.debug(%22se
@@ -1902,32 +1902,36 @@
                 
+    
 store.set(key, t
@@ -1940,32 +1940,36 @@
 ckableSession);%0A
+    
                 
@@ -1999,32 +1999,36 @@
 y);%0A            
+    
 %7D else %7B%0A       
@@ -2012,32 +2012,36 @@
        %7D else %7B%0A
+    
                 
@@ -2118,32 +2118,50 @@
  sessionStore);%0A
+                %7D%0A
             %7D%0A  

fix nosuchelement error on getSessionId (unlikely)