package com.airlock.iam.common.application.configuration.accountlink.persistence;

import com.airlock.iam.common.domain.service.accountlink.persistence.AccountLinkPersister;
import com.airlock.iam.common.infrastructure.repository.AccountLinkDatabasePersisterImpl;
import com.airlock.iam.core.infrastructure.db.OracleSequenceCheck;
import com.airlock.iam.core.misc.persistency.SqlDataSource;
import com.airlock.iam.database.infrastructure.IamTableMapping;
import com.airlock.iam.database.infrastructure.JooqConfig;
import com.airlock.iam.database.infrastructure.TenantId;
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.lifecycle.AfterConfigActivation;
import com.airlock.iam.plugin.api.annotation.property.BooleanProperty;
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.google.common.annotations.VisibleForTesting;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;

@Plugin(
   displayName = "Account Link Database Repository",
   description = "Account Link Repository for relational databases. Stores information about linked accounts."
)
@PluginLicenseRestriction(moduleAffiliation = {"OAuthAccountLinking", "OAuthSocialRegistration"})
public final class AccountLinkDatabasePersisterConfig implements AccountLinkPersisterConfig {
   public static final String ORACLE_SEQUENCE_NAME = "account_link_seq";
   @PluginProperty(displayName = "SQL Data Source", description = "Defines how connections to the database are obtained.", optional = false)
   private SqlDataSource sqlDataSource;
   @VisibleForTesting
   static final String SQL_DATA_SOURCE_FIELD = "sqlDataSource";
   @StringProperty(description = "The name of the database table containing the account links.", optional = true, defaultValue = "account_link")
   private String tableName;
   @StringProperty(
      description = "The name of the database sequence providing primary keys (Oracle only). If left empty, Airlock IAM expects the database to support auto-increment columns (SQL Server, MySQL).",
      optional = true,
      suggestedValues = "account_link_seq"
   )
   private String sequenceName;
   @VisibleForTesting
   static final String SEQUENCE_NAME_FIELD = "sequenceName";
   @StringProperty(
      displayName = "Tenant ID",
      description = "The value which is added to account links to distinguish between different tenants. The value is also used when retrieving account links from the persistence. <br/>If no value is configured, then 'no_tenant' is used as value on the database.",
      optional = true,
      maxLength = 50,
      exampleValues = {"customerA", "customerB"},
      validationRegex = "(?!no_tenant$).*"
   )
   private String tenantId;
   @VisibleForTesting
   static final String TENANT_ID_FIELD = "tenantId";
   @BooleanProperty(
      displayName = "Log Queries",
      description = "Enable to log SQL queries (only effective if the log level is at least INFO). Attention: query values (including potentially sensitive data) will be logged as well.",
      optional = true,
      defaultValue = "false"
   )
   private boolean logQueries;
   private AccountLinkPersister persister;

   public AccountLinkDatabasePersisterConfig(SqlDataSource sqlDataSource, String tableName, String sequenceName, String tenantId, boolean logQueries) {
      this.sqlDataSource = sqlDataSource;
      this.tableName = tableName;
      this.sequenceName = sequenceName;
      this.tenantId = tenantId;
      this.logQueries = logQueries;
   }

   @ConfigurationValidator
   static void checkSequencePresentIfOracleDetected(
      PluginConfiguration config, ConfigurationEnvironment env, ConfigurationContext context, Environment validationEnvironment
   ) throws ConfigurationException {
      OracleSequenceCheck.oracleSequenceCheck("sqlDataSource", "sequenceName").check(config, env, context, validationEnvironment);
   }

   @AfterConfigActivation
   void initPersister() {
      this.persister = new AccountLinkDatabasePersisterImpl(this.jooqConfig());
   }

   @Override
   public AccountLinkPersister persister() {
      return this.persister;
   }

   @VisibleForTesting
   @Override
   public JooqConfig jooqConfig() {
      return new JooqConfig(
         this.getSqlDataSource(),
         this.getSqlDataSource().getDbType(),
         this.sequenceName,
         IamTableMapping.iamTableMappings(this.tableName, "account_link"),
         this.logQueries,
         TenantId.tenantId(this.tenantId != null ? this.tenantId : "no_tenant")
      );
   }

   public SqlDataSource getSqlDataSource() {
      return this.sqlDataSource;
   }

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

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

   @SuppressFBWarnings(justification = "generated code")
   public static class AccountLinkDatabasePersisterConfigBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private SqlDataSource sqlDataSource;
      @SuppressFBWarnings(justification = "generated code")
      private String tableName;
      @SuppressFBWarnings(justification = "generated code")
      private String sequenceName;
      @SuppressFBWarnings(justification = "generated code")
      private String tenantId;
      @SuppressFBWarnings(justification = "generated code")
      private boolean logQueries;

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

      @SuppressFBWarnings(justification = "generated code")
      public AccountLinkDatabasePersisterConfig.AccountLinkDatabasePersisterConfigBuilder sqlDataSource(SqlDataSource sqlDataSource) {
         this.sqlDataSource = sqlDataSource;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public AccountLinkDatabasePersisterConfig.AccountLinkDatabasePersisterConfigBuilder tableName(String tableName) {
         this.tableName = tableName;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public AccountLinkDatabasePersisterConfig.AccountLinkDatabasePersisterConfigBuilder sequenceName(String sequenceName) {
         this.sequenceName = sequenceName;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public AccountLinkDatabasePersisterConfig.AccountLinkDatabasePersisterConfigBuilder tenantId(String tenantId) {
         this.tenantId = tenantId;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public AccountLinkDatabasePersisterConfig.AccountLinkDatabasePersisterConfigBuilder logQueries(boolean logQueries) {
         this.logQueries = logQueries;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public AccountLinkDatabasePersisterConfig build() {
         return new AccountLinkDatabasePersisterConfig(this.sqlDataSource, this.tableName, this.sequenceName, this.tenantId, this.logQueries);
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "AccountLinkDatabasePersisterConfig.AccountLinkDatabasePersisterConfigBuilder(sqlDataSource="
            + this.sqlDataSource
            + ", tableName="
            + this.tableName
            + ", sequenceName="
            + this.sequenceName
            + ", tenantId="
            + this.tenantId
            + ", logQueries="
            + this.logQueries
            + ")";
      }
   }
}
