package com.airlock.iam.common.application.configuration.state;

import com.airlock.iam.common.api.application.configuration.state.StateRepositoryConfig;
import com.airlock.iam.common.infrastructure.state.redis.LockKeepAlivePoller;
import com.airlock.iam.core.infrastructure.resourcepool.ResourcePool;
import com.airlock.iam.plugin.api.annotation.config.ConfigurationValidator;
import com.airlock.iam.plugin.api.annotation.lifecycle.AfterConfigActivation;
import com.airlock.iam.plugin.api.annotation.lifecycle.BeforePluginDeactivation;
import com.airlock.iam.plugin.api.annotation.property.BooleanProperty;
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.ConfigurationException;
import com.airlock.iam.plugin.api.exception.PropertyConfigurationException;
import com.airlock.iam.plugin.api.valuereader.BooleanPropertyValueReader;
import com.airlock.iam.plugin.api.valuereader.StringPropertyValueReader;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import org.redisson.api.RedissonClient;

public abstract class AbstractRedisStateRepositoryConfig implements StateRepositoryConfig, ResourcePool.TrackableResourceSupplier<RedissonClient> {
   private static final ResourcePool<AbstractRedisStateRepositoryConfig, RedissonClient> POOL = new ResourcePool<>();
   @PluginProperty(
      description = "<p>Encryption settings defining whether and how state information in Redis is encrypted. IAM state contains sensitive information. State encryption prevents other systems from reading and modifying IAM state information.</p><p>To enable state encryption with non-encrypted state already present, use the \"Migrating State Encryption\" plugin.</p>",
      optional = false
   )
   @GroupProperty(groupIdentifier = "Basic Settings")
   private StateEncryptionConfig encryption;
   @BooleanProperty(
      description = "If enabled, session data for the Loginapp and Adminapp is stored in a flat key naming scheme that is backward compatible to IAM 8.1.\nIf disabled (default for new installations as of IAM 8.2), all keys are stored in a structured naming scheme.\n<p>When changing this setting after state has already been stored in Redis, that state will be lost and the corresponding sessions will be terminated.</p>\n",
      optional = true,
      defaultValue = "false",
      displayName = "Use Legacy Key Format"
   )
   @GroupProperty(groupIdentifier = "Advanced Settings")
   private boolean legacyKeyFormat;
   private static final String LEGACY_KEY_FORMAT_PROPERTY = "legacyKeyFormat";
   @StringProperty(
      description = "<p>A string that will be used as a namespace for the keys in Redis.</p>\n<p>This is useful if, for example, multiple IAM instances share the same Redis instance and one must ensure\nthat their Redis keys don't interfere with each other</p>\n<p>When changing this setting after state has already been stored in Redis, that state will be lost and the corresponding sessions will be terminated.</p>\n",
      optional = true,
      validationRegex = "^[A-Za-z0-9]+$",
      defaultValue = "default"
   )
   @GroupProperty(groupIdentifier = "Advanced Settings")
   private String namespace;
   private static final String NAMESPACE_PROPERTY = "namespace";
   private static final String NAMESPACE_DEFAULT_VALUE = "default";

   protected AbstractRedisStateRepositoryConfig(StateEncryptionConfig encryption, boolean legacyKeyFormat, String namespace) {
      this.encryption = encryption;
      this.legacyKeyFormat = legacyKeyFormat;
      this.namespace = namespace;
   }

   public boolean useLegacyKeyFormat() {
      return this.legacyKeyFormat;
   }

   public String getNamespace() {
      return this.namespace;
   }

   public StateEncryptionConfig getEncryption() {
      return this.encryption;
   }

   @AfterConfigActivation
   void afterConfigActivation() {
      POOL.onActivation(this);
      if (!POOL.isEmpty()) {
         LockKeepAlivePoller.getInstance().startPolling(POOL::getResources);
      }
   }

   @BeforePluginDeactivation
   public void cleanup() {
      POOL.onDeactivation(this);
      if (POOL.isEmpty()) {
         LockKeepAlivePoller.getInstance().stopPolling();
      }
   }

   public RedissonClient getRedissonClient() {
      return POOL.getResource(this);
   }

   public void closeResource(RedissonClient client) {
      client.shutdown();
   }

   @ConfigurationValidator
   public static void checkLegacyFormatAndNamespaceCompatibility(
      PluginConfiguration conf, ConfigurationEnvironment env, ConfigurationContext context, Environment validationEnvironment
   ) throws ConfigurationException {
      boolean legacyKeyFormat = BooleanPropertyValueReader.effectiveBooleanPropertyValue(conf, "legacyKeyFormat", env, context);
      if (legacyKeyFormat) {
         String namespace = StringPropertyValueReader.effectiveStringConfigValue(conf, "namespace", env, context);
         if (!"default".equals(namespace)) {
            throw new PropertyConfigurationException(conf, env, context, "Namespace cannot be set if 'Legacy Key Format' is enabled.", new String[0]);
         }
      }
   }

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