package com.airlock.iam.oauth2.infrastructure.service;

import com.airlock.iam.base.misc.log.LogUtil;
import com.airlock.iam.common.api.domain.model.claims.Claim;
import com.airlock.iam.common.api.domain.model.user.PersistentUser;
import com.airlock.iam.common.domain.model.claims.ClaimSet;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import com.airlock.iam.oauth2.api.domain.model.claims.OAuth2TokenClaimData;
import com.airlock.iam.oauth2.api.domain.model.claims.UserClaimExtractor;
import com.nimbusds.jwt.JWTClaimsSet.Builder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

public final class CommonTokenClaimService {
   private static final ELogger LOG = ELoggerFactory.getLogger(CommonTokenClaimService.class);
   private final Collection<String> iamReservedClaims;

   public CommonTokenClaimService(Collection<String> iamReservedClaims) {
      this.iamReservedClaims = iamReservedClaims;
   }

   List<Claim<?>> extractCustomClaimValues(List<UserClaimExtractor<?>> customClaimsExtractors, PersistentUser persistentUser, OAuth2TokenClaimData tokenInfo) {
      return customClaimsExtractors.stream()
         .map(extractor -> extractor.extract(persistentUser, tokenInfo))
         .flatMap(Optional::stream)
         .collect(Collectors.toList());
   }

   void combineDefaultAndClientSpecificClaims(Builder jwtClaimsSet, List<Claim<?>> defaultClaims, List<Claim<?>> clientSpecificClaims) {
      Set<String> clientClaimNames = clientSpecificClaims.stream().map(Claim::name).collect(Collectors.toSet());
      List<Claim<?>> combinedClaims = new ArrayList<>(clientSpecificClaims);
      defaultClaims.stream().filter(claim -> !clientClaimNames.contains(claim.name())).filter(this::isNotReservedClaim).forEach(combinedClaims::add);
      ClaimSet.verifyUniqueClaims(combinedClaims).forEach(claim -> jwtClaimsSet.claim(claim.name(), claim.value().toJSONSerializable()));
   }

   private boolean isNotReservedClaim(Claim<?> claim) {
      boolean reserved = this.iamReservedClaims.contains(claim.name());
      if (reserved) {
         LOG.info(String.format("Ignoring custom claim %s because it is a reserved claim.", LogUtil.quote(claim.name())));
         return false;
      } else {
         return true;
      }
   }
}
