package com.airlock.iam.common.infrastructure.rememberme;

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.misc.exception.Exceptions;
import com.airlock.iam.base.misc.log.LogUtil;
import com.airlock.iam.common.domain.service.rememberme.RememberMeCommonRepository;
import com.airlock.iam.core.infrastructure.db.JooqProvider;
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.RememberMeTokenTable;
import java.util.function.Supplier;
import lombok.NonNull;
import org.jooq.Condition;
import org.jooq.DSLContext;

public class RememberMeCommonRepositoryImpl implements RememberMeCommonRepository {
   @NonNull
   private final Supplier<DSLContext> jooqDslProvider;
   private final TenantId tenantId;

   public RememberMeCommonRepositoryImpl(@NonNull JooqConfig jooqConfig) {
      if (jooqConfig == null) {
         throw new NullPointerException("jooqConfig is marked non-null but is null");
      }

      this.jooqDslProvider = () -> JooqProvider.jooqProvider(jooqConfig).db();
      this.tenantId = jooqConfig.tenantId();
   }

   @Override
   public boolean update(UserId oldUserId, UserId newUserId) {
      return Exceptions.wrapException(
         () -> (Boolean)JooqTransaction.jooqTransaction(this.db()).executeAndReturn(db -> this.updateUserId(oldUserId, newUserId, db)),
         e -> new FailureException(String.format("Error while renaming user on all Remember-Me token from %s to %s", oldUserId, newUserId), e)
      );
   }

   private boolean updateUserId(UserId oldUserId, UserId newUserId, DSLContext db) {
      int updatedRecords = db.update(RememberMeTokenTable.REMEMBER_ME_TOKEN)
         .set(RememberMeTokenTable.REMEMBER_ME_TOKEN.IAM_USER_ID, newUserId.getValue())
         .where(this.userIdMatches(oldUserId))
         .and(this.tenantMatches())
         .execute();
      if (updatedRecords == 0) {
         return false;
      } else if (updatedRecords > 0) {
         return true;
      } else {
         throw new FailureException(
            String.format(
               "Unexpected number of affected records while renaming user for all Remember-Me tokens from %s to %s",
               LogUtil.logSafeQuote(oldUserId),
               LogUtil.logSafeQuote(newUserId)
            )
         );
      }
   }

   @Override
   public int deleteBy(UserId userId) {
      return Exceptions.wrapException(
         () -> (Integer)JooqTransaction.jooqTransaction(this.db()).executeAndReturn(db -> this.deleteByUserId(userId, db)),
         e -> new FailureException(String.format("Error while deleting Remember-Me tokens for user %s", LogUtil.logSafeQuote(userId)), e)
      );
   }

   private int deleteByUserId(UserId userId, DSLContext db) {
      int deletedRecords = db.deleteFrom(RememberMeTokenTable.REMEMBER_ME_TOKEN).where(this.userIdMatches(userId)).and(this.tenantMatches()).execute();
      if (deletedRecords >= 0) {
         return deletedRecords;
      } else {
         throw new FailureException(
            String.format("Unexpected number of affected records while deleting Remember-Me tokens for user %s", LogUtil.logSafeQuote(userId))
         );
      }
   }

   private Condition userIdMatches(UserId userId) {
      return RememberMeTokenTable.REMEMBER_ME_TOKEN.IAM_USER_ID.eq(userId.getValue());
   }

   private Condition tenantMatches() {
      return RememberMeTokenTable.REMEMBER_ME_TOKEN.TENANT_ID.eq(this.tenantId.tenantId());
   }

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