package com.airlock.iam.oauth2.domain.service.clientauthentication.clientsecret;

import com.airlock.iam.base.misc.log.LogUtil;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import com.airlock.iam.oauth2.api.domain.model.clientauthentication.OAuth2AuthClientId;
import com.airlock.iam.oauth2.api.domain.model.clientauthentication.OAuth2ClientAuthentication;
import com.airlock.iam.oauth2.api.domain.service.clientauthentication.OAuth2ClientAuthenticationVerifier;
import com.airlock.iam.oauth2.domain.model.clientauthentication.OAuth2AuthenticationVerifierSource;
import com.airlock.iam.oauth2.domain.model.clientauthentication.OAuth2ClientAuthenticationData;
import com.airlock.iam.oauth2.domain.model.clientauthentication.OAuth2ClientSecretAuthenticationPayload;
import com.airlock.iam.security.util.SecureComparison;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.Optional;
import lombok.NonNull;

public final class OAuth2ClientSecretAuthenticationVerifier implements OAuth2ClientAuthenticationVerifier<OAuth2ClientSecretAuthenticationPayload> {
   private static final ELogger LOG = ELoggerFactory.getLogger(OAuth2ClientSecretAuthenticationVerifier.class);
   @NonNull
   private final OAuth2AuthenticationVerifierSource clientSecretVerifierSource;

   public Optional<OAuth2ClientAuthentication> verify(OAuth2ClientSecretAuthenticationPayload authenticationPayload) {
      OAuth2AuthClientId authenticationClientID = authenticationPayload.clientId()
         .orElseThrow(() -> new IllegalStateException("Required Client ID missing in client secret authentication payload"));
      Optional<OAuth2ClientAuthenticationData> verificationData = this.clientSecretVerifierSource.verificationData(authenticationClientID);
      if (verificationData.isEmpty()) {
         LOG.info("Client not found: " + LogUtil.logSafeQuote(authenticationClientID));
         return Optional.empty();
      } else {
         String authenticationClientSecret = authenticationPayload.secret();
         boolean clientIdMatches = authenticationPayload.clientIdFromRequest()
            .map(c -> SecureComparison.isEqual(c, authenticationClientID.id()))
            .orElse(Boolean.TRUE);
         if (!clientIdMatches) {
            LOG.info("Client ID from authentication does not match the client_id parameter");
            return Optional.empty();
         } else {
            boolean clientSecretMatches = verificationData.get()
               .clientSecret()
               .map(s -> SecureComparison.isEqual(authenticationClientSecret, s))
               .orElse(Boolean.FALSE);
            if (!clientSecretMatches) {
               LOG.info("Client Secret from authentication does not match the configured secret.");
               return Optional.empty();
            } else {
               return Optional.of(OAuth2ClientAuthentication.clientAuthentication(authenticationPayload));
            }
         }
      }
   }

   @SuppressFBWarnings(justification = "generated code")
   public OAuth2ClientSecretAuthenticationVerifier(@NonNull OAuth2AuthenticationVerifierSource clientSecretVerifierSource) {
      if (clientSecretVerifierSource == null) {
         throw new NullPointerException("clientSecretVerifierSource is marked non-null but is null");
      }

      this.clientSecretVerifierSource = clientSecretVerifierSource;
   }
}
