package com.airlock.iam.oauth2.domain.model.remoteconsent;

import com.airlock.iam.core.misc.util.ticket.KeyMultiValue;
import com.airlock.iam.core.misc.util.ticket.Ticket;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import com.airlock.iam.oauth2.api.domain.model.OAuth2Scope;
import com.google.common.annotations.VisibleForTesting;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;

public final class RemoteConsentResponseTokenFactory {
   private static final ELogger LOG = ELoggerFactory.getLogger(RemoteConsentResponseTokenFactory.class);
   private static final String SCOPE_CLAIM_NAME = "scope";
   private static final String CONSENT_NONCE_CLAIM_NAME = "consent_nonce";
   private static final String CONSENT_GIVEN_CLAIM_NAME = "consent_given";
   private final Consumer<String> infoLog;

   public static Optional<RemoteConsentResponseToken> createConsentToken(Ticket ticket, RemoteConsentSessionNonce sessionNonce) {
      return new RemoteConsentResponseTokenFactory(LOG::info).createToken(ticket, sessionNonce);
   }

   @VisibleForTesting
   RemoteConsentResponseTokenFactory(Consumer<String> infoLog) {
      this.infoLog = infoLog;
   }

   @VisibleForTesting
   Optional<RemoteConsentResponseToken> createToken(Ticket ticket, RemoteConsentSessionNonce sessionNonce) {
      Optional<List<OAuth2Scope>> oAuth2Scopes = this.scopeClaim(ticket);
      Optional<RemoteConsentResponseNonce> responseNonce = this.nonce(ticket);
      Optional<Boolean> consentGiven = this.isConsentGiven(ticket);
      if (oAuth2Scopes.isEmpty() || responseNonce.isEmpty() || consentGiven.isEmpty()) {
         return Optional.empty();
      }

      if (!sessionNonce.matches(responseNonce.get())) {
         this.infoLog("The response nonce does not match the session nonce");
         return Optional.empty();
      }

      if (consentGiven.get()) {
         return Optional.of(RemoteConsentResponseToken.tokenWithConsentAndScopes(oAuth2Scopes.get()));
      }

      this.infoLog("The user denied the remote consent.");
      return Optional.of(RemoteConsentResponseToken.tokenWithoutConsent());
   }

   private Optional<Boolean> isConsentGiven(Ticket ticket) {
      Optional<KeyMultiValue> consentGiven = Optional.ofNullable(ticket.getValueForName("consent_given"));
      if (consentGiven.isEmpty()) {
         this.infoLog("The remote consent token does not contain a 'consent_given' claim and is therefore invalid.");
         return Optional.empty();
      } else {
         return Optional.of(Boolean.parseBoolean(consentGiven.get().getFirstValue()));
      }
   }

   private Optional<List<OAuth2Scope>> scopeClaim(Ticket ticket) {
      Optional<KeyMultiValue> scopeClaim = Optional.ofNullable(ticket.getValueForName("scope"));
      if (scopeClaim.isEmpty()) {
         this.infoLog("The remote consent token does not contain a 'scope' claim and is therefore invalid.");
         return Optional.empty();
      } else {
         return Optional.of(OAuth2Scope.convertToScopeList(scopeClaim.get().getValues()));
      }
   }

   private Optional<RemoteConsentResponseNonce> nonce(Ticket ticket) {
      Optional<KeyMultiValue> consentNonce = Optional.ofNullable(ticket.getValueForName("consent_nonce"));
      if (consentNonce.isEmpty()) {
         this.infoLog("The remote consent token does not contain a 'consent_nonce' claim and is therefore invalid.");
         return Optional.empty();
      } else {
         return Optional.of(new RemoteConsentResponseNonce(consentNonce.get().getFirstValue()));
      }
   }

   private void infoLog(String message) {
      this.infoLog.accept(message);
   }
}
