package com.airlock.iam.oauth2.application.service.introspection;

import com.airlock.iam.common.domain.model.authentication.factor.AuthenticationFactorConverter;
import com.airlock.iam.common.domain.service.credential.HttpBasicAuthExtractor;
import com.airlock.iam.core.domain.model.authentication.factor.KnownAuthenticationFactor;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.oauth2.api.domain.model.OAuth2ClientId;
import com.airlock.iam.oauth2.application.configuration.introspection.BasicAuthTokenIntrospectionAuthenticationConfig;
import com.airlock.iam.oauth2.domain.service.OAuth2BasicAuthHeaderUtils;
import com.airlock.iam.oauth2.domain.service.reporting.OAuth2ReportingService;
import com.airlock.iam.security.util.SecureComparison;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.nio.charset.Charset;
import java.util.List;
import java.util.Optional;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Strings;

public class BasicAuthTokenIntrospectionAuthService implements TokenIntrospectionAuthService {
   private static final String BASIC_AUTH_PREFIX = "Basic ";
   private final Charset charset;
   private final List<BasicAuthTokenIntrospectionAuthenticationConfig.UsernamePasswordCombination> allowedUsers;
   private final ELogger logger;
   private final OAuth2ReportingService reportingService;

   @Override
   public boolean isCredentialValid(String credential) {
      if (StringUtils.isBlank(credential)) {
         return this.invalid();
      } else {
         credential = credential.trim();
         if (!Strings.CS.startsWith(credential, "Basic ")) {
            this.logger.info("Received wrong credential. Expected basic-auth but got \"" + credential + "\"");
            return this.invalid();
         } else if (!credential.startsWith("Basic ")) {
            this.logger.debug("No basic auth header provided; access denied.");
            return this.invalid();
         } else {
            Optional<HttpBasicAuthExtractor.BasicAuthHeader> header = OAuth2BasicAuthHeaderUtils.parseOAuth2BasicAuthHeader(
               Optional.of(credential), this.charset
            );
            if (header.isEmpty()) {
               return this.invalid();
            } else {
               return !this.isUserAllowed(header.get()) ? this.invalid() : this.valid();
            }
         }
      }
   }

   private boolean valid() {
      this.reportingService.reportLoginSuccess();
      return true;
   }

   private boolean invalid() {
      this.reportingService.reportLoginFailure();
      return false;
   }

   private boolean isUserAllowed(HttpBasicAuthExtractor.BasicAuthHeader header) {
      for (BasicAuthTokenIntrospectionAuthenticationConfig.UsernamePasswordCombination allowedUser : this.allowedUsers) {
         if (SecureComparison.isEqual(allowedUser.getUsername(), header.username().getValue())
            && SecureComparison.isEqual(allowedUser.getPassword(), header.password().getValue())) {
            this.logger.debug("Authenticating user \"" + header.username() + "\" for token introspection using Basic-Authentication.");
            this.reportingService
               .reportFactorUseSuccess(
                  OAuth2ClientId.oAuth2ClientId(header.username().getValue()), AuthenticationFactorConverter.convertFactor(KnownAuthenticationFactor.PASSWORD)
               );
            return true;
         }
      }

      this.logger.debug("Access denied for user \"" + header.username() + "\" for token introspection using Basic-Authentication; username or password wrong.");
      this.reportingService
         .reportFactorUseFailure(
            OAuth2ClientId.oAuth2ClientId(header.username().getValue()), AuthenticationFactorConverter.convertFactor(KnownAuthenticationFactor.PASSWORD)
         );
      return false;
   }

   @SuppressFBWarnings(justification = "generated code")
   public BasicAuthTokenIntrospectionAuthService(
      Charset charset,
      List<BasicAuthTokenIntrospectionAuthenticationConfig.UsernamePasswordCombination> allowedUsers,
      ELogger logger,
      OAuth2ReportingService reportingService
   ) {
      this.charset = charset;
      this.allowedUsers = allowedUsers;
      this.logger = logger;
      this.reportingService = reportingService;
   }
}
