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

import com.airlock.iam.apipolicyservice.domain.model.ApiPolicyServiceTechClientView;
import com.airlock.iam.apipolicyservice.domain.model.ApiPolicyServiceTechClientViewRepository;
import com.airlock.iam.base.api.domain.model.exception.FailureException;
import com.airlock.iam.base.misc.exception.Exceptions;
import com.airlock.iam.base.misc.log.LogUtil;
import com.airlock.iam.core.domain.service.jwt.JwtHmacAndAesGcmEncryption;
import com.airlock.iam.core.infrastructure.db.JooqProvider;
import com.airlock.iam.database.infrastructure.JooqConfig;
import com.airlock.iam.database.infrastructure.TenantId;
import com.airlock.iam.database.infrastructure.generated.schema.tables.ApiKeyTable;
import com.airlock.iam.database.infrastructure.generated.schema.tables.PlanUsageTable;
import com.airlock.iam.database.infrastructure.generated.schema.tables.PrincipalTable;
import com.airlock.iam.database.infrastructure.generated.schema.tables.SubscriptionTable;
import com.airlock.iam.techclient.domain.model.apikey.ApiKeySecret;
import com.airlock.iam.techclient.infrastructure.repository.principal.SharedPrincipalQueries;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import java.util.Optional;
import java.util.function.Supplier;
import java.util.stream.Stream;
import org.jooq.Condition;
import org.jooq.DSLContext;
import org.jooq.Record;
import org.jooq.SelectConditionStep;
import org.jooq.SelectFieldOrAsterisk;
import org.jooq.TableField;

public class ApiPolicyServiceTechClientViewRepositoryImpl implements ApiPolicyServiceTechClientViewRepository {
   private final JwtHmacAndAesGcmEncryption macAndEncryption;
   private final Supplier<DSLContext> provider;
   private final TenantId tenantId;

   @Inject
   public ApiPolicyServiceTechClientViewRepositoryImpl(
      JooqConfig jooqConfig, @Named("API_KEY_SECRET_VALUE_ENCRYPTION") JwtHmacAndAesGcmEncryption macAndEncrypter
   ) {
      this.provider = () -> JooqProvider.jooqProvider(jooqConfig).db();
      this.tenantId = jooqConfig.tenantId();
      this.macAndEncryption = macAndEncrypter;
   }

   @Override
   public Optional<ApiPolicyServiceTechClientView> findBy(ApiKeySecret secret) {
      return (Optional<ApiPolicyServiceTechClientView>)Exceptions.wrapException(
         () -> this.findTechClientWithMatchingSecret(secret, this.db()),
         e -> new FailureException("Error resolving technical client for API key with fingerprint " + LogUtil.quote(secret.getFingerprint()), e)
      );
   }

   private Optional<ApiPolicyServiceTechClientView> findTechClientWithMatchingSecret(ApiKeySecret secret, DSLContext db) {
      Optional<ApiPolicyServiceTechClientView> techClient = this.findApiKeyTechClientsWhere(db, this.matchingApiKeySecretFingerprint(secret))
         .collect(ReduceApiPolicyServiceTechClients.reduceToApiPolicyServiceTechClient(this.macAndEncryption));
      return techClient.isPresent() && !techClient.get().getApiKey().getSecret().matches(secret) ? Optional.empty() : techClient;
   }

   private Stream<Record> findApiKeyTechClientsWhere(DSLContext db, Condition condition) {
      return this.findAllClientsWithTenantClause(db).and(condition).fetchStream();
   }

   private SelectConditionStep<Record> findAllClientsWithTenantClause(DSLContext db) {
      return db.select(
            new SelectFieldOrAsterisk[]{
               PrincipalTable.PRINCIPAL.asterisk(),
               ApiKeyTable.API_KEY.LOCKED,
               ApiKeyTable.API_KEY.SECRET,
               ApiKeyTable.API_KEY.FINGERPRINT,
               ApiKeyTable.API_KEY.VALID_FROM,
               ApiKeyTable.API_KEY.VALID_TO,
               SubscriptionTable.SUBSCRIPTION.ID,
               SubscriptionTable.SUBSCRIPTION.DISPLAY_NAME,
               PlanUsageTable.PLAN_USAGE.NAME,
               PlanUsageTable.PLAN_USAGE.RATE_LIMIT
            }
         )
         .from(PrincipalTable.PRINCIPAL)
         .leftJoin(ApiKeyTable.API_KEY)
         .on(ApiKeyTable.API_KEY.PRINCIPAL_PK.eq(PrincipalTable.PRINCIPAL.PK))
         .and(SharedPrincipalQueries.apiKeyTenantMatches(this.tenantId))
         .leftJoin(SubscriptionTable.SUBSCRIPTION)
         .on(SubscriptionTable.SUBSCRIPTION.PK.eq(PrincipalTable.PRINCIPAL.SUBSCRIPTION_PK))
         .and(SharedPrincipalQueries.subscriptionTenantMatches(this.tenantId))
         .leftJoin(PlanUsageTable.PLAN_USAGE)
         .onKey(new TableField[]{PlanUsageTable.PLAN_USAGE.SUBSCRIPTION_PK})
         .where(SharedPrincipalQueries.principalTenantMatches(this.tenantId));
   }

   private Condition matchingApiKeySecretFingerprint(ApiKeySecret apiKeySecret) {
      return ApiKeyTable.API_KEY.FINGERPRINT.eq(apiKeySecret.getFingerprint().asString());
   }

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