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

import com.airlock.iam.base.misc.log.LogUtil;
import com.airlock.iam.login.application.configuration.location.propagate.TargetURIResolverConfig;
import com.airlock.iam.login.application.configuration.targetapp.ApplicationIdConfig;
import com.airlock.iam.plugin.api.annotation.config.ConfigurationValidator;
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.PluginListProperty;
import com.airlock.iam.plugin.api.annotation.property.PluginProperty;
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.valuereader.PluginListPropertyValueReader;
import com.airlock.iam.plugin.api.valuereader.StringPropertyValueReader;
import com.airlock.iam.plugin.framework.misc.exception.MandatoryPropertyMissingException;
import com.airlock.iam.saml2.application.configuration.assertion.attribute.SamlFlowIdpAttributeConfig;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;

@Plugin(description = "<p>Configures a SAML 2.0 Service Provider (SP) for use in the Flow-Based Authentication.</p>", displayName = "SAML 2.0 Service Provider")
@PluginLicenseRestriction(moduleAffiliation = "SamlIdp")
public final class Saml2ServiceProviderAccessConfig {
   @PluginProperty(description = "Configures the Service Provider (SP) entity.", displayName = "SP Entity Config", optional = false)
   private Saml2SPEntityConfig spEntityConfig;
   public static final String SP_ENTITY_CONFIG_PROPERTY = "spEntityConfig";
   @PluginProperty(
      description = "Defines the default application ID of the authentication flow to start when an Authentication Request is received by this Service Provider.",
      displayName = "Default Flow Application ID",
      optional = false
   )
   private ApplicationIdConfig defaultFlowApplicationId;
   public static final String DEFAULT_FLOW_APPLICATION_ID_PROPERTY = "defaultFlowApplicationId";
   private static final String DEFAULT_FLOW_APPLICATION_ID_DISPLAY_NAME = "Default Flow Application ID";
   @PluginListProperty(
      description = "If the Authentication Request from the Service Provider contains any Requested Authentication Contexts (<tt>AuthnContextClassRef</tt> element), a specific flow can be started instead of the default flow.<br>The mappings are applied in the order in which they are configured. When left empty, the configured \"Default Flow Application ID\" authentication flow is started.",
      displayName = "RequestedAuthnContext to Flow Application ID",
      optional = true
   )
   private List<Saml2RequestedAuthnContextToApplicationIdConfig> requestedAuthnContextToFlowAppId;
   public static final String REQUESTED_AUTHN_CONTEXT_TO_FLOW_APP_ID_PROPERTY = "requestedAuthnContextToFlowAppId";
   @PluginProperty(
      description = "If this property is set, the RelayState during IdP-Initiated SSO will be set to the configured URI.",
      displayName = "Relay State URI (IdP-Initiated SSO)",
      optional = true
   )
   private TargetURIResolverConfig idpInitiatedRelayStateUri;
   @PluginListProperty(description = "List of Attributes to add to every Assertion.", optional = true)
   private List<SamlFlowIdpAttributeConfig> attributes;
   public static final String ATTRIBUTES_PROPERTY = "attributes";

   public Optional<TargetURIResolverConfig> getIdpInitiatedRelayStateUri() {
      return Optional.ofNullable(this.idpInitiatedRelayStateUri);
   }

   @ConfigurationValidator
   public static void validateNoDuplicateSamlAssertionAttributes(
      PluginConfiguration config, ConfigurationEnvironment env, ConfigurationContext context, Environment validationEnvironment
   ) throws ConfigurationException {
      Set<String> attributeNames = new HashSet<>();

      for (PluginConfiguration attribute : PluginListPropertyValueReader.effectivePluginListPropertyValue(config, "attributes", env, context)) {
         String attributeName = StringPropertyValueReader.effectiveStringConfigValue(attribute, "samlAttributeName", env, context);
         if (attributeName == null) {
            throw new MandatoryPropertyMissingException("samlAttributeName", attribute, env, context);
         }

         if (!attributeNames.add(attributeName)) {
            throw new PropertyConstraintViolationException(
               "attributes", config, env, context, "Found duplicate SAML 2.0 assertion attributes for name " + LogUtil.quote(attributeName)
            );
         }
      }
   }

   public Saml2SPEntityConfig getSpEntityConfig() {
      return this.spEntityConfig;
   }

   public ApplicationIdConfig getDefaultFlowApplicationId() {
      return this.defaultFlowApplicationId;
   }

   public List<Saml2RequestedAuthnContextToApplicationIdConfig> getRequestedAuthnContextToFlowAppId() {
      return this.requestedAuthnContextToFlowAppId;
   }

   public List<SamlFlowIdpAttributeConfig> getAttributes() {
      return this.attributes;
   }

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

   @SuppressFBWarnings(justification = "generated code")
   public Saml2ServiceProviderAccessConfig(
      Saml2SPEntityConfig spEntityConfig,
      ApplicationIdConfig defaultFlowApplicationId,
      List<Saml2RequestedAuthnContextToApplicationIdConfig> requestedAuthnContextToFlowAppId,
      TargetURIResolverConfig idpInitiatedRelayStateUri,
      List<SamlFlowIdpAttributeConfig> attributes
   ) {
      this.spEntityConfig = spEntityConfig;
      this.defaultFlowApplicationId = defaultFlowApplicationId;
      this.requestedAuthnContextToFlowAppId = requestedAuthnContextToFlowAppId;
      this.idpInitiatedRelayStateUri = idpInitiatedRelayStateUri;
      this.attributes = attributes;
   }

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

   @SuppressFBWarnings(justification = "generated code")
   public static class Saml2ServiceProviderAccessConfigBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private Saml2SPEntityConfig spEntityConfig;
      @SuppressFBWarnings(justification = "generated code")
      private ApplicationIdConfig defaultFlowApplicationId;
      @SuppressFBWarnings(justification = "generated code")
      private List<Saml2RequestedAuthnContextToApplicationIdConfig> requestedAuthnContextToFlowAppId;
      @SuppressFBWarnings(justification = "generated code")
      private TargetURIResolverConfig idpInitiatedRelayStateUri;
      @SuppressFBWarnings(justification = "generated code")
      private ArrayList<SamlFlowIdpAttributeConfig> attributes;

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

      @SuppressFBWarnings(justification = "generated code")
      public Saml2ServiceProviderAccessConfig.Saml2ServiceProviderAccessConfigBuilder spEntityConfig(Saml2SPEntityConfig spEntityConfig) {
         this.spEntityConfig = spEntityConfig;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Saml2ServiceProviderAccessConfig.Saml2ServiceProviderAccessConfigBuilder defaultFlowApplicationId(ApplicationIdConfig defaultFlowApplicationId) {
         this.defaultFlowApplicationId = defaultFlowApplicationId;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Saml2ServiceProviderAccessConfig.Saml2ServiceProviderAccessConfigBuilder requestedAuthnContextToFlowAppId(
         List<Saml2RequestedAuthnContextToApplicationIdConfig> requestedAuthnContextToFlowAppId
      ) {
         this.requestedAuthnContextToFlowAppId = requestedAuthnContextToFlowAppId;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Saml2ServiceProviderAccessConfig.Saml2ServiceProviderAccessConfigBuilder idpInitiatedRelayStateUri(
         TargetURIResolverConfig idpInitiatedRelayStateUri
      ) {
         this.idpInitiatedRelayStateUri = idpInitiatedRelayStateUri;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Saml2ServiceProviderAccessConfig.Saml2ServiceProviderAccessConfigBuilder attribute(SamlFlowIdpAttributeConfig attribute) {
         if (this.attributes == null) {
            this.attributes = new ArrayList<>();
         }

         this.attributes.add(attribute);
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Saml2ServiceProviderAccessConfig.Saml2ServiceProviderAccessConfigBuilder attributes(Collection<? extends SamlFlowIdpAttributeConfig> attributes) {
         if (attributes == null) {
            throw new NullPointerException("attributes cannot be null");
         }

         if (this.attributes == null) {
            this.attributes = new ArrayList<>();
         }

         this.attributes.addAll(attributes);
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Saml2ServiceProviderAccessConfig.Saml2ServiceProviderAccessConfigBuilder clearAttributes() {
         if (this.attributes != null) {
            this.attributes.clear();
         }

         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public Saml2ServiceProviderAccessConfig build() {
         return new Saml2ServiceProviderAccessConfig(
            this.spEntityConfig,
            this.defaultFlowApplicationId,
            this.requestedAuthnContextToFlowAppId,
            this.idpInitiatedRelayStateUri,
            switch (this.attributes == null ? 0 : this.attributes.size()) {
               case 0 -> Collections.emptyList();
               case 1 -> Collections.singletonList(this.attributes.get(0));
               default -> Collections.unmodifiableList(new ArrayList<>(this.attributes));
            }
         );
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "Saml2ServiceProviderAccessConfig.Saml2ServiceProviderAccessConfigBuilder(spEntityConfig="
            + this.spEntityConfig
            + ", defaultFlowApplicationId="
            + this.defaultFlowApplicationId
            + ", requestedAuthnContextToFlowAppId="
            + this.requestedAuthnContextToFlowAppId
            + ", idpInitiatedRelayStateUri="
            + this.idpInitiatedRelayStateUri
            + ", attributes="
            + this.attributes
            + ")";
      }
   }
}
