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.LinkedTokenReference;
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.OAuth2TokenTableRefreshToken;
import com.airlock.iam.oauth2.domain.model.token.RawOAuth2TokenWithHash;
import com.airlock.iam.oauth2.domain.model.token.RefreshTokenInfo;
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.OAuth2RandomTokenGenerator;
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 OAuth2AccessAndRefreshTokenGenerator {
   @NonNull
   private final TokenDataProvider tokenDataProvider;
   @NonNull
   private final OAuth2RandomTokenGenerator randomTokenGenerator;
   @NonNull
   private final OAuth2TokenGenerator accessTokenGenerator;
   @NonNull
   private final OAuth2AccessTokenClaimsService claimsService;
   @NonNull
   private final RawPasswordHash passwordHash;
   private final int refreshTokenExpiresIn;
   private final int accessTokenExpiresIn;

   public OAuth2RawAccessTokenContainer generateAndAssignAccessAndRefreshToken(SessionInfo session, AccessTokenInfo tokenInfo) throws PersisterException {
      TokenBean refreshTokenDelegate = this.createToken();
      RawOAuth2TokenWithHash rawRefreshTokenWithHash = new RawOAuth2TokenWithHash(this.randomTokenGenerator.generateToken(), this.passwordHash);
      TokenBean accessTokenDelegate = this.createToken();
      TokenGeneratorData tokenGeneratorData = new TokenGeneratorData(
         accessTokenDelegate.getGenerationDate(),
         tokenInfo.scopes(),
         tokenInfo.tokenBinding(),
         session.getClientId(),
         session.getSessionId(),
         this.claimsService.loadOrEvaluateAccessTokenClaims(accessTokenInformation(session, tokenInfo))
      );
      RawOAuth2TokenWithHash rawAccessTokenWithHash = new RawOAuth2TokenWithHash(this.accessTokenGenerator.generateToken(tokenGeneratorData), this.passwordHash);
      OAuth2TokenTableAccessToken accessToken = OAuth2TokenTableAccessToken.createOAuth2AccessToken(
         accessTokenDelegate, rawAccessTokenWithHash, session, tokenInfo, this.accessTokenExpiresIn, rawRefreshTokenWithHash
      );
      this.tokenDataProvider.assign(accessToken.getDelegate(), session.getUsername(), session.getUsername());
      OAuth2TokenTableRefreshToken refreshToken = OAuth2TokenTableRefreshToken.createOAuth2RefreshToken(
         refreshTokenDelegate,
         rawRefreshTokenWithHash,
         session,
         new RefreshTokenInfo(tokenInfo.scopes(), LinkedTokenReference.fromRawToken(rawAccessTokenWithHash)),
         this.refreshTokenExpiresIn
      );
      this.tokenDataProvider.assign(refreshToken.getDelegate(), session.getUsername(), session.getUsername());
      return new OAuth2RawAccessTokenContainer(accessToken, rawAccessTokenWithHash, rawRefreshTokenWithHash);
   }

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

   private TokenBean createToken() throws PersisterException {
      return this.tokenDataProvider.createNewToken(KnownTokenTypes.OAUTH2.getId());
   }

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

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

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

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

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

      this.tokenDataProvider = tokenDataProvider;
      this.randomTokenGenerator = randomTokenGenerator;
      this.accessTokenGenerator = accessTokenGenerator;
      this.claimsService = claimsService;
      this.passwordHash = passwordHash;
      this.refreshTokenExpiresIn = refreshTokenExpiresIn;
      this.accessTokenExpiresIn = accessTokenExpiresIn;
   }

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

   @SuppressFBWarnings(justification = "generated code")
   public static class OAuth2AccessAndRefreshTokenGeneratorBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private TokenDataProvider tokenDataProvider;
      @SuppressFBWarnings(justification = "generated code")
      private OAuth2RandomTokenGenerator randomTokenGenerator;
      @SuppressFBWarnings(justification = "generated code")
      private OAuth2TokenGenerator accessTokenGenerator;
      @SuppressFBWarnings(justification = "generated code")
      private OAuth2AccessTokenClaimsService claimsService;
      @SuppressFBWarnings(justification = "generated code")
      private RawPasswordHash passwordHash;
      @SuppressFBWarnings(justification = "generated code")
      private int refreshTokenExpiresIn;
      @SuppressFBWarnings(justification = "generated code")
      private int accessTokenExpiresIn;

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

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2AccessAndRefreshTokenGenerator.OAuth2AccessAndRefreshTokenGeneratorBuilder 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 OAuth2AccessAndRefreshTokenGenerator.OAuth2AccessAndRefreshTokenGeneratorBuilder randomTokenGenerator(
         @NonNull OAuth2RandomTokenGenerator randomTokenGenerator
      ) {
         if (randomTokenGenerator == null) {
            throw new NullPointerException("randomTokenGenerator is marked non-null but is null");
         }

         this.randomTokenGenerator = randomTokenGenerator;
         return this;
      }

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

         this.accessTokenGenerator = accessTokenGenerator;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2AccessAndRefreshTokenGenerator.OAuth2AccessAndRefreshTokenGeneratorBuilder 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 OAuth2AccessAndRefreshTokenGenerator.OAuth2AccessAndRefreshTokenGeneratorBuilder 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 OAuth2AccessAndRefreshTokenGenerator.OAuth2AccessAndRefreshTokenGeneratorBuilder refreshTokenExpiresIn(int refreshTokenExpiresIn) {
         this.refreshTokenExpiresIn = refreshTokenExpiresIn;
         return this;
      }

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

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2AccessAndRefreshTokenGenerator build() {
         return new OAuth2AccessAndRefreshTokenGenerator(
            this.tokenDataProvider,
            this.randomTokenGenerator,
            this.accessTokenGenerator,
            this.claimsService,
            this.passwordHash,
            this.refreshTokenExpiresIn,
            this.accessTokenExpiresIn
         );
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "OAuth2AccessAndRefreshTokenGenerator.OAuth2AccessAndRefreshTokenGeneratorBuilder(tokenDataProvider="
            + this.tokenDataProvider
            + ", randomTokenGenerator="
            + this.randomTokenGenerator
            + ", accessTokenGenerator="
            + this.accessTokenGenerator
            + ", claimsService="
            + this.claimsService
            + ", passwordHash="
            + this.passwordHash
            + ", refreshTokenExpiresIn="
            + this.refreshTokenExpiresIn
            + ", accessTokenExpiresIn="
            + this.accessTokenExpiresIn
            + ")";
      }
   }
}
