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

import com.airlock.iam.base.misc.log.LogUtil;
import com.airlock.iam.common.domain.service.jwt.encryption.JwtTicketDecrypter;
import com.airlock.iam.common.domain.service.jwt.encryption.JwtTicketEncrypter;
import com.airlock.iam.common.domain.service.jwt.signature.JwtSignatureVerifier;
import com.airlock.iam.common.domain.service.jwt.signature.JwtSigner;
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.IntegerProperty;
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.ConfigurationException;
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.google.common.annotations.VisibleForTesting;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Optional;
import java.util.regex.Pattern;

@Plugin(
   description = "<p>IAM will redirect the user to the configured remote consent URL to confirm OAuth 2.0 scopes.</p><p>The remote site must implement the Remote Consent Protocol. Please refer to the documentation for further information.</p>",
   displayName = "OAuth 2.0 Remote Consent"
)
@Groups(
   groupSequence = {"Request Settings", "Response Settings", "Remote Consent Site Settings"},
   openGroups = {"Request Settings", "Response Settings", "Remote Consent Site Settings"}
)
@PluginLicenseRestriction(moduleAffiliation = "RemoteConsent")
public final class OAuth2RemoteConsentConfig implements OAuth2ConsentConfig {
   static final String REQUEST_SETTINGS = "Request Settings";
   static final String RESPONSE_SETTINGS = "Response Settings";
   static final String REMOTE_CONSENT_SITE_SETTINGS = "Remote Consent Site Settings";
   public static final String OAUTH_2_0_REMOTE_CONSENT_DISPLAY_NAME = "OAuth 2.0 Remote Consent";
   @StringProperty(
      description = "URL for the remote consent page to redirect the user to. For security reasons, only https URLs are allowed.",
      displayName = "Remote Consent URL",
      exampleValues = "https://example.com/remoteconsent",
      optional = false
   )
   @GroupProperty(groupIdentifier = "Request Settings")
   private String remoteConsentUrl;
   public static final String REMOTE_CONSENT_URL = "remoteConsentUrl";
   @PluginProperty(
      description = "Settings that are used for signing the JWT contained in the consent request. This JWT is sent to the remote site.",
      displayName = "JWT Signer",
      optional = false
   )
   @GroupProperty(groupIdentifier = "Request Settings")
   private JwtSigner signer;
   @PluginProperty(
      description = "Settings that are used for encrypting the JWT contained in the consent request.",
      displayName = "JWT Encrypter",
      optional = false
   )
   @GroupProperty(groupIdentifier = "Request Settings")
   private JwtTicketEncrypter encrypter;
   @IntegerProperty(
      description = "Number of seconds JWT contained in the consent request is valid for.",
      displayName = "Validity [s]",
      minValue = 1,
      defaultValue = 1800,
      exampleValues = {60, 1800},
      optional = true
   )
   @GroupProperty(groupIdentifier = "Request Settings")
   private int validity;
   @StringProperty(
      description = "URL for the callback from the remote consent page. This URL is included in the consent request JWT. The remote site must use this URL to redirect the user back to IAM. For security reasons, only https URLs are allowed.",
      displayName = "Callback URL",
      exampleValues = {"https://iam.example.com/auth-login/ui/app/auth/oauth2/consent/confirm", "https://iam.example.com/auth-login/oauth2-confirm"},
      optional = false
   )
   @GroupProperty(groupIdentifier = "Response Settings")
   private String callbackUrl;
   public static final String CALLBACK_URL = "callbackUrl";
   @PluginProperty(
      description = "Settings that are used for verifying the MAC or signature of the JWT containing the user consent. This JWT is sent by the remote site.",
      displayName = "JWT Signature Verifier",
      optional = false
   )
   @GroupProperty(groupIdentifier = "Response Settings")
   private JwtSignatureVerifier verifier;
   @PluginProperty(description = "Settings that are used for decrypting the JWT sent by the remote site.", displayName = "JWT Decrypter", optional = false)
   @GroupProperty(groupIdentifier = "Response Settings")
   private JwtTicketDecrypter decrypter;
   @StringProperty(
      description = "<p>The Airlock Gateway (WAF) role / credential that is set when accessing the remote consent site.</p><p>This can be used when IAM and the remote consent site provider are behind the same Airlock Gateway and you want to restrict the access to the remote consent site by a specific Gateway mapping.</p><p>The name of the credential can be followed by a colon and the <em>idle timeout</em> of the credential in seconds, e.g. \"myrole:300\" sets the credential \"myrole\" that will expire after 5 minutes of client inactivity.</p><p>With a second colon and a second number, the <em>life-time</em> can be set, e.g. \"myrole:300:3600\" will set the credential \"myrole\" for a maximum of 1 hour, but it will also expire after 5 minutes of client inactivity.</p>",
      displayName = "Airlock Gateway (WAF) Role for Remote Consent Site",
      exampleValues = "remote_consent_site",
      optional = true
   )
   @GroupProperty(groupIdentifier = "Remote Consent Site Settings")
   private String airlockGatewayRole;
   public static final String AIRLOCK_GATEWAY_ROLE_FIELD = "airlockGatewayRole";
   private static final Pattern WAF_CREDENTIAL_WITH_TIMEOUTS_PATTERN = Pattern.compile("^[^:]+(:\\d+){0,2}$");

   @ConfigurationValidator
   public static void validateRemoteConsentUrl(PluginConfiguration config, ConfigurationEnvironment configEnv, ConfigurationContext context, Environment env) throws ConfigurationException {
      validateUrlProperty(config, configEnv, context, "remoteConsentUrl");
   }

   @ConfigurationValidator
   public static void validateCallbackUrl(PluginConfiguration config, ConfigurationEnvironment configEnv, ConfigurationContext context, Environment env) throws ConfigurationException {
      validateUrlProperty(config, configEnv, context, "callbackUrl");
   }

   @VisibleForTesting
   static void validateUrlProperty(PluginConfiguration config, ConfigurationEnvironment configEnv, ConfigurationContext context, String propertyName) throws PropertyTypeMismatchException, PropertyConstraintViolationException {
      String value = StringPropertyValueReader.effectiveStringConfigValue(config, propertyName, configEnv, context);
      URL url = validateUrl(config, configEnv, context, value, propertyName);
      validateHttps(config, configEnv, context, url, propertyName);
   }

   private static URL validateUrl(
      PluginConfiguration config, ConfigurationEnvironment configEnv, ConfigurationContext context, String value, String propertyName
   ) throws PropertyConstraintViolationException {
      try {
         return new URL(value);
      } catch (MalformedURLException e) {
         throw new PropertyConstraintViolationException(propertyName, config, configEnv, context, "Malformed URL: " + e.getMessage());
      }
   }

   private static void validateHttps(PluginConfiguration config, ConfigurationEnvironment configEnv, ConfigurationContext context, URL url, String propertyName) throws PropertyConstraintViolationException {
      if (!"https".equals(url.getProtocol())) {
         throw new PropertyConstraintViolationException(
            propertyName, config, configEnv, context, "Unsupported URL protocol " + LogUtil.quote(url.getProtocol()) + ". Please use \"https\"."
         );
      }
   }

   @ConfigurationValidator
   public static void validateRemoteConsentSiteRole(
      PluginConfiguration config, ConfigurationEnvironment configEnv, ConfigurationContext context, Environment env
   ) throws ConfigurationException {
      String value = StringPropertyValueReader.effectiveStringConfigValue(config, "airlockGatewayRole", configEnv, context);
      if (value != null && (!WAF_CREDENTIAL_WITH_TIMEOUTS_PATTERN.matcher(value).matches() || "none".equals(value))) {
         throw new PropertyConstraintViolationException("airlockGatewayRole", config, configEnv, context, "Invalid credential syntax");
      }
   }

   public Optional<String> wafCredential() {
      return Optional.ofNullable(this.airlockGatewayRole);
   }

   @Override
   public boolean isEnableConsentStorage() {
      return false;
   }

   public String getRemoteConsentUrl() {
      return this.remoteConsentUrl;
   }

   public JwtSigner getSigner() {
      return this.signer;
   }

   public JwtTicketEncrypter getEncrypter() {
      return this.encrypter;
   }

   public int getValidity() {
      return this.validity;
   }

   public String getCallbackUrl() {
      return this.callbackUrl;
   }

   public JwtSignatureVerifier getVerifier() {
      return this.verifier;
   }

   public JwtTicketDecrypter getDecrypter() {
      return this.decrypter;
   }

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

   @SuppressFBWarnings(justification = "generated code")
   public OAuth2RemoteConsentConfig(
      String remoteConsentUrl,
      JwtSigner signer,
      JwtTicketEncrypter encrypter,
      int validity,
      String callbackUrl,
      JwtSignatureVerifier verifier,
      JwtTicketDecrypter decrypter,
      String airlockGatewayRole
   ) {
      this.remoteConsentUrl = remoteConsentUrl;
      this.signer = signer;
      this.encrypter = encrypter;
      this.validity = validity;
      this.callbackUrl = callbackUrl;
      this.verifier = verifier;
      this.decrypter = decrypter;
      this.airlockGatewayRole = airlockGatewayRole;
   }

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

   @SuppressFBWarnings(justification = "generated code")
   public static class OAuth2RemoteConsentConfigBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private String remoteConsentUrl;
      @SuppressFBWarnings(justification = "generated code")
      private JwtSigner signer;
      @SuppressFBWarnings(justification = "generated code")
      private JwtTicketEncrypter encrypter;
      @SuppressFBWarnings(justification = "generated code")
      private int validity;
      @SuppressFBWarnings(justification = "generated code")
      private String callbackUrl;
      @SuppressFBWarnings(justification = "generated code")
      private JwtSignatureVerifier verifier;
      @SuppressFBWarnings(justification = "generated code")
      private JwtTicketDecrypter decrypter;
      @SuppressFBWarnings(justification = "generated code")
      private String airlockGatewayRole;

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

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2RemoteConsentConfig.OAuth2RemoteConsentConfigBuilder remoteConsentUrl(String remoteConsentUrl) {
         this.remoteConsentUrl = remoteConsentUrl;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2RemoteConsentConfig.OAuth2RemoteConsentConfigBuilder signer(JwtSigner signer) {
         this.signer = signer;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2RemoteConsentConfig.OAuth2RemoteConsentConfigBuilder encrypter(JwtTicketEncrypter encrypter) {
         this.encrypter = encrypter;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2RemoteConsentConfig.OAuth2RemoteConsentConfigBuilder validity(int validity) {
         this.validity = validity;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2RemoteConsentConfig.OAuth2RemoteConsentConfigBuilder callbackUrl(String callbackUrl) {
         this.callbackUrl = callbackUrl;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2RemoteConsentConfig.OAuth2RemoteConsentConfigBuilder verifier(JwtSignatureVerifier verifier) {
         this.verifier = verifier;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2RemoteConsentConfig.OAuth2RemoteConsentConfigBuilder decrypter(JwtTicketDecrypter decrypter) {
         this.decrypter = decrypter;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2RemoteConsentConfig.OAuth2RemoteConsentConfigBuilder airlockGatewayRole(String airlockGatewayRole) {
         this.airlockGatewayRole = airlockGatewayRole;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2RemoteConsentConfig build() {
         return new OAuth2RemoteConsentConfig(
            this.remoteConsentUrl, this.signer, this.encrypter, this.validity, this.callbackUrl, this.verifier, this.decrypter, this.airlockGatewayRole
         );
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "OAuth2RemoteConsentConfig.OAuth2RemoteConsentConfigBuilder(remoteConsentUrl="
            + this.remoteConsentUrl
            + ", signer="
            + this.signer
            + ", encrypter="
            + this.encrypter
            + ", validity="
            + this.validity
            + ", callbackUrl="
            + this.callbackUrl
            + ", verifier="
            + this.verifier
            + ", decrypter="
            + this.decrypter
            + ", airlockGatewayRole="
            + this.airlockGatewayRole
            + ")";
      }
   }
}
