package com.airlock.iam.flow.shared.application.configuration;

import com.airlock.iam.common.api.application.configuration.valueprovider.StringValueProviderConfig;
import com.airlock.iam.core.misc.plugin.config.validation.CharsetValidator;
import com.airlock.iam.core.misc.util.ticket.TicketEncoder;
import com.airlock.iam.flow.api.application.configuration.valueprovider.ValueMapProviderConfig;
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.property.LongProperty;
import com.airlock.iam.plugin.api.annotation.property.PluginListProperty;
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 edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.nio.charset.Charset;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

@Plugin(description = "Provides an SSO ticket (e.g. a JWT ticket) as a string.")
public final class TicketStringProviderConfig implements StringValueProviderConfig {
   @PluginListProperty(
      description = "These plugins provide the map of all values that are available to create the ticket. Use the \"Key-Value Pairs\" property to define how the provided values are mapped into the ticket. The value providers are applied in the configured order. Later providers can overwrite values of earlier ones.",
      optional = true
   )
   private List<ValueMapProviderConfig> valueProviders;
   @PluginListProperty(
      description = "<p>List of key-value pairs that are included in the ticket. Each of them selects a value provided by the \"Value Providers\" and includes it in the ticket under a specified key.</p><p>Example: the Value Providers provide an entry with key \"user-id\" and value \"tester\". A key-value pair configuration with ticket key \"username\" and value selector \"user-id\" results in a ticket entry with key \"username\" and value \"tester\".</p>",
      displayName = "Key-Value Pairs",
      optional = true
   )
   private List<TicketKeyValue> keyValues;
   @LongProperty(
      description = "That validity duration of the ticket (in seconds).",
      displayName = "Validity [s]",
      exampleValues = {10L, 600L, 3600L},
      minValue = 1L,
      optional = true,
      defaultValue = 600L
   )
   private long validitySeconds;
   @StringProperty(description = "Encoding scheme for creating the ticket.", displayName = "URL Encoding Scheme", optional = true, defaultValue = "UTF-8")
   private String urlEncodingScheme;
   private static final String URL_ENCODING_SCHEME_FIELD = "urlEncodingScheme";
   @PluginProperty(
      description = "<p>The ticket encoder plugin used to encode the ticket to a string.</p><p><b>Attention:</b> Do not use the \"Plain Ticket Encoder\" when storing this value in a cookie, as it might contain illegal values for cookies.</p><p>Some ticket encoders do not support ticket expiry, i.e. they do not encode the ticket validity into the ticket.</p>",
      optional = false
   )
   private TicketEncoder ticketEncoder;

   public Duration getValidity() {
      return Duration.ofSeconds(this.validitySeconds);
   }

   public Charset getUrlEncodingScheme() {
      return Charset.forName(this.urlEncodingScheme);
   }

   public List<ValueMapProviderConfig> getValueProviders() {
      return this.valueProviders;
   }

   public List<TicketKeyValue> getKeyValues() {
      return this.keyValues;
   }

   public TicketEncoder getTicketEncoder() {
      return this.ticketEncoder;
   }

   @ConfigurationValidator
   static void assureConfigConstraints(
      PluginConfiguration config, ConfigurationEnvironment env, ConfigurationContext context, Environment validationEnvironment
   ) throws ConfigurationException {
      CharsetValidator.validateCharsetProperty("urlEncodingScheme", config, env, context);
   }

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

   @SuppressFBWarnings(justification = "generated code")
   public TicketStringProviderConfig(
      List<ValueMapProviderConfig> valueProviders, List<TicketKeyValue> keyValues, long validitySeconds, String urlEncodingScheme, TicketEncoder ticketEncoder
   ) {
      this.valueProviders = valueProviders;
      this.keyValues = keyValues;
      this.validitySeconds = validitySeconds;
      this.urlEncodingScheme = urlEncodingScheme;
      this.ticketEncoder = ticketEncoder;
   }

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

   @SuppressFBWarnings(justification = "generated code")
   public static class TicketStringProviderConfigBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private ArrayList<ValueMapProviderConfig> valueProviders;
      @SuppressFBWarnings(justification = "generated code")
      private ArrayList<TicketKeyValue> keyValues;
      @SuppressFBWarnings(justification = "generated code")
      private long validitySeconds;
      @SuppressFBWarnings(justification = "generated code")
      private String urlEncodingScheme;
      @SuppressFBWarnings(justification = "generated code")
      private TicketEncoder ticketEncoder;

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

      @SuppressFBWarnings(justification = "generated code")
      public TicketStringProviderConfig.TicketStringProviderConfigBuilder valueProvider(ValueMapProviderConfig valueProvider) {
         if (this.valueProviders == null) {
            this.valueProviders = new ArrayList<>();
         }

         this.valueProviders.add(valueProvider);
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public TicketStringProviderConfig.TicketStringProviderConfigBuilder valueProviders(Collection<? extends ValueMapProviderConfig> valueProviders) {
         if (valueProviders == null) {
            throw new NullPointerException("valueProviders cannot be null");
         }

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

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

      @SuppressFBWarnings(justification = "generated code")
      public TicketStringProviderConfig.TicketStringProviderConfigBuilder clearValueProviders() {
         if (this.valueProviders != null) {
            this.valueProviders.clear();
         }

         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public TicketStringProviderConfig.TicketStringProviderConfigBuilder keyValue(TicketKeyValue keyValue) {
         if (this.keyValues == null) {
            this.keyValues = new ArrayList<>();
         }

         this.keyValues.add(keyValue);
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public TicketStringProviderConfig.TicketStringProviderConfigBuilder keyValues(Collection<? extends TicketKeyValue> keyValues) {
         if (keyValues == null) {
            throw new NullPointerException("keyValues cannot be null");
         }

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

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

      @SuppressFBWarnings(justification = "generated code")
      public TicketStringProviderConfig.TicketStringProviderConfigBuilder clearKeyValues() {
         if (this.keyValues != null) {
            this.keyValues.clear();
         }

         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public TicketStringProviderConfig.TicketStringProviderConfigBuilder validitySeconds(long validitySeconds) {
         this.validitySeconds = validitySeconds;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public TicketStringProviderConfig.TicketStringProviderConfigBuilder urlEncodingScheme(String urlEncodingScheme) {
         this.urlEncodingScheme = urlEncodingScheme;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public TicketStringProviderConfig.TicketStringProviderConfigBuilder ticketEncoder(TicketEncoder ticketEncoder) {
         this.ticketEncoder = ticketEncoder;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public TicketStringProviderConfig build() {
         List valueProviders = switch (this.valueProviders == null ? 0 : this.valueProviders.size()) {
            case 0 -> Collections.emptyList();
            case 1 -> Collections.singletonList(this.valueProviders.get(0));
            default -> Collections.unmodifiableList(new ArrayList<>(this.valueProviders));
         };

         return new TicketStringProviderConfig(valueProviders, switch (this.keyValues == null ? 0 : this.keyValues.size()) {
            case 0 -> Collections.emptyList();
            case 1 -> Collections.singletonList(this.keyValues.get(0));
            default -> Collections.unmodifiableList(new ArrayList<>(this.keyValues));
         }, this.validitySeconds, this.urlEncodingScheme, this.ticketEncoder);
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "TicketStringProviderConfig.TicketStringProviderConfigBuilder(valueProviders="
            + this.valueProviders
            + ", keyValues="
            + this.keyValues
            + ", validitySeconds="
            + this.validitySeconds
            + ", urlEncodingScheme="
            + this.urlEncodingScheme
            + ", ticketEncoder="
            + this.ticketEncoder
            + ")";
      }
   }
}
