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

import com.airlock.iam.base.api.domain.model.exception.FailureException;
import com.airlock.iam.common.api.domain.model.persistency.exception.PersisterException;
import com.airlock.iam.core.misc.persistency.token.TokenBean;
import com.airlock.iam.core.misc.persistency.token.TokenDataProvider;
import com.airlock.iam.core.misc.token.KnownTokenTypes;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.oauth2.api.domain.model.OAuth2Scope;
import com.airlock.iam.oauth2.domain.model.acr.OpenIdConnectAcrValue;
import com.airlock.iam.oauth2.domain.model.codegrant.OpenIdConnectAuthenticationRequestAttributes;
import com.airlock.iam.oauth2.domain.model.logger.IntegrationLoggerFactory;
import com.airlock.iam.oauth2.domain.model.oidc.OidcUserAgentSessionId;
import com.airlock.iam.oauth2.domain.model.token.IdTokenInfo;
import com.airlock.iam.oauth2.domain.model.token.OAuth2TokenKey;
import com.airlock.iam.oauth2.domain.model.token.OpenIDConnectTokenTableIDToken;
import com.airlock.iam.oauth2.domain.model.token.SessionInfo;
import com.airlock.iam.oauth2.domain.service.acr.OpenIdConnectIdTokenAcrSelection;
import com.airlock.iam.oauth2.domain.service.codegrant.CodeFlowOpenIdConnectIdTokenGenerator;
import com.airlock.iam.oauth2.domain.service.token.OAuth2RandomTokenGenerator;
import com.airlock.iam.plugin.api.config.LogTypes;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import jakarta.inject.Provider;
import java.util.Optional;
import java.util.Set;

public final class CodeFlowOpenIdConnectIdTokenGeneratorImpl implements CodeFlowOpenIdConnectIdTokenGenerator {
   public static final String CODE_FLOW_ACR_SELECTION = "codeFlowAcrSelection";
   private final Provider<CodeFlowOpenIdConnectIdTokenLifetime> tokenLifetime;
   private final Provider<TokenDataProvider> tokenDataProvider;
   private final Provider<OAuth2RandomTokenGenerator> randomTokenGenerator;
   private final Provider<Optional<OpenIdConnectIdTokenAcrSelection>> acrSelection;
   private final Provider<IntegrationLoggerFactory> loggerFactory;

   @Inject
   public CodeFlowOpenIdConnectIdTokenGeneratorImpl(
      Provider<CodeFlowOpenIdConnectIdTokenLifetime> tokenLifetime,
      Provider<TokenDataProvider> tokenDataProvider,
      Provider<OAuth2RandomTokenGenerator> randomTokenGenerator,
      @Named("codeFlowAcrSelection") Provider<Optional<OpenIdConnectIdTokenAcrSelection>> acrSelection,
      Provider<IntegrationLoggerFactory> loggerFactory
   ) {
      this.tokenLifetime = tokenLifetime;
      this.tokenDataProvider = tokenDataProvider;
      this.randomTokenGenerator = randomTokenGenerator;
      this.acrSelection = acrSelection;
      this.loggerFactory = loggerFactory;
   }

   @Override
   public void generateAndAssignIdToken(
      OpenIdConnectAuthenticationRequestAttributes oidcRequest,
      SessionInfo sessionInfo,
      Set<OAuth2Scope> grantedScopes,
      Optional<OidcUserAgentSessionId> oidcUserAgentSessionId
   ) {
      Optional<OpenIdConnectAcrValue> selectedAcr = ((Optional)this.acrSelection.get()).flatMap(s -> s.select(oidcRequest.acrValues(), oidcRequest.clientId()));
      IdTokenInfo idTokenInfo = new IdTokenInfo(grantedScopes, oidcRequest.nonce(), selectedAcr, oidcUserAgentSessionId);

      try {
         TokenBean delegate = ((TokenDataProvider)this.tokenDataProvider.get()).createNewToken(KnownTokenTypes.OAUTH2.getId());
         OAuth2TokenKey idTokenKey = ((OAuth2RandomTokenGenerator)this.randomTokenGenerator.get()).generateKey();
         OpenIDConnectTokenTableIDToken idToken = OpenIDConnectTokenTableIDToken.createOpenIDConnectIDToken(
            delegate, idTokenKey, idTokenInfo, sessionInfo, ((CodeFlowOpenIdConnectIdTokenLifetime)this.tokenLifetime.get()).tokenExpiresIn()
         );
         ((TokenDataProvider)this.tokenDataProvider.get()).assign(idToken.getDelegate(), sessionInfo.getUsername(), sessionInfo.getUsername());
      } catch (PersisterException e) {
         this.getLogger().warn(LogTypes.PLUGIN_INFO, "OpenID Connect: Couldn't generate ID token for authorization code flow");
         throw new FailureException("ID token generation and assignment for authorization code flow failed");
      }
   }

   private ELogger getLogger() {
      return ((IntegrationLoggerFactory)this.loggerFactory.get()).create(CodeFlowOpenIdConnectIdTokenGeneratorImpl.class);
   }
}
