package com.airlock.iam.oauth2.domain.service.client.resource;

import com.airlock.iam.common.api.domain.model.role.Role;
import com.airlock.iam.common.domain.model.role.RolesUtil;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableSet;
import java.util.Collection;
import java.util.Map;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.stream.Collectors;

public final class OAuth2RemoteUserRoleResourceLogic implements OAuth2RemoteResource {
   private static final ELogger LOG = ELoggerFactory.getLogger(OAuth2RemoteUserRoleResourceLogic.class);
   private final OAuth2ResourceSelector resourceSelector;
   private final Consumer<String> infoLogger;

   public OAuth2RemoteUserRoleResourceLogic(OAuth2ResourceSelector resourceSelector) {
      this.resourceSelector = resourceSelector;
      this.infoLogger = LOG::info;
   }

   @VisibleForTesting
   OAuth2RemoteUserRoleResourceLogic(OAuth2ResourceSelector resourceSelector, Consumer<String> infoLogger) {
      this.resourceSelector = resourceSelector;
      this.infoLogger = infoLogger;
   }

   @Override
   public OAuth2SingleResourceData collectResource(Map<String, Object> parsedResourceResponse) {
      Optional<Object> data = this.resourceSelector.retrieveResource(parsedResourceResponse, Object.class);
      return data.map(this::map).orElseGet(this::noResourceFound);
   }

   private OAuth2SingleResourceData noResourceFound() {
      this.infoLog("Unable to extract the roles from the parsed resource response.");
      return DefaultOAuth2SingleResourceData.emptyResourceData();
   }

   private OAuth2SingleResourceData map(Object data) {
      if (isStringCollection(data)) {
         return resourceDataForRoleCollection(toCollection(data));
      }

      if (data instanceof String) {
         return resourceDataForSingleRole((String)data);
      }

      this.infoLog("Retrieved resource '" + data + "' was of unexpected type. Valid types are either 'String' or 'Collection<String>'.");
      return DefaultOAuth2SingleResourceData.emptyResourceData();
   }

   private void infoLog(String message) {
      this.infoLogger.accept(message);
   }

   private static boolean isStringCollection(Object data) {
      return data instanceof Collection<?> dataCollection ? dataCollection.stream().allMatch(String.class::isInstance) : false;
   }

   private static Collection<String> toCollection(Object data) {
      return ((Collection)data).stream().map(d -> (String)d).collect(Collectors.toList());
   }

   private static OAuth2SingleResourceData resourceDataForSingleRole(String role) {
      return DefaultOAuth2SingleResourceData.resourceDataWithRoles(ImmutableSet.of(Role.parseRole(role)));
   }

   private static OAuth2SingleResourceData resourceDataForRoleCollection(Collection<String> roles) {
      return DefaultOAuth2SingleResourceData.resourceDataWithRoles(ImmutableSet.copyOf(RolesUtil.rolesFrom(roles)));
   }
}
