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

import com.airlock.iam.base.api.domain.model.plugin.LogicForConfig;
import com.airlock.iam.common.api.domain.model.role.Role;
import com.airlock.iam.common.domain.model.user.PrincipalInfo;
import com.airlock.iam.flow.shared.domain.model.valueprovider.RoleProvider;
import com.airlock.iam.oauth2.api.domain.model.OAuth2Scope;
import com.airlock.iam.oauth2.api.domain.model.provider.scope.OAuth2ScopeCondition;
import com.airlock.iam.oauth2.application.configuration.scope.RoleBasedOAuth2ScopeConditionConfig;
import jakarta.inject.Inject;
import jakarta.inject.Provider;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

@LogicForConfig(RoleBasedOAuth2ScopeConditionConfig.class)
public final class RoleBasedOAuth2ScopeCondition implements OAuth2ScopeCondition {
   private final Pattern scopeMatcher;
   private final List<RoleProvider> roleProviders;
   private final Provider<PrincipalInfo> userPrincipal;

   @Inject
   public RoleBasedOAuth2ScopeCondition(Pattern scopeMatcher, Provider<PrincipalInfo> userPrincipal, List<RoleProvider> roleProviders) {
      this.scopeMatcher = scopeMatcher;
      this.roleProviders = roleProviders;
      this.userPrincipal = userPrincipal;
   }

   @Override
   public boolean isAllowed(OAuth2Scope scope) {
      return this.matches(scope) && this.hasMatchingRole(scope);
   }

   private boolean hasMatchingRole(OAuth2Scope scope) {
      return this.roles().stream().map(Role::getRoleName).anyMatch(roleName -> roleName.equals(scope.name()));
   }

   private Set<Role> roles() {
      return this.roleProviders
         .stream()
         .flatMap(roleProvider -> roleProvider.provideRoles(((PrincipalInfo)this.userPrincipal.get()).getKnownIdentity().orElseThrow()).stream())
         .collect(Collectors.toSet());
   }

   private boolean matches(OAuth2Scope scope) {
      return this.scopeMatcher.matcher(scope.name()).matches();
   }
}
