package com.airlock.iam.oauth2.domain.model.settings;

import com.airlock.iam.base.misc.log.LogUtil;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import com.airlock.iam.flow.api.domain.model.flow.ApplicationId;
import com.airlock.iam.oauth2.api.domain.model.OAuth2ClientId;
import com.airlock.iam.oauth2.domain.model.acr.AcrToApplicationId;
import com.airlock.iam.oauth2.domain.model.acr.OpenIdConnectAcrValue;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.List;
import java.util.Optional;
import lombok.NonNull;

public final class OAuth2ApplicationFlowSelectionSettings {
   private static final ELogger LOG = ELoggerFactory.getLogger(OAuth2ApplicationFlowSelectionSettings.class);
   @NonNull
   private final List<AcrToApplicationId> acrToAppIdMappings;
   @NonNull
   private final Optional<ApplicationId> targetFlowId;
   @NonNull
   private final AcrToApplicationIdMappingsProvider acrToAppIdMappingsOverrideProvider;
   @NonNull
   private final ApplicationIdProvider targetFlowIdOverrideProvider;

   public Optional<AcrToApplicationId> mapRequestedAcrToAppId(List<OpenIdConnectAcrValue> requested, OAuth2ClientId clientId) {
      List<AcrToApplicationId> acrToApplicationIdMappingsOverride = this.acrToAppIdMappingsOverrideProvider.provideAcrToApplicationIdMappings(clientId);
      if (!acrToApplicationIdMappingsOverride.isEmpty()) {
         Optional<AcrToApplicationId> acrToAppId = mapRequestedAcrToAppId(requested, acrToApplicationIdMappingsOverride);
         if (acrToAppId.isPresent()) {
            LOG.info(
               String.format(
                  "Selected application ID %s from static client override based on requested ACR %s",
                  LogUtil.quote(acrToAppId.get().getAuthenticationFlowId()),
                  LogUtil.quote(acrToAppId.get().getAcr().name())
               )
            );
            return acrToAppId;
         }
      }

      Optional<AcrToApplicationId> acrToAppId = mapRequestedAcrToAppId(requested, this.acrToAppIdMappings);
      if (acrToAppId.isPresent()) {
         LOG.info(
            String.format(
               "Selected application ID %s from auth code flow based on requested ACR %s",
               LogUtil.quote(acrToAppId.get().getAuthenticationFlowId()),
               LogUtil.quote(acrToAppId.get().getAcr().name())
            )
         );
      }

      return acrToAppId;
   }

   public Optional<ApplicationId> getTargetFlowId(OAuth2ClientId clientId) {
      return this.targetFlowIdOverrideProvider.provideDefaultApplicationId(clientId).or(() -> this.targetFlowId);
   }

   private static Optional<AcrToApplicationId> mapRequestedAcrToAppId(List<OpenIdConnectAcrValue> requested, List<AcrToApplicationId> acrToAppIdMappings) {
      return requested.stream().map(acr -> findFirstMatchingAcrMapping(acrToAppIdMappings, acr)).filter(Optional::isPresent).map(Optional::get).findFirst();
   }

   private static Optional<AcrToApplicationId> findFirstMatchingAcrMapping(List<AcrToApplicationId> acrToAppIdMappings, OpenIdConnectAcrValue acr) {
      return acrToAppIdMappings.stream().filter(mapping -> mapping.getAcr().equals(acr)).findFirst();
   }

   @SuppressFBWarnings(justification = "generated code")
   OAuth2ApplicationFlowSelectionSettings(
      @NonNull List<AcrToApplicationId> acrToAppIdMappings,
      @NonNull Optional<ApplicationId> targetFlowId,
      @NonNull AcrToApplicationIdMappingsProvider acrToAppIdMappingsOverrideProvider,
      @NonNull ApplicationIdProvider targetFlowIdOverrideProvider
   ) {
      if (acrToAppIdMappings == null) {
         throw new NullPointerException("acrToAppIdMappings is marked non-null but is null");
      }

      if (targetFlowId == null) {
         throw new NullPointerException("targetFlowId is marked non-null but is null");
      }

      if (acrToAppIdMappingsOverrideProvider == null) {
         throw new NullPointerException("acrToAppIdMappingsOverrideProvider is marked non-null but is null");
      }

      if (targetFlowIdOverrideProvider == null) {
         throw new NullPointerException("targetFlowIdOverrideProvider is marked non-null but is null");
      }

      this.acrToAppIdMappings = acrToAppIdMappings;
      this.targetFlowId = targetFlowId;
      this.acrToAppIdMappingsOverrideProvider = acrToAppIdMappingsOverrideProvider;
      this.targetFlowIdOverrideProvider = targetFlowIdOverrideProvider;
   }

   @SuppressFBWarnings(justification = "generated code")
   public static OAuth2ApplicationFlowSelectionSettings.OAuth2ApplicationFlowSelectionSettingsBuilder builder() {
      return new OAuth2ApplicationFlowSelectionSettings.OAuth2ApplicationFlowSelectionSettingsBuilder();
   }

   @SuppressFBWarnings(justification = "generated code")
   public static class OAuth2ApplicationFlowSelectionSettingsBuilder {
      @SuppressFBWarnings(justification = "generated code")
      private List<AcrToApplicationId> acrToAppIdMappings;
      @SuppressFBWarnings(justification = "generated code")
      private Optional<ApplicationId> targetFlowId;
      @SuppressFBWarnings(justification = "generated code")
      private AcrToApplicationIdMappingsProvider acrToAppIdMappingsOverrideProvider;
      @SuppressFBWarnings(justification = "generated code")
      private ApplicationIdProvider targetFlowIdOverrideProvider;

      @SuppressFBWarnings(justification = "generated code")
      OAuth2ApplicationFlowSelectionSettingsBuilder() {
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2ApplicationFlowSelectionSettings.OAuth2ApplicationFlowSelectionSettingsBuilder acrToAppIdMappings(
         @NonNull List<AcrToApplicationId> acrToAppIdMappings
      ) {
         if (acrToAppIdMappings == null) {
            throw new NullPointerException("acrToAppIdMappings is marked non-null but is null");
         }

         this.acrToAppIdMappings = acrToAppIdMappings;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2ApplicationFlowSelectionSettings.OAuth2ApplicationFlowSelectionSettingsBuilder targetFlowId(@NonNull Optional<ApplicationId> targetFlowId) {
         if (targetFlowId == null) {
            throw new NullPointerException("targetFlowId is marked non-null but is null");
         }

         this.targetFlowId = targetFlowId;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2ApplicationFlowSelectionSettings.OAuth2ApplicationFlowSelectionSettingsBuilder acrToAppIdMappingsOverrideProvider(
         @NonNull AcrToApplicationIdMappingsProvider acrToAppIdMappingsOverrideProvider
      ) {
         if (acrToAppIdMappingsOverrideProvider == null) {
            throw new NullPointerException("acrToAppIdMappingsOverrideProvider is marked non-null but is null");
         }

         this.acrToAppIdMappingsOverrideProvider = acrToAppIdMappingsOverrideProvider;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2ApplicationFlowSelectionSettings.OAuth2ApplicationFlowSelectionSettingsBuilder targetFlowIdOverrideProvider(
         @NonNull ApplicationIdProvider targetFlowIdOverrideProvider
      ) {
         if (targetFlowIdOverrideProvider == null) {
            throw new NullPointerException("targetFlowIdOverrideProvider is marked non-null but is null");
         }

         this.targetFlowIdOverrideProvider = targetFlowIdOverrideProvider;
         return this;
      }

      @SuppressFBWarnings(justification = "generated code")
      public OAuth2ApplicationFlowSelectionSettings build() {
         return new OAuth2ApplicationFlowSelectionSettings(
            this.acrToAppIdMappings, this.targetFlowId, this.acrToAppIdMappingsOverrideProvider, this.targetFlowIdOverrideProvider
         );
      }

      @SuppressFBWarnings(justification = "generated code")
      @Override
      public String toString() {
         return "OAuth2ApplicationFlowSelectionSettings.OAuth2ApplicationFlowSelectionSettingsBuilder(acrToAppIdMappings="
            + this.acrToAppIdMappings
            + ", targetFlowId="
            + this.targetFlowId
            + ", acrToAppIdMappingsOverrideProvider="
            + this.acrToAppIdMappingsOverrideProvider
            + ", targetFlowIdOverrideProvider="
            + this.targetFlowIdOverrideProvider
            + ")";
      }
   }
}
