package org.apereo.cas.config;

import com.google.common.base.Throwables;
import org.apereo.cas.CipherExecutor;
import org.apereo.cas.authentication.DefaultMultifactorTriggerSelectionStrategy;
import org.apereo.cas.authentication.MultifactorTriggerSelectionStrategy;
import org.apereo.cas.authentication.ProtocolAttributeEncoder;
import org.apereo.cas.authentication.principal.DefaultWebApplicationResponseBuilderLocator;
import org.apereo.cas.authentication.principal.PersistentIdGenerator;
import org.apereo.cas.authentication.principal.ResponseBuilder;
import org.apereo.cas.authentication.principal.ResponseBuilderLocator;
import org.apereo.cas.authentication.principal.ShibbolethCompatiblePersistentIdGenerator;
import org.apereo.cas.authentication.principal.WebApplicationService;
import org.apereo.cas.authentication.principal.WebApplicationServiceResponseBuilder;
import org.apereo.cas.authentication.support.DefaultCasProtocolAttributeEncoder;
import org.apereo.cas.authentication.support.NoOpProtocolAttributeEncoder;
import org.apereo.cas.configuration.CasConfigurationProperties;
import org.apereo.cas.services.AbstractResourceBasedServiceRegistryDao;
import org.apereo.cas.services.DefaultServicesManager;
import org.apereo.cas.services.InMemoryServiceRegistry;
import org.apereo.cas.services.RegisteredServiceCipherExecutor;
import org.apereo.cas.services.ServiceRegistryDao;
import org.apereo.cas.services.ServiceRegistryInitializer;
import org.apereo.cas.services.ServicesManager;
import org.apereo.cas.util.services.DefaultRegisteredServiceCipherExecutor;
import org.apereo.cas.util.services.RegisteredServiceJsonSerializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;

import java.util.List;

/**
 * This is {@link CasCoreServicesConfiguration}.
 *
 * @author Misagh Moayyed
 * @since 5.0.0
 */
@Configuration("casCoreServicesConfiguration")
@EnableConfigurationProperties(CasConfigurationProperties.class)
public class CasCoreServicesConfiguration {
    private static final Logger LOGGER = LoggerFactory.getLogger(CasCoreServicesConfiguration.class);
    
    private static final String BEAN_NAME_SERVICE_REGISTRY_DAO = "serviceRegistryDao";

    @Autowired
    private CasConfigurationProperties casProperties;

    @Autowired
    private ApplicationContext context;

    @Autowired
    private ApplicationEventPublisher eventPublisher;

    @RefreshScope
    @Bean
    public MultifactorTriggerSelectionStrategy defaultMultifactorTriggerSelectionStrategy() {
        final String attributeNameTriggers = casProperties.getAuthn().getMfa().getGlobalPrincipalAttributeNameTriggers();
        final String requestParameter = casProperties.getAuthn().getMfa().getRequestParameter();

        return new DefaultMultifactorTriggerSelectionStrategy(attributeNameTriggers, requestParameter);
    }

    @RefreshScope
    @Bean
    public PersistentIdGenerator shibbolethCompatiblePersistentIdGenerator() {
        return new ShibbolethCompatiblePersistentIdGenerator();
    }

    @Bean
    public ResponseBuilderLocator webApplicationResponseBuilderLocator() {
        return new DefaultWebApplicationResponseBuilderLocator();
    }

    @ConditionalOnMissingBean(name = "webApplicationServiceResponseBuilder")
    @Bean
    public ResponseBuilder<WebApplicationService> webApplicationServiceResponseBuilder() {
        return new WebApplicationServiceResponseBuilder();
    }

    @RefreshScope
    @Bean
    public ProtocolAttributeEncoder casAttributeEncoder(@Qualifier("serviceRegistryDao")
                                                        final ServiceRegistryDao serviceRegistryDao,
                                                        @Qualifier("cacheCredentialsCipherExecutor")
                                                        final CipherExecutor cacheCredentialsCipherExecutor) {
        return new DefaultCasProtocolAttributeEncoder(servicesManager(serviceRegistryDao),
                registeredServiceCipherExecutor(), cacheCredentialsCipherExecutor);
    }

    @Bean
    public ProtocolAttributeEncoder noOpCasAttributeEncoder() {
        return new NoOpProtocolAttributeEncoder();
    }

    @Bean
    public RegisteredServiceCipherExecutor registeredServiceCipherExecutor() {
        return new DefaultRegisteredServiceCipherExecutor();
    }

    @Bean
    public ServicesManager servicesManager(@Qualifier("serviceRegistryDao") final ServiceRegistryDao serviceRegistryDao) {
        return new DefaultServicesManager(serviceRegistryDao);
    }

    @ConditionalOnMissingBean(name = BEAN_NAME_SERVICE_REGISTRY_DAO)
    @Bean
    public ServiceRegistryDao serviceRegistryDao() {
        LOGGER.warn("Runtime memory is used as the persistence storage for retrieving and persisting service definitions. "
                + "Changes that are made to service definitions during runtime "
                + "WILL be LOST upon container restarts.");
        
        final InMemoryServiceRegistry impl = new InMemoryServiceRegistry();
        if (context.containsBean("inMemoryRegisteredServices")) {
            final List list = context.getBean("inMemoryRegisteredServices", List.class);
            impl.setRegisteredServices(list);
        }
        return impl;
    }

    @Autowired
    @ConditionalOnMissingBean(name = "jsonServiceRegistryDao")
    @Bean
    public ServiceRegistryInitializer serviceRegistryInitializer(@Qualifier(BEAN_NAME_SERVICE_REGISTRY_DAO) final ServiceRegistryDao serviceRegistryDao) {
        return new ServiceRegistryInitializer(embeddedJsonServiceRegistry(eventPublisher), serviceRegistryDao, servicesManager(serviceRegistryDao),
                casProperties.getServiceRegistry().isInitFromJson());
    }

    @Autowired
    @ConditionalOnMissingBean(name = "jsonServiceRegistryDao")
    @Bean
    public ServiceRegistryDao embeddedJsonServiceRegistry(final ApplicationEventPublisher publisher) {
        try {
            return new EmbeddedServiceRegistryDao(publisher);
        } catch (final Exception e) {
            throw Throwables.propagate(e);
        }
    }

    /**
     * The embedded service registry that processes built-in JSON service files
     * on the classpath.
     */
    public static class EmbeddedServiceRegistryDao extends AbstractResourceBasedServiceRegistryDao {
        EmbeddedServiceRegistryDao(final ApplicationEventPublisher publisher) throws Exception {
            super(new ClassPathResource("services"), new RegisteredServiceJsonSerializer(), false, publisher);
        }

        @Override
        protected String getExtension() {
            return "json";
        }
    }
}

@@ -1264,24 +1264,74 @@
 ceRegistry;%0A
+import org.apereo.cas.services.RegisteredService;%0A
 import org.a
@@ -2390,16 +2390,44 @@
 ource;%0A%0A
+import java.util.ArrayList;%0A
 import j
@@ -2802,20 +2802,16 @@
 class);%0A
-    
 %0A    pri
@@ -5559,37 +5559,16 @@
 runtime 
-%22%0A                + %22
 WILL be 
@@ -5600,24 +5600,16 @@
 rts.%22);%0A
-        
 %0A       
@@ -5619,66 +5619,58 @@
 nal 
-InMemoryServiceRegistry impl = new InMemoryServiceRegistry
+List%3CRegisteredService%3E services = new ArrayList%3C%3E
 ();%0A
@@ -5751,26 +5751,24 @@
     
-final List list = 
+services.addAll(
 cont
@@ -5820,16 +5820,17 @@
 t.class)
+)
 ;%0A      
@@ -5835,55 +5835,18 @@
     
+%7D
     
-impl.setRegisteredServices(list);%0A
      
-   %7D
 %0A   
@@ -5861,12 +5861,45 @@
 urn 
-impl
+new InMemoryServiceRegistry(services)
 ;%0A  

Clean up service registry creation