package org.apereo.cas.web.flow.resolver.impl.mfa;

import org.apache.commons.lang3.StringUtils;
import org.apereo.cas.CentralAuthenticationService;
import org.apereo.cas.authentication.Authentication;
import org.apereo.cas.authentication.AuthenticationException;
import org.apereo.cas.authentication.AuthenticationServiceSelectionPlan;
import org.apereo.cas.authentication.AuthenticationSystemSupport;
import org.apereo.cas.authentication.principal.Service;
import org.apereo.cas.configuration.CasConfigurationProperties;
import org.apereo.cas.services.MultifactorAuthenticationProvider;
import org.apereo.cas.services.MultifactorAuthenticationProviderSelector;
import org.apereo.cas.services.RegisteredService;
import org.apereo.cas.services.ServicesManager;
import org.apereo.cas.ticket.registry.TicketRegistrySupport;
import org.apereo.cas.util.ResourceUtils;
import org.apereo.cas.util.ScriptingUtils;
import org.apereo.cas.web.flow.authentication.BaseMultifactorAuthenticationProviderEventResolver;
import org.apereo.cas.web.support.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.Resource;
import org.springframework.web.util.CookieGenerator;
import org.springframework.webflow.execution.Event;
import org.springframework.webflow.execution.RequestContext;

import java.util.Collections;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

/**
 * This is {@link GroovyScriptMultifactorAuthenticationPolicyEventResolver}
 * that conditionally evaluates a groovy script to resolve the mfa provider id
 * and event.
 *
 * @author Misagh Moayyed
 * @since 5.1.0
 */
public class GroovyScriptMultifactorAuthenticationPolicyEventResolver extends BaseMultifactorAuthenticationProviderEventResolver {
    private static final Logger LOGGER = LoggerFactory.getLogger(GroovyScriptMultifactorAuthenticationPolicyEventResolver.class);

    private final Resource groovyScript;

    public GroovyScriptMultifactorAuthenticationPolicyEventResolver(final AuthenticationSystemSupport authenticationSystemSupport,
                                                                    final CentralAuthenticationService centralAuthenticationService,
                                                                    final ServicesManager servicesManager,
                                                                    final TicketRegistrySupport ticketRegistrySupport,
                                                                    final CookieGenerator warnCookieGenerator,
                                                                    final AuthenticationServiceSelectionPlan authenticationSelectionStrategies,
                                                                    final MultifactorAuthenticationProviderSelector selector,
                                                                    final CasConfigurationProperties casProperties) {
        super(authenticationSystemSupport, centralAuthenticationService, servicesManager,
                ticketRegistrySupport, warnCookieGenerator,
                authenticationSelectionStrategies, selector);
        groovyScript = casProperties.getAuthn().getMfa().getGroovyScript();
    }

    @Override
    public Set<Event> resolveInternal(final RequestContext context) {
        final Service service = resolveServiceFromAuthenticationRequest(context);
        final RegisteredService registeredService = resolveRegisteredServiceInRequestContext(context);
        final Authentication authentication = WebUtils.getAuthentication(context);

        if (groovyScript == null || !ResourceUtils.doesResourceExist(groovyScript)) {
            LOGGER.debug("No groovy script is found or configured for multifactor authentication");
            return null;
        }
        if (authentication == null) {
            LOGGER.debug("No authentication is available to determine event for principal");
            return null;
        }
        if (registeredService == null || service == null) {
            LOGGER.debug("No registeredService is available to determine event for principal [{}]", authentication.getPrincipal());
            return null;
        }

        final Map<String, MultifactorAuthenticationProvider> providerMap =
                WebUtils.getAvailableMultifactorAuthenticationProviders(this.applicationContext);
        if (providerMap == null || providerMap.isEmpty()) {
            LOGGER.error("No multifactor authentication providers are available in the application context");
            throw new AuthenticationException();
        }

        try {
            final Object[] args = {service, registeredService, authentication, LOGGER};
            final String provider = ScriptingUtils.executeGroovyScript(groovyScript, args, String.class);
            if (StringUtils.isBlank(provider)) {
                return null;
            }

            final Optional<MultifactorAuthenticationProvider> providerFound = resolveProvider(providerMap, provider);
            if (providerFound.isPresent()) {
                if (providerFound.get().isAvailable(registeredService)) {
                    final Event event = validateEventIdForMatchingTransitionInContext(providerFound.get().getId(), context,
                            buildEventAttributeMap(authentication.getPrincipal(), registeredService, providerFound.get()));
                    return Collections.singleton(event);
                }
                LOGGER.warn("Located multifactor provider [{}], yet the provider cannot be reached or verified", providerFound.get());
                return null;
            }
            LOGGER.warn("No multifactor provider could be found for [{}]", provider);
            throw new AuthenticationException();

        } catch (final Exception e) {
            LOGGER.error(e.getMessage(), e);
        }
        return null;
    }
}
