package com.airlock.iam.ssi.infrastructure.repository;

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.core.infrastructure.db.JooqProvider;
import com.airlock.iam.database.infrastructure.JooqConfig;
import com.airlock.iam.database.infrastructure.JooqTransaction;
import com.airlock.iam.database.infrastructure.generated.schema.tables.VerifiableCredentialTable;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import com.airlock.iam.ssi.domain.model.vc.VerifiableCredential;
import com.airlock.iam.ssi.domain.model.vc.VerifiableCredentialId;
import com.airlock.iam.ssi.domain.model.vc.VerifiableCredentialRepository;
import jakarta.inject.Inject;
import jakarta.inject.Provider;
import java.util.List;
import java.util.Set;
import org.jooq.Condition;
import org.jooq.DSLContext;
import org.jooq.Record;
import org.jooq.SelectFieldOrAsterisk;

public class JooqVerifiableCredentialRepository implements VerifiableCredentialRepository {
   private static final ELogger LOG = ELoggerFactory.getLogger(JooqVerifiableCredentialRepository.class);
   private final Provider<JooqConfig> jooqConfig;

   @Inject
   public JooqVerifiableCredentialRepository(Provider<JooqConfig> jooqConfig) {
      this.jooqConfig = jooqConfig;
   }

   @Override
   public void save(VerifiableCredential vc) {
      LOG.debug(String.format("Saving VC %s for user %s.", LogUtil.logSafeQuote(vc.credentialId()), LogUtil.logSafeQuote(vc.userId())));
      Exceptions.wrapException(
         () -> JooqTransaction.jooqTransaction(this.db()).execute(db -> this.save(db, vc)),
         e -> new FailureException(
            String.format("Failed to save VC %s for user %s.", LogUtil.logSafeQuote(vc.credentialId()), LogUtil.logSafeQuote(vc.userId())), e
         )
      );
   }

   @Override
   public Set<VerifiableCredential> findByUserId(UserId userId) {
      LOG.debug(String.format("Finding VCs by user ID %s", LogUtil.logSafeQuote(userId)));
      return Set.copyOf(
         Exceptions.wrapException(
            () -> this.findByUserIdInternal(userId),
            e -> new FailureException(String.format("Failed to find VCs by user ID %s", LogUtil.logSafeQuote(userId)), e)
         )
      );
   }

   @Override
   public void deleteByUserId(UserId user) {
      LOG.debug(String.format("Deleting VCs by user ID %s", LogUtil.logSafeQuote(user)));
      Exceptions.wrapException(
         () -> this.db().deleteFrom(VerifiableCredentialTable.VERIFIABLE_CREDENTIAL).where(userIdEquals(user)).and(this.tenantMatches()).execute(),
         e -> new FailureException(String.format("Failed to delete VCs by user ID %s", LogUtil.logSafeQuote(user)), e)
      );
   }

   @Override
   public void updateUserName(String originalName, String newName) {
      LOG.debug(String.format("Updating user name from %s to %s", LogUtil.logSafeQuote(originalName), LogUtil.logSafeQuote(newName)));
      Exceptions.wrapException(
         () -> this.db()
            .update(VerifiableCredentialTable.VERIFIABLE_CREDENTIAL)
            .set(VerifiableCredentialTable.VERIFIABLE_CREDENTIAL.USER_ID, newName)
            .where(VerifiableCredentialTable.VERIFIABLE_CREDENTIAL.USER_ID.eq(originalName))
            .and(this.tenantMatches())
            .execute(),
         e -> new FailureException(
            String.format("Failed to update user name from %s to %s", LogUtil.logSafeQuote(originalName), LogUtil.logSafeQuote(newName)), e
         )
      );
   }

   private List<VerifiableCredential> findByUserIdInternal(UserId userId) {
      return this.db()
         .select(new SelectFieldOrAsterisk[0])
         .from(VerifiableCredentialTable.VERIFIABLE_CREDENTIAL)
         .where(userIdEquals(userId))
         .and(this.tenantMatches())
         .fetch(this::toVc);
   }

   private VerifiableCredential toVc(Record record) {
      return new VerifiableCredential(
         VerifiableCredentialId.verifiableCredentialId((String)record.get(VerifiableCredentialTable.VERIFIABLE_CREDENTIAL.CREDENTIAL_ID)),
         UserId.userId((String)record.get(VerifiableCredentialTable.VERIFIABLE_CREDENTIAL.USER_ID))
      );
   }

   private static Condition userIdEquals(UserId userId) {
      return VerifiableCredentialTable.VERIFIABLE_CREDENTIAL.USER_ID.eq(userId.getValue());
   }

   private Condition tenantMatches() {
      return VerifiableCredentialTable.VERIFIABLE_CREDENTIAL.TENANT_ID.eq(this.tenantId());
   }

   private void save(DSLContext db, VerifiableCredential vc) {
      db.insertInto(VerifiableCredentialTable.VERIFIABLE_CREDENTIAL)
         .set(VerifiableCredentialTable.VERIFIABLE_CREDENTIAL.CREDENTIAL_ID, vc.credentialId().value())
         .set(VerifiableCredentialTable.VERIFIABLE_CREDENTIAL.USER_ID, vc.userId().getValue())
         .set(VerifiableCredentialTable.VERIFIABLE_CREDENTIAL.TENANT_ID, this.tenantId())
         .execute();
   }

   private String tenantId() {
      return ((JooqConfig)this.jooqConfig.get()).tenantId().tenantId();
   }

   private DSLContext db() {
      return JooqProvider.jooqProvider((JooqConfig)this.jooqConfig.get()).db();
   }
}
