package com.airlock.iam.oauth2.domain.service.acr;

import com.airlock.iam.base.api.domain.model.plugin.LogicForConfig;
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.oauth2.api.domain.model.OAuth2ClientId;
import com.airlock.iam.oauth2.application.configuration.OpenIdConnectAcrMappingClaimConfig;
import com.airlock.iam.oauth2.domain.model.acr.OpenIdConnectAcrValue;
import jakarta.inject.Inject;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@LogicForConfig(OpenIdConnectAcrMappingClaimConfig.class)
public final class OpenIdConnectFlowConditionToIdTokenAcrMappingSelection implements OpenIdConnectIdTokenAcrSelection {
   private static final ELogger LOG = ELoggerFactory.getLogger(OpenIdConnectFlowConditionToIdTokenAcrMappingSelection.class);
   private final List<OpenIdConnectFlowConditionAcr> flowConditionAcrs;
   private final OpenIdConnectAcrMappingClaimConfig config;

   @Inject
   public OpenIdConnectFlowConditionToIdTokenAcrMappingSelection(
      List<OpenIdConnectFlowConditionAcr> flowConditionAcrs, OpenIdConnectAcrMappingClaimConfig config
   ) {
      this.flowConditionAcrs = flowConditionAcrs;
      this.config = config;
   }

   @Override
   public Optional<OpenIdConnectAcrValue> select(List<OpenIdConnectAcrValue> requestedAcrValues, OAuth2ClientId clientId) {
      return this.firstConditionFulfillingRequestedAcr(requestedAcrValues)
         .or(this::firstConditionFulfillingConfiguredAcr)
         .or(this::defaultAcrValue)
         .stream()
         .peek(openIdConnectAcrValue -> LOG.debug(String.format("Add ACR claim value %s into ID Token", LogUtil.quote(openIdConnectAcrValue))))
         .findFirst();
   }

   private Optional<OpenIdConnectAcrValue> firstConditionFulfillingRequestedAcr(List<OpenIdConnectAcrValue> requestedAcrValues) {
      List<OpenIdConnectAcrValue> conditionFulfilledAcrs = this.conditionFulfillingAcrs();
      Optional<OpenIdConnectAcrValue> acrValue = requestedAcrValues.stream().filter(conditionFulfilledAcrs::contains).findFirst();
      if (acrValue.isPresent()) {
         LOG.debug("Requested ACR matches flow condition");
      }

      return acrValue;
   }

   private Optional<OpenIdConnectAcrValue> firstConditionFulfillingConfiguredAcr() {
      Optional<OpenIdConnectAcrValue> acrValue = this.conditionFulfillingAcrs().stream().findFirst();
      if (acrValue.isPresent()) {
         LOG.debug("No requested ACR satisfies flow condition, first matching flow condition is used");
      }

      return acrValue;
   }

   private List<OpenIdConnectAcrValue> conditionFulfillingAcrs() {
      return this.flowConditionAcrs
         .stream()
         .map(OpenIdConnectFlowConditionAcr::acrOnConditionFulfilled)
         .flatMap(acr -> acr.isPresent() ? Stream.of(acr.get()) : Stream.empty())
         .collect(Collectors.toList());
   }

   private Optional<OpenIdConnectAcrValue> defaultAcrValue() {
      if (this.config.getDefaultAcr() != null && !this.config.getDefaultAcr().isEmpty()) {
         Optional<OpenIdConnectAcrValue> acrValue = Optional.of(OpenIdConnectAcrValue.of(this.config.getDefaultAcr()));
         LOG.debug("No requested ACR satisfies flow condition and no flow condition has matched, use configured default ACR value");
         return acrValue;
      } else {
         return Optional.empty();
      }
   }
}
