package com.airlock.iam.saml2.infrastructure.plugin;

import com.airlock.iam.base.api.domain.model.user.UserId;
import com.airlock.iam.common.api.application.dependency.injection.LegacyPluginInjectorFactory;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import com.airlock.iam.saml2.domain.model.session.FlowFederationSession;
import com.airlock.iam.saml2.domain.model.session.FlowFederationSessionId;
import com.airlock.iam.saml2.domain.model.session.FlowFederationSessionRepository;
import com.airlock.iam.saml2.infrastructure.util.Saml2RequestThreadContext;
import com.sun.identity.plugin.session.SessionException;
import com.sun.identity.plugin.session.SessionListener;
import com.sun.identity.plugin.session.SessionProvider;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

public final class SessionProviderImpl implements SessionProvider {
   private final Set<SessionListener> listeners = new HashSet<>();
   private static final ELogger LOG = ELoggerFactory.getLogger(SessionProviderImpl.class);

   @Override
   public String getPrincipalName(Object session) throws SessionException {
      Optional<String> userId = toFlowFederationSession(session).getAuthenticatedUserId().map(UserId::getValue);
      if (userId.isEmpty()) {
         throw new SessionException("User ID is not present");
      } else {
         return userId.get();
      }
   }

   @Override
   public String[] getProperty(Object session, String name) throws UnsupportedOperationException, SessionException {
      return toFlowFederationSession(session).getValues(name).map(list -> list.toArray(String[]::new)).orElse(null);
   }

   @Override
   public void setProperty(Object session, String name, String[] values) throws UnsupportedOperationException, SessionException {
      toFlowFederationSession(session).setValues(name, Arrays.asList(values));
   }

   public FlowFederationSession getSession(HttpServletRequest request) throws SessionException {
      Optional<FlowFederationSession> federationSession = repository().getFederationSession();
      if (federationSession.isEmpty()) {
         throw new SessionException("No federation session yet!");
      } else if (!federationSession.get().isValid()) {
         throw new SessionException("No authenticated session yet!");
      } else {
         return federationSession.get();
      }
   }

   @Override
   public String getSessionID(Object session) {
      return FlowFederationSession.toFlowFederationSessionOrThrow(session, () -> new IllegalArgumentException("Invalid session type")).getSessionId().value();
   }

   @Override
   public void invalidateSession(Object session, HttpServletRequest request, HttpServletResponse response) throws SessionException {
      if (session != null) {
         try {
            FlowFederationSession federationSession = toFlowFederationSession(session);
            if (federationSession.isValid()) {
               federationSession.invalidate();
               repository().removeFederationSession();
            }
         } finally {
            synchronized (this.listeners) {
               for (SessionListener listener : this.listeners) {
                  listener.sessionInvalidated(session);
               }
            }
         }
      }
   }

   @Override
   public boolean isValid(Object session) throws SessionException {
      return toFlowFederationSession(session).isValid();
   }

   private static FlowFederationSession createNewFlowFederationSession() {
      FlowFederationSession federationSession = FlowFederationSession.emptySession();
      repository().putFederationSession(federationSession);
      LOG.debug("Added new federation session to httpSession");
      return federationSession;
   }

   @Override
   public void addListener(Object session, SessionListener listener) {
      synchronized (this.listeners) {
         this.listeners.add(listener);
      }
   }

   @Override
   public Object getSession(String sessionID) throws SessionException {
      FlowFederationSession ses = this.getSession(Saml2RequestThreadContext.getRequest());
      if (ses.getSessionId().equals(FlowFederationSessionId.federationSessionId(sessionID))) {
         return ses;
      } else {
         throw new SessionException("Current sessionID does not match requested sessionID");
      }
   }

   @Override
   public Object createSession(Map<String, String> info, HttpServletRequest request, HttpServletResponse response, StringBuffer targetApplication) {
      FlowFederationSession federationSession = createNewFlowFederationSession();
      String principalName = info.get("principalName");
      federationSession.setAuthenticatedUserId(UserId.userId(principalName));
      return federationSession;
   }

   @Override
   public String rewriteURL(Object session, String url) {
      return url;
   }

   @Override
   public long getTimeLeft(Object session) {
      return 0L;
   }

   @Override
   public void setLoadBalancerCookie(HttpServletResponse response) {
      throw new UnsupportedOperationException("WSFederation's load balancer cookie not supported.");
   }

   private static FlowFederationSessionRepository repository() {
      return LegacyPluginInjectorFactory.getInjector().getInstance(FlowFederationSessionRepository.class);
   }

   private static FlowFederationSession toFlowFederationSession(Object ses) throws SessionException {
      return FlowFederationSession.toFlowFederationSessionOrThrow(
         ses, () -> new SessionException("Invalid session type: " + (ses == null ? "null" : ses.getClass().getName()))
      );
   }
}
