package com.airlock.iam.oauth2.domain.service.token;

import com.airlock.iam.base.api.domain.model.user.UserId;
import com.airlock.iam.base.misc.collection.IamCollections;
import com.airlock.iam.base.misc.collection.Pair;
import com.airlock.iam.base.misc.log.LogUtil;
import com.airlock.iam.common.domain.service.oauth.OAuth2UserTokenService;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import com.airlock.iam.flow.shared.api.domain.model.session.UserSession;
import com.airlock.iam.oauth2.api.domain.model.AuthorizationServerId;
import com.airlock.iam.oauth2.domain.model.oauth2.session.OAuth2UserAgentSessions;
import com.airlock.iam.oauth2.domain.model.oidc.OidcUserAgentSessions;
import com.airlock.iam.oauth2.domain.model.session.OAuth2SessionId;
import com.airlock.iam.oauth2.domain.model.session.OperationStatistics;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jakarta.inject.Inject;
import jakarta.inject.Provider;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public final class OAuth2UserTokenServiceImpl implements OAuth2UserTokenService {
   private static final ELogger LOG = ELoggerFactory.getLogger(OAuth2UserTokenServiceImpl.class);
   private final Provider<List<OAuth2TokenServiceSettings>> settings;
   private final Provider<UserSession> userSession;

   @Inject
   public OAuth2UserTokenServiceImpl(Provider<List<OAuth2TokenServiceSettings>> settings, Provider<UserSession> userSession) {
      this.settings = settings;
      this.userSession = userSession;
   }

   @Override
   public void handleLogout(UserId userId) {
      ((List)this.settings.get())
         .stream()
         .filter(settings -> settings.tokenDeletionStrategyOnLogout().deleteAllTokens())
         .forEach(setting -> deleteAllUserSessionsAndLogAction(userId, setting, "logout"));
      ((List)this.settings.get())
         .stream()
         .filter(settings -> settings.tokenDeletionStrategyOnLogout().deleteSessionTokens())
         .forEach(setting -> deleteCurrentUserSessionAndLogAction(this.getAsIdToUserAgentSessionIds(), setting, userId, "logout"));
   }

   @Override
   public void handlePasswordChange(UserId userId) {
      ((List)this.settings.get())
         .stream()
         .filter(OAuth2TokenServiceSettings::isDeleteTokensOnPasswordChange)
         .forEach(setting -> deleteAllUserSessionsAndLogAction(userId, setting, "password change"));
   }

   @Override
   public void handleUserLocked(UserId userId) {
      ((List)this.settings.get())
         .stream()
         .filter(OAuth2TokenServiceSettings::isDeleteTokensOnUserLocked)
         .forEach(setting -> deleteAllUserSessionsAndLogAction(userId, setting, "user lock"));
   }

   @SuppressFBWarnings(value = "FORMAT_STRING_MANIPULATION", justification = "logReason is not user defined input")
   private static void deleteAllUserSessionsAndLogAction(UserId id, OAuth2TokenServiceSettings setting, String logReason) {
      OperationStatistics statistics = deleteAllUserSessions(id, setting);
      if (statistics.getTokenCount() > 0 || statistics.getSessionCount() > 0) {
         LOG.info(
            String.format(
               "Deleted %s and %s for user %s on authorization server %s as result of a %s.",
               LogUtil.pluralize(statistics.getTokenCount(), "OAuth 2.0 token"),
               LogUtil.pluralize(statistics.getSessionCount(), "session"),
               LogUtil.logSafeQuote(id.getValue()),
               LogUtil.logSafeQuote(setting.authorizationServerId().id()),
               logReason
            )
         );
      }
   }

   private static OperationStatistics deleteAllUserSessions(UserId id, OAuth2TokenServiceSettings setting) {
      return setting.extendedSessionRepository().deleteAllSessionsByUser(id);
   }

   private Map<AuthorizationServerId, List<OAuth2SessionId>> getAsIdToUserAgentSessionIds() {
      List<Pair<AuthorizationServerId, OAuth2SessionId>> oidcSessions = ((UserSession)this.userSession.get())
         .findUserSessionAttribute(OidcUserAgentSessions.class)
         .map(OidcUserAgentSessions::authorizationServerSessions)
         .orElse(List.of());
      List<Pair<AuthorizationServerId, OAuth2SessionId>> oAuth2Sessions = ((UserSession)this.userSession.get())
         .findUserSessionAttribute(OAuth2UserAgentSessions.class)
         .map(OAuth2UserAgentSessions::authorizationServerSessions)
         .orElse(List.of());
      return IamCollections.mergeCollections(oidcSessions, oAuth2Sessions)
         .stream()
         .collect(Collectors.groupingBy(Pair::getFirst, Collectors.mapping(Pair::getSecond, Collectors.toList())));
   }

   @SuppressFBWarnings(value = "FORMAT_STRING_MANIPULATION", justification = "logReason is not user defined input")
   private static void deleteCurrentUserSessionAndLogAction(
      Map<AuthorizationServerId, List<OAuth2SessionId>> asIdToSessionIds, OAuth2TokenServiceSettings setting, UserId id, String logReason
   ) {
      AuthorizationServerId asId = setting.authorizationServerId();
      if (asIdToSessionIds.containsKey(asId)) {
         List<OAuth2SessionId> sessionIds = asIdToSessionIds.get(asId);
         setting.extendedSessionRepository().delete(sessionIds);
         LOG.info(
            String.format(
               "Deleted %s for user %s on authorization server %s as result of a %s.",
               LogUtil.pluralize(sessionIds.size(), "session"),
               LogUtil.logSafeQuote(id.getValue()),
               LogUtil.logSafeQuote(asId.id()),
               logReason
            )
         );
      }
   }
}
