package org.apereo.cas.authentication;

import org.apereo.cas.services.RegisteredService;
import org.apereo.cas.authentication.handler.support.HttpBasedServiceCredentialsAuthenticationHandler;
import org.apereo.cas.authentication.principal.Service;
import org.apereo.cas.services.ServicesManager;
import org.apereo.cas.services.UnauthorizedSsoServiceException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * This is {@link RegisteredServiceAuthenticationHandlerResolver}
 * that acts on the criteria presented by a registered service to
 * detect which handler(s) should be resolved for authentication.
 *
 * @author Misagh Moayyed
 * @since 5.0.0
 */
public class RegisteredServiceAuthenticationHandlerResolver implements AuthenticationHandlerResolver {
    private static final Logger LOGGER = LoggerFactory.getLogger(RegisteredServiceAuthenticationHandlerResolver.class);
    /**
     * The Services manager.
     */
    protected final ServicesManager servicesManager;

    /**
     * Instantiates a new Registered service authentication handler resolver.
     *
     * @param servicesManager the services manager
     */
    public RegisteredServiceAuthenticationHandlerResolver(final ServicesManager servicesManager) {
        this.servicesManager = servicesManager;
    }

    @Override
    public Set<AuthenticationHandler> resolve(final Set<AuthenticationHandler> candidateHandlers,
                                              final AuthenticationTransaction transaction) {
        final Service service = transaction.getService();
        if (service != null && this.servicesManager != null) {
            final RegisteredService registeredService = this.servicesManager.findServiceBy(service);
            if (registeredService == null || !registeredService.getAccessStrategy().isServiceAccessAllowed()) {
                LOGGER.warn("Service [{}] is not allowed to use SSO.", registeredService);
                throw new UnauthorizedSsoServiceException();
            }
            if (!registeredService.getRequiredHandlers().isEmpty()) {
                LOGGER.debug("Authentication transaction requires [{}] for service [{}]", registeredService.getRequiredHandlers(), service);
                final Set<AuthenticationHandler> handlerSet = new LinkedHashSet<>(candidateHandlers);
                LOGGER.info("Candidate authentication handlers examined this transaction are [{}]", handlerSet);

                final Iterator<AuthenticationHandler> it = handlerSet.iterator();
                while (it.hasNext()) {
                    final AuthenticationHandler handler = it.next();
                    if (!(handler instanceof HttpBasedServiceCredentialsAuthenticationHandler)
                            && !registeredService.getRequiredHandlers().contains(handler.getName())) {
                        LOGGER.debug("Authentication handler [{}] is not required for this transaction and is removed", handler.getName());
                        it.remove();
                    }
                }
                LOGGER.debug("Authentication handlers for this transaction are [{}]", handlerSet);
                return handlerSet;
            }
            LOGGER.debug("No specific authentication handlers are required for this transaction");
        }

        final String handlers = candidateHandlers.stream().map(AuthenticationHandler::getName).collect(Collectors.joining());
        LOGGER.debug("Authentication handlers used for this transaction are [{}]", handlers);
        return candidateHandlers;
    }
}
