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

import com.airlock.iam.base.api.domain.model.event.DomainEvent;
import com.airlock.iam.base.api.domain.model.plugin.LogicForConfig;
import com.airlock.iam.common.domain.model.event.LoginappSubscribedEvent;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import com.airlock.iam.oauth2.api.domain.model.OAuth2Scope;
import com.airlock.iam.oauth2.api.domain.model.event.OAuth2UserConsentEvent;
import com.airlock.iam.oauth2.application.configuration.event.OAuth2UserConsentSubscribedEventConfig;
import jakarta.annotation.Nonnull;
import jakarta.inject.Inject;
import jakarta.inject.Provider;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;
import java.util.regex.Pattern;

@LogicForConfig(OAuth2UserConsentSubscribedEventConfig.class)
public class OAuth2UserConsentSubscribedEvent implements LoginappSubscribedEvent {
   private static final ELogger LOG = ELoggerFactory.getLogger(OAuth2UserConsentSubscribedEvent.class);
   private final Provider<OAuth2UserConsentFilterSettings> filterSettings;

   @Inject
   public OAuth2UserConsentSubscribedEvent(Provider<OAuth2UserConsentFilterSettings> filterSettings) {
      this.filterSettings = filterSettings;
   }

   @Override
   public boolean canHandle(DomainEvent event) {
      return event instanceof OAuth2UserConsentEvent consentEvent
         && this.filterAuthorizationServerId(consentEvent.authorizationServerId().id())
         && this.filterClientId(consentEvent.clientId().id())
         && this.filterAcceptedScopes(consentEvent.acceptedScopes())
         && this.filterDeniedScopes(consentEvent.deniedScopes())
         && this.filterAlwaysGrantedScopes(consentEvent.alwaysGrantedScopes());
   }

   private boolean filterAuthorizationServerId(String authorizationServerId) {
      Optional<Pattern> pattern = ((OAuth2UserConsentFilterSettings)this.filterSettings.get()).authorizationServerIdPattern();
      Boolean patternIsEmptyOrMatchesAsId = pattern.<Boolean>map(p -> p.asMatchPredicate().test(authorizationServerId)).orElse(true);
      if (patternIsEmptyOrMatchesAsId) {
         return true;
      }

      LOG.info(
         "Configured authorization server id pattern (%s) does not match the OAuth 2.0 Authorization Server id: (%s). Event will be skipped."
            .formatted(pattern.get(), authorizationServerId)
      );
      return false;
   }

   private boolean filterClientId(String clientId) {
      Optional<Pattern> pattern = ((OAuth2UserConsentFilterSettings)this.filterSettings.get()).clientIdPattern();
      Boolean patternIsEmptyOrMatchesClientId = pattern.<Boolean>map(p -> p.asMatchPredicate().test(clientId)).orElse(true);
      if (patternIsEmptyOrMatchesClientId) {
         return true;
      }

      LOG.info("Configured client id pattern (%s) does not match the OAuth 2.0 Client id: (%s). Event will be skipped.".formatted(pattern.get(), clientId));
      return false;
   }

   private boolean filterAcceptedScopes(Set<OAuth2Scope> acceptedScopes) {
      if (acceptedScopes.isEmpty()) {
         boolean shouldHandleEmptyAcceptedScopes = ((OAuth2UserConsentFilterSettings)this.filterSettings.get()).handleEmptyAcceptedScopes();
         if (shouldHandleEmptyAcceptedScopes) {
            return true;
         }

         LOG.info("There are no accepted scopes and the event is configured not to handle empty accepted scopes. Event will be skipped.");
         return false;
      } else {
         List<Pattern> patterns = ((OAuth2UserConsentFilterSettings)this.filterSettings.get()).acceptedScopesPatterns();
         boolean allPatternsMatchAtLeastOneScope = patterns.stream().allMatch(patternMatchesAnyScope(acceptedScopes));
         if (allPatternsMatchAtLeastOneScope) {
            return true;
         }

         LOG.info(
            "At least one of the configured accepted scope filters (%s) does not match any of the accepted scopes: (%s). Event will be skipped."
               .formatted(patterns.stream().map(Pattern::pattern).toList(), acceptedScopes.stream().map(OAuth2Scope::name).toList())
         );
         return false;
      }
   }

   private boolean filterDeniedScopes(Set<OAuth2Scope> deniedScopes) {
      if (deniedScopes.isEmpty()) {
         boolean shouldHandleEmptyDeniedScopes = ((OAuth2UserConsentFilterSettings)this.filterSettings.get()).handleEmptyDeniedScopes();
         if (shouldHandleEmptyDeniedScopes) {
            return true;
         }

         LOG.info("There are no denied scopes and the event is configured not to handle empty denied scopes. Event will be skipped.");
         return false;
      } else {
         List<Pattern> patterns = ((OAuth2UserConsentFilterSettings)this.filterSettings.get()).deniedScopesPatterns();
         boolean allPatternsMatchAtLeastOneScope = patterns.stream().allMatch(patternMatchesAnyScope(deniedScopes));
         if (allPatternsMatchAtLeastOneScope) {
            return true;
         }

         LOG.info(
            "At least one of the configured denied scope filters (%s) does not match any of the denied scopes: (%s). Event will be skipped."
               .formatted(patterns.stream().map(Pattern::pattern).toList(), deniedScopes.stream().map(OAuth2Scope::name).toList())
         );
         return false;
      }
   }

   private boolean filterAlwaysGrantedScopes(Set<OAuth2Scope> alwaysGrantedScopes) {
      if (alwaysGrantedScopes.isEmpty()) {
         boolean shouldHandleEmptyAlwaysGranted = ((OAuth2UserConsentFilterSettings)this.filterSettings.get()).handleEmptyAlwaysGrantedScopes();
         if (shouldHandleEmptyAlwaysGranted) {
            return true;
         }

         LOG.info("There are no always granted scopes and the event is configured not to handle empty always granted scopes. Event will be skipped.");
         return false;
      } else {
         List<Pattern> patterns = ((OAuth2UserConsentFilterSettings)this.filterSettings.get()).alwaysGrantedScopesPatterns();
         boolean allPatternsMatchAtLeastOneScope = patterns.stream().allMatch(patternMatchesAnyScope(alwaysGrantedScopes));
         if (allPatternsMatchAtLeastOneScope) {
            return true;
         }

         LOG.info(
            "At least one of the configured always granted scope filters (%s) does not match any of the always granted scopes: (%s). Event will be skipped."
               .formatted(patterns.stream().map(Pattern::pattern).toList(), alwaysGrantedScopes.stream().map(OAuth2Scope::name).toList())
         );
         return false;
      }
   }

   @Nonnull
   private static Predicate<Pattern> patternMatchesAnyScope(Set<OAuth2Scope> scopes) {
      return p -> scopes.stream().map(OAuth2Scope::name).anyMatch(p.asMatchPredicate());
   }

   @Override
   public String subscribedEventClass() {
      return OAuth2UserConsentEvent.class.getCanonicalName();
   }
}
