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

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.core.misc.util.password.hash.RawPasswordHash;
import com.airlock.iam.oauth2.api.domain.model.claims.OAuth2TokenInformation;
import com.airlock.iam.oauth2.domain.model.token.AccessTokenInfo;
import com.airlock.iam.oauth2.domain.model.token.OAuth2RawAccessTokenContainer;
import com.airlock.iam.oauth2.domain.model.token.OAuth2TokenTableAccessToken;
import com.airlock.iam.oauth2.domain.model.token.RawOAuth2TokenWithHash;
import com.airlock.iam.oauth2.domain.model.token.SessionInfo;
import com.airlock.iam.oauth2.domain.service.OAuth2AccessTokenClaimsService;
import com.airlock.iam.oauth2.domain.service.token.OAuth2TokenGenerator;
import com.airlock.iam.oauth2.domain.service.token.TokenGeneratorData;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import lombok.NonNull;

public final class OAuth2AccessTokenGeneratorImpl implements OAuth2AccessTokenGenerator {
   @NonNull
   private final TokenDataProvider tokenDataProvider;
   @NonNull
   private final OAuth2TokenGenerator tokenGenerator;
   @NonNull
   private final RawPasswordHash passwordHash;
   @NonNull
   private final OAuth2AccessTokenClaimsService claimsService;
   private final int accessTokenExpiresIn;

   @Override
   public OAuth2RawAccessTokenContainer generateAndAssignAccessToken(SessionInfo session, AccessTokenInfo accessTokenInfo) throws PersisterException {
      TokenBean accessTokenDelegate = this.tokenDataProvider.createNewToken(KnownTokenTypes.OAUTH2.getId());
      TokenGeneratorData tokenInfo = new TokenGeneratorData(
         accessTokenDelegate.getGenerationDate(),
         accessTokenInfo.scopes(),
         accessTokenInfo.tokenBinding(),
         session.getClientId(),
         session.getSessionId(),
         this.claimsService.loadOrEvaluateAccessTokenClaims(accessTokenInformation(session, accessTokenInfo))
      );
      RawOAuth2TokenWithHash rawAccessToken = new RawOAuth2TokenWithHash(this.tokenGenerator.generateToken(tokenInfo), this.passwordHash);
      OAuth2TokenTableAccessToken accessToken = OAuth2TokenTableAccessToken.createOAuth2AccessToken(
         accessTokenDelegate, rawAccessToken, session, accessTokenInfo, this.accessTokenExpiresIn
      );
      this.tokenDataProvider.assign(accessToken.getDelegate(), session.getUsername(), session.getUsername());
      return new OAuth2RawAccessTokenContainer(accessToken, rawAccessToken);
   }

   private static OAuth2TokenInformation accessTokenInformation(SessionInfo session, AccessTokenInfo accessTokenInfo) {
      return new OAuth2TokenInformation(session.getUsername(), accessTokenInfo.scopes(), session.getClientId(), session.getSessionId());
   }

   @SuppressFBWarnings(justification = "generated code")
   OAuth2AccessTokenGeneratorImpl(
      @NonNull TokenDataProvider tokenDataProvider,
      @NonNull OAuth2TokenGenerator tokenGenerator,
      @NonNull RawPasswordHash passwordHash,
      @NonNull OAuth2AccessTokenClaimsService claimsService,
      int accessTokenExpiresIn
   ) {
      if (tokenDataProvider == null) {
         throw new NullPointerException("tokenDataProvider is marked non-null but is null");
      }

      if (tokenGenerator == null) {
         throw new NullPointerException("tokenGenerator is marked non-null but is null");
      }

      if (passwordHash == null) {
         throw new NullPointerException("passwordHash is marked non-null but is null");
      }

      if (claimsService == null) {
         throw new NullPointerException("claimsService is marked non-null but is null");
      }

      this.tokenDataProvider = tokenDataProvider;
      this.tokenGenerator = tokenGenerator;
      this.passwordHash = passwordHash;
      this.claimsService = claimsService;
      this.accessTokenExpiresIn = accessTokenExpiresIn;
   }

   @SuppressFBWarnings(justification = "generated code")
   public static OAuth2AccessTokenGeneratorImpl.OAuth2AccessTokenGeneratorImplBuilder builder() {
      return new OAuth2AccessTokenGeneratorImpl.OAuth2AccessTokenGeneratorImplBuilder();
   }

   @SuppressFBWarnings(justification = "generated code")
   public static class OAuth2AccessTokenGeneratorImplBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private TokenDataProvider tokenDataProvider;
      @SuppressFBWarnings(justification = "generated code")
      private OAuth2TokenGenerator tokenGenerator;
      @SuppressFBWarnings(justification = "generated code")
      private RawPasswordHash passwordHash;
      @SuppressFBWarnings(justification = "generated code")
      private OAuth2AccessTokenClaimsService claimsService;
      @SuppressFBWarnings(justification = "generated code")
      private int accessTokenExpiresIn;

      @SuppressFBWarnings(justification = "generated code")
      OAuth2AccessTokenGeneratorImplBuilder() {
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2AccessTokenGeneratorImpl.OAuth2AccessTokenGeneratorImplBuilder tokenDataProvider(@NonNull TokenDataProvider tokenDataProvider) {
         if (tokenDataProvider == null) {
            throw new NullPointerException("tokenDataProvider is marked non-null but is null");
         }

         this.tokenDataProvider = tokenDataProvider;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2AccessTokenGeneratorImpl.OAuth2AccessTokenGeneratorImplBuilder tokenGenerator(@NonNull OAuth2TokenGenerator tokenGenerator) {
         if (tokenGenerator == null) {
            throw new NullPointerException("tokenGenerator is marked non-null but is null");
         }

         this.tokenGenerator = tokenGenerator;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2AccessTokenGeneratorImpl.OAuth2AccessTokenGeneratorImplBuilder passwordHash(@NonNull RawPasswordHash passwordHash) {
         if (passwordHash == null) {
            throw new NullPointerException("passwordHash is marked non-null but is null");
         }

         this.passwordHash = passwordHash;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2AccessTokenGeneratorImpl.OAuth2AccessTokenGeneratorImplBuilder claimsService(@NonNull OAuth2AccessTokenClaimsService claimsService) {
         if (claimsService == null) {
            throw new NullPointerException("claimsService is marked non-null but is null");
         }

         this.claimsService = claimsService;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2AccessTokenGeneratorImpl.OAuth2AccessTokenGeneratorImplBuilder accessTokenExpiresIn(int accessTokenExpiresIn) {
         this.accessTokenExpiresIn = accessTokenExpiresIn;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2AccessTokenGeneratorImpl build() {
         return new OAuth2AccessTokenGeneratorImpl(
            this.tokenDataProvider, this.tokenGenerator, this.passwordHash, this.claimsService, this.accessTokenExpiresIn
         );
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "OAuth2AccessTokenGeneratorImpl.OAuth2AccessTokenGeneratorImplBuilder(tokenDataProvider="
            + this.tokenDataProvider
            + ", tokenGenerator="
            + this.tokenGenerator
            + ", passwordHash="
            + this.passwordHash
            + ", claimsService="
            + this.claimsService
            + ", accessTokenExpiresIn="
            + this.accessTokenExpiresIn
            + ")";
      }
   }
}
