package com.airlock.iam.admin.infrastructure.continuation;

import com.airlock.iam.admin.domain.model.token.password.AdminappFlowContinuationTokenRepository;
import com.airlock.iam.base.api.domain.model.exception.FailureException;
import com.airlock.iam.base.api.domain.model.user.UserId;
import com.airlock.iam.base.infrastructure.db.DbInstantConverter;
import com.airlock.iam.base.misc.exception.Exceptions;
import com.airlock.iam.core.infrastructure.db.JooqProvider;
import com.airlock.iam.database.infrastructure.DatabaseType;
import com.airlock.iam.database.infrastructure.JooqConfig;
import com.airlock.iam.database.infrastructure.JooqTransaction;
import com.airlock.iam.database.infrastructure.TenantId;
import com.airlock.iam.database.infrastructure.generated.schema.tables.FlowContinuationTokenTable;
import com.airlock.iam.database.infrastructure.generated.schema.tables.records.FlowContinuationTokenRecord;
import com.airlock.iam.flow.api.domain.model.flow.FlowId;
import com.airlock.iam.flow.shared.domain.model.continuation.FlowContinuationInformation;
import com.airlock.iam.flow.shared.domain.model.continuation.FlowContinuationTokenHash;
import com.airlock.iam.plugin.api.config.ConfigurationContext;
import java.util.Optional;
import java.util.function.Supplier;
import lombok.NonNull;
import org.jooq.DSLContext;
import org.jooq.InsertSetMoreStep;
import org.jooq.Record;
import org.jooq.Sequence;
import org.jooq.impl.DSL;
import org.jooq.impl.SQLDataType;

public final class AdminappFlowContinuationTokenRepositoryImpl implements AdminappFlowContinuationTokenRepository {
   @NonNull
   private final Supplier<DSLContext> jooqDslProvider;
   @NonNull
   private final TenantId tenantId;
   private final Optional<Sequence<Long>> sequence;

   public AdminappFlowContinuationTokenRepositoryImpl(JooqConfig jooq) {
      this.jooqDslProvider = () -> JooqProvider.jooqProvider(jooq).db();
      this.tenantId = jooq.tenantId();
      if (jooq.dbType().equals(DatabaseType.ORACLE)) {
         this.sequence = Optional.of(DSL.sequence(DSL.name("flow_continuation_token_seq"), SQLDataType.BIGINT));
      } else {
         this.sequence = Optional.empty();
      }
   }

   @Override
   public FlowContinuationInformation insert(FlowContinuationInformation info) {
      return (FlowContinuationInformation)Exceptions.wrapException(
         () -> (FlowContinuationInformation)JooqTransaction.jooqTransaction(this.db())
            .executeAndReturn(
               db -> {
                  InsertSetMoreStep<FlowContinuationTokenRecord> record = db.insertInto(FlowContinuationTokenTable.FLOW_CONTINUATION_TOKEN)
                     .set(FlowContinuationTokenTable.FLOW_CONTINUATION_TOKEN.TOKEN_HASH, info.tokenHash().base64Url())
                     .set(FlowContinuationTokenTable.FLOW_CONTINUATION_TOKEN.USER_ID, info.userId().getValue())
                     .set(FlowContinuationTokenTable.FLOW_CONTINUATION_TOKEN.TARGET_FLOW_ID, info.targetFlowId().getFlowId())
                     .set(FlowContinuationTokenTable.FLOW_CONTINUATION_TOKEN.CREATED_AT, DbInstantConverter.toNanoSecondsSinceEpoch(info.createdAt()))
                     .set(FlowContinuationTokenTable.FLOW_CONTINUATION_TOKEN.VALID_UNTIL, DbInstantConverter.toNanoSecondsSinceEpoch(info.validUntil()))
                     .set(
                        FlowContinuationTokenTable.FLOW_CONTINUATION_TOKEN.CONFIGURATION_CONTEXT,
                        info.context().isDefaultContext() ? "[DEFAULT]" : info.context().getIdentifier()
                     )
                     .set(FlowContinuationTokenTable.FLOW_CONTINUATION_TOKEN.TENANT_ID, this.tenantId.tenantId());
                  if (this.sequence.isPresent()) {
                     record = record.set(FlowContinuationTokenTable.FLOW_CONTINUATION_TOKEN.PK, this.sequence.get().nextval());
                  }

                  return this.mapFlowContinuationInformation((Record)record.returning().fetchOptional().orElseThrow());
               }
            ),
         e -> new FailureException("error inserting continuation data", e)
      );
   }

   private FlowContinuationInformation mapFlowContinuationInformation(Record record) {
      return new FlowContinuationInformation(
         FlowContinuationTokenHash.fromBase64Url((String)record.get(FlowContinuationTokenTable.FLOW_CONTINUATION_TOKEN.TOKEN_HASH)),
         UserId.userId((String)record.get(FlowContinuationTokenTable.FLOW_CONTINUATION_TOKEN.USER_ID)),
         new FlowId((String)record.get(FlowContinuationTokenTable.FLOW_CONTINUATION_TOKEN.TARGET_FLOW_ID)),
         DbInstantConverter.toInstant((Long)record.get(FlowContinuationTokenTable.FLOW_CONTINUATION_TOKEN.CREATED_AT)),
         DbInstantConverter.toInstant((Long)record.get(FlowContinuationTokenTable.FLOW_CONTINUATION_TOKEN.VALID_UNTIL)),
         ConfigurationContext.configurationContext((String)record.get(FlowContinuationTokenTable.FLOW_CONTINUATION_TOKEN.CONFIGURATION_CONTEXT))
      );
   }

   private DSLContext db() {
      return this.jooqDslProvider.get();
   }
}
