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

import com.airlock.iam.base.api.domain.model.exception.FailureException;
import com.airlock.iam.base.infrastructure.db.DbInstantConverter;
import com.airlock.iam.base.misc.exception.Exceptions;
import com.airlock.iam.base.misc.log.LogUtil;
import com.airlock.iam.base.misc.time.TimeProvider;
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;
import com.airlock.iam.database.infrastructure.generated.schema.tables.records.Oauth2AcceptedClientAssertionsRecord;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import com.airlock.iam.oauth2.domain.model.clientauthentication.OAuth2AcceptedClientAssertion;
import com.airlock.iam.oauth2.domain.model.clientauthentication.OAuth2AcceptedClientAssertionId;
import com.airlock.iam.oauth2.domain.model.clientauthentication.OAuth2AcceptedClientAssertionRepository;
import jakarta.inject.Inject;
import jakarta.inject.Provider;
import java.util.Optional;
import org.jooq.DSLContext;

public class OAuth2AcceptedClientAssertionRepositoryImpl implements OAuth2AcceptedClientAssertionRepository {
   private static final ELogger LOG = ELoggerFactory.getLogger(OAuth2AcceptedClientAssertionRepositoryImpl.class);
   private final Provider<JooqConfig> jooqConfig;
   private final Provider<TimeProvider> timeProvider;

   @Inject
   public OAuth2AcceptedClientAssertionRepositoryImpl(Provider<JooqConfig> jooqConfig, Provider<TimeProvider> timeProvider) {
      this.jooqConfig = jooqConfig;
      this.timeProvider = timeProvider;
   }

   @Override
   public boolean contains(OAuth2AcceptedClientAssertionId id) {
      return Exceptions.wrapException(
         () -> (Boolean)JooqTransaction.jooqTransaction(this.db()).executeAndReturn(db -> this.findAndRemoveIfExpired(id, db)),
         e -> new FailureException(
            String.format("Could not execute contains query for ID %s. Cause: %s", LogUtil.logSafeQuote(id.asString()), e.getMessage()), e
         )
      );
   }

   @Override
   public void insert(OAuth2AcceptedClientAssertion assertion) {
      Exceptions.wrapException(
         () -> JooqTransaction.jooqTransaction(this.db())
            .execute(
               db -> db.insertInto(Tables.OAUTH2_ACCEPTED_CLIENT_ASSERTIONS)
                  .columns(
                     Tables.OAUTH2_ACCEPTED_CLIENT_ASSERTIONS.JTI,
                     Tables.OAUTH2_ACCEPTED_CLIENT_ASSERTIONS.EXPIRES_AT,
                     Tables.OAUTH2_ACCEPTED_CLIENT_ASSERTIONS.TENANT_ID
                  )
                  .values(assertion.getId().asString(), DbInstantConverter.toNanoSecondsSinceEpoch(assertion.getExpiresAt()), this.tenantId())
                  .execute()
            ),
         e -> new FailureException(
            String.format("Could not insert client assertion with id %s. Cause: %s", LogUtil.logSafeQuote(assertion.getId().asString()), e.getMessage()), e
         )
      );
      LOG.debug("Inserted client assertion id (jti) with ID: " + LogUtil.logSafeQuote(assertion.getId().asString()));
   }

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

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

   private boolean findAndRemoveIfExpired(OAuth2AcceptedClientAssertionId id, DSLContext db) {
      Optional<OAuth2AcceptedClientAssertion> assertion = this.findById(db, id);
      if (assertion.isPresent() && assertion.get().getExpiresAt().isBefore(((TimeProvider)this.timeProvider.get()).getCurrentInstant())) {
         this.removeAssertion(db, id);
         return false;
      } else {
         return assertion.isPresent();
      }
   }

   private void removeAssertion(DSLContext db, OAuth2AcceptedClientAssertionId id) {
      int deleted = db.deleteFrom(Tables.OAUTH2_ACCEPTED_CLIENT_ASSERTIONS).where(Tables.OAUTH2_ACCEPTED_CLIENT_ASSERTIONS.JTI.eq(id.getId())).execute();
      if (deleted > 0) {
         LOG.debug(String.format("Removed expired OAuth2 client assertion: %s", LogUtil.logSafeQuote(id.asString())));
      }
   }

   private Optional<OAuth2AcceptedClientAssertion> findById(DSLContext db, OAuth2AcceptedClientAssertionId id) {
      return db.selectFrom(Tables.OAUTH2_ACCEPTED_CLIENT_ASSERTIONS)
         .where(Tables.OAUTH2_ACCEPTED_CLIENT_ASSERTIONS.JTI.eq(id.getId()).and(Tables.OAUTH2_ACCEPTED_CLIENT_ASSERTIONS.TENANT_ID.eq(this.tenantId())))
         .fetch(this::mapAssertion)
         .stream()
         .findFirst();
   }

   private OAuth2AcceptedClientAssertion mapAssertion(Oauth2AcceptedClientAssertionsRecord rec) {
      return new OAuth2AcceptedClientAssertion(
         OAuth2AcceptedClientAssertionId.clientAssertionId(rec.getJti()), DbInstantConverter.toInstant(rec.getExpiresAt())
      );
   }
}
