package com.airlock.iam.apipolicyservice.application.configuration;

import com.airlock.iam.common.api.application.configuration.credential.RequestAuthenticationConfig;
import com.airlock.iam.common.application.configuration.RestConfigSpec;
import com.airlock.iam.common.application.configuration.logging.CorrelationIdSettingsConfig;
import com.airlock.iam.common.application.configuration.usertrail.UserTrailLogRepositoryConfig;
import com.airlock.iam.common.domain.model.jwt.signature.JwsAlgorithmHmac;
import com.airlock.iam.common.domain.service.jwt.JwtSettingsValidationError;
import com.airlock.iam.common.domain.service.jwt.signature.HmacJwtSigner;
import com.airlock.iam.common.domain.service.jwt.signature.JwtSignatureVerifierImpl;
import com.airlock.iam.core.misc.authen.UsernameTransformer;
import com.airlock.iam.core.misc.util.context.ContextExtractor;
import com.airlock.iam.core.misc.util.context.NoContextExtractor;
import com.airlock.iam.plugin.api.annotation.config.ConfigurationValidator;
import com.airlock.iam.plugin.api.annotation.config.Groups;
import com.airlock.iam.plugin.api.annotation.config.Plugin;
import com.airlock.iam.plugin.api.annotation.license.PluginLicenseRestriction;
import com.airlock.iam.plugin.api.annotation.property.GroupProperty;
import com.airlock.iam.plugin.api.annotation.property.PluginProperty;
import com.airlock.iam.plugin.api.annotation.property.StringProperty;
import com.airlock.iam.plugin.api.config.ConfigurationContext;
import com.airlock.iam.plugin.api.config.ConfigurationEnvironment;
import com.airlock.iam.plugin.api.config.Environment;
import com.airlock.iam.plugin.api.config.PluginConfiguration;
import com.airlock.iam.plugin.api.exception.PropertyConstraintViolationException;
import com.airlock.iam.plugin.api.exception.PropertyTypeMismatchException;
import com.airlock.iam.plugin.api.valuereader.StringPropertyValueReader;
import com.airlock.iam.plugin.framework.misc.AppId;
import com.airlock.iam.techclient.application.configuration.TechClientRepositoryConfig;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.Collections;
import java.util.List;
import java.util.Optional;

@Plugin(
   description = "Configures the API Policy Service web application.<p>This web application currently offers a REST endpoint targeted at Airlock WAF that allows to retrieve information about a technical client by resolving a given API key. Among other, the returned information contains details about the technical client and associated plans (including rate limits).</p>",
   displayName = "API Policy Service"
)
@PluginLicenseRestriction(moduleAffiliation = "ApiPolicyService")
@Groups(groupSequence = {"Basic Settings", "Advanced Settings"}, openGroups = "Basic Settings")
public final class ApiPolicyServiceAppConfig implements RestConfigSpec {
   public static final String API_POLICY_SERVICE_DISPLAY_NAME = "API Policy Service";
   public static final AppId API_POLICY_SERVICE_CONFIG_IDENTIFIER = AppId.appId("api-policy-service");
   public static final String WAF_REQUEST_ID_HEADER = "X-AL-RequestId";
   public static final String WAF_SESSION_ID_HEADER = "X-AL-SessionId";
   public static final String WAF_REMOTE_ADDR_HEADER = "X-AL-RemoteAddr";
   @PluginProperty(description = "A repository that allows to configure the DB access concerning API policy service functionality.", optional = false)
   @GroupProperty(groupIdentifier = "Basic Settings")
   private TechClientRepositoryConfig repository;
   @StringProperty(
      description = "<p>Shared secret to verify the JWT signature. Must be the same as on the Airlock Gateway (WAF) using this API Policy Service endpoint.</p><p>The shared secret must be encoded in base64. The minimal required unencoded length is at least 512 bits. Configuration validation fails if the secret is too short.</p><p>One can, for example, generate a random secret with 512 bits (64 bytes) as base64 encoded string using openssl as follows: <code>openssl rand -base64 96</code></p>",
      isSensitive = true,
      optional = false
   )
   @GroupProperty(groupIdentifier = "Security Settings")
   private String sharedSecret;
   public static final String HMAC_KEY_PROPERTY_NAME = "sharedSecret";
   @PluginProperty(
      description = "Specifies how a context is to be extracted from a request.",
      optional = true,
      defaultValue = NoContextExtractor.class,
      supportsContexts = false
   )
   @GroupProperty(groupIdentifier = "Advanced Settings")
   private ContextExtractor contextExtractor;
   @PluginProperty(
      description = "<p>Configures the database settings to use when persisting user trail log entries.</p>\n<p>If this value is defined, then all user trail log messages generated by the API Policy\nService App module will additionally be forwarded to the database configured within the\nreferenced repository plugin.</p>\n<p>All forwarded log entries are stored inside the table \"USER_TRAIL_LOG\". Note that\nsetting this value does not disable writing log messages to the API Policy Service\nlog file.</p>",
      globalPluginReference = "globalUserTrailLogRepository",
      optional = true
   )
   @GroupProperty(groupIdentifier = "Advanced Settings")
   private UserTrailLogRepositoryConfig logUserTrailToDatabase;
   @PluginProperty(
      displayName = "Correlation ID Settings",
      description = "<p>Defines settings for correlation ID transfer and logging inside the Api Policy Service module.</p>\n<p>If undefined, no correlation ID will be logged for this module.</p>",
      globalPluginReference = "globalCorrelationIdSettings",
      optional = true
   )
   @GroupProperty(groupIdentifier = "Advanced Settings")
   private CorrelationIdSettingsConfig correlationIdSettings;

   public Optional<UserTrailLogRepositoryConfig> getUserTrailLogRepositoryConfig() {
      return Optional.ofNullable(this.logUserTrailToDatabase);
   }

   public Optional<CorrelationIdSettingsConfig> getCorrelationIdSettingsConfig() {
      return Optional.ofNullable(this.correlationIdSettings);
   }

   public Optional<RequestAuthenticationConfig> getRequestAuthenticationConfig() {
      throw new UnsupportedOperationException("Credential extraction is not configurable for API Policy Service.");
   }

   public List<UsernameTransformer> getUsernameTransformers() {
      return Collections.emptyList();
   }

   public Optional<String> getBaseUri() {
      return Optional.empty();
   }

   public boolean isLinkResponseRewritingEnabled() {
      return false;
   }

   public TechClientRepositoryConfig getRepository() {
      return this.repository;
   }

   public String getSharedSecret() {
      return this.sharedSecret;
   }

   public ContextExtractor getContextExtractor() {
      return this.contextExtractor;
   }

   @ConfigurationValidator
   public static void ensureHmacAlgorithmAndKeyWork(PluginConfiguration pc, ConfigurationEnvironment env, ConfigurationContext context, Environment valenv) throws PropertyConstraintViolationException, PropertyTypeMismatchException {
      if (valenv == Environment.CURRENT_VM) {
         String hmacKey = StringPropertyValueReader.effectiveStringConfigValue(pc, "sharedSecret", env, context);
         Optional<JwtSettingsValidationError> signerError = HmacJwtSigner.validate(JwsAlgorithmHmac.HS512, hmacKey);
         Optional<JwtSettingsValidationError> verifierError = JwtSignatureVerifierImpl.validate(JwsAlgorithmHmac.HS512, hmacKey);
         if (signerError.isPresent() || verifierError.isPresent()) {
            JwtSettingsValidationError error = signerError.orElseGet(verifierError::get);
            throw new PropertyConstraintViolationException("sharedSecret", pc, env, context, error.getMessage(), (Throwable)error.getCause().orElse(null));
         }
      }
   }

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

   @SuppressFBWarnings(justification = "generated code")
   public ApiPolicyServiceAppConfig() {
   }

   @SuppressFBWarnings(justification = "generated code")
   public ApiPolicyServiceAppConfig(
      TechClientRepositoryConfig repository,
      String sharedSecret,
      ContextExtractor contextExtractor,
      UserTrailLogRepositoryConfig logUserTrailToDatabase,
      CorrelationIdSettingsConfig correlationIdSettings
   ) {
      this.repository = repository;
      this.sharedSecret = sharedSecret;
      this.contextExtractor = contextExtractor;
      this.logUserTrailToDatabase = logUserTrailToDatabase;
      this.correlationIdSettings = correlationIdSettings;
   }

   @SuppressFBWarnings(justification = "generated code")
   public static class ApiPolicyServiceAppConfigBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private TechClientRepositoryConfig repository;
      @SuppressFBWarnings(justification = "generated code")
      private String sharedSecret;
      @SuppressFBWarnings(justification = "generated code")
      private ContextExtractor contextExtractor;
      @SuppressFBWarnings(justification = "generated code")
      private UserTrailLogRepositoryConfig logUserTrailToDatabase;
      @SuppressFBWarnings(justification = "generated code")
      private CorrelationIdSettingsConfig correlationIdSettings;

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

      @SuppressFBWarnings(justification = "generated code")
      public ApiPolicyServiceAppConfig.ApiPolicyServiceAppConfigBuilder repository(TechClientRepositoryConfig repository) {
         this.repository = repository;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public ApiPolicyServiceAppConfig.ApiPolicyServiceAppConfigBuilder sharedSecret(String sharedSecret) {
         this.sharedSecret = sharedSecret;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public ApiPolicyServiceAppConfig.ApiPolicyServiceAppConfigBuilder contextExtractor(ContextExtractor contextExtractor) {
         this.contextExtractor = contextExtractor;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public ApiPolicyServiceAppConfig.ApiPolicyServiceAppConfigBuilder logUserTrailToDatabase(UserTrailLogRepositoryConfig logUserTrailToDatabase) {
         this.logUserTrailToDatabase = logUserTrailToDatabase;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public ApiPolicyServiceAppConfig.ApiPolicyServiceAppConfigBuilder correlationIdSettings(CorrelationIdSettingsConfig correlationIdSettings) {
         this.correlationIdSettings = correlationIdSettings;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public ApiPolicyServiceAppConfig build() {
         return new ApiPolicyServiceAppConfig(
            this.repository, this.sharedSecret, this.contextExtractor, this.logUserTrailToDatabase, this.correlationIdSettings
         );
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "ApiPolicyServiceAppConfig.ApiPolicyServiceAppConfigBuilder(repository="
            + this.repository
            + ", sharedSecret="
            + this.sharedSecret
            + ", contextExtractor="
            + this.contextExtractor
            + ", logUserTrailToDatabase="
            + this.logUserTrailToDatabase
            + ", correlationIdSettings="
            + this.correlationIdSettings
            + ")";
      }
   }
}
