package com.cerner.jwala.service.configuration.service;

import com.cerner.jwala.commandprocessor.impl.jsch.JschBuilder;
import com.cerner.jwala.commandprocessor.jsch.impl.ChannelSessionKey;
import com.cerner.jwala.commandprocessor.jsch.impl.KeyedPooledJschChannelFactory;
import com.cerner.jwala.common.FileUtility;
import com.cerner.jwala.common.domain.model.id.Identifier;
import com.cerner.jwala.common.domain.model.jvm.Jvm;
import com.cerner.jwala.common.domain.model.jvm.JvmState;
import com.cerner.jwala.common.domain.model.ssh.DecryptPassword;
import com.cerner.jwala.common.domain.model.ssh.SshConfiguration;
import com.cerner.jwala.common.domain.model.state.CurrentState;
import com.cerner.jwala.common.domain.model.webserver.WebServer;
import com.cerner.jwala.common.domain.model.webserver.WebServerReachableState;
import com.cerner.jwala.common.properties.ApplicationProperties;
import com.cerner.jwala.control.configuration.AemCommandExecutorConfig;
import com.cerner.jwala.control.configuration.SshConfig;
import com.cerner.jwala.persistence.configuration.AemPersistenceServiceConfiguration;
import com.cerner.jwala.persistence.jpa.service.*;
import com.cerner.jwala.persistence.jpa.service.impl.GroupJvmRelationshipServiceImpl;
import com.cerner.jwala.persistence.service.*;
import com.cerner.jwala.persistence.service.impl.JpaJvmPersistenceServiceImpl;
import com.cerner.jwala.persistence.service.impl.ResourceDaoImpl;
import com.cerner.jwala.service.HistoryFacadeService;
import com.cerner.jwala.service.HistoryService;
import com.cerner.jwala.service.RemoteCommandExecutorService;
import com.cerner.jwala.service.app.ApplicationCommandService;
import com.cerner.jwala.service.app.ApplicationService;
import com.cerner.jwala.service.app.impl.ApplicationCommandServiceImpl;
import com.cerner.jwala.service.app.impl.ApplicationServiceImpl;
import com.cerner.jwala.service.balancermanager.BalancerManagerService;
import com.cerner.jwala.service.balancermanager.impl.BalancerManagerHtmlParser;
import com.cerner.jwala.service.balancermanager.impl.BalancerManagerHttpClient;
import com.cerner.jwala.service.balancermanager.impl.BalancerManagerServiceImpl;
import com.cerner.jwala.service.balancermanager.impl.BalancerManagerXmlParser;
import com.cerner.jwala.service.binarydistribution.BinaryDistributionControlService;
import com.cerner.jwala.service.binarydistribution.BinaryDistributionLockManager;
import com.cerner.jwala.service.binarydistribution.BinaryDistributionService;
import com.cerner.jwala.service.binarydistribution.impl.BinaryDistributionControlServiceImpl;
import com.cerner.jwala.service.binarydistribution.impl.BinaryDistributionLockManagerImpl;
import com.cerner.jwala.service.binarydistribution.impl.BinaryDistributionServiceImpl;
import com.cerner.jwala.service.bootstrap.ApplicationContextListener;
import com.cerner.jwala.service.group.*;
import com.cerner.jwala.service.group.impl.GroupControlServiceImpl;
import com.cerner.jwala.service.group.impl.GroupJvmControlServiceImpl;
import com.cerner.jwala.service.group.impl.GroupServiceImpl;
import com.cerner.jwala.service.group.impl.GroupWebServerControlServiceImpl;
import com.cerner.jwala.service.impl.HistoryServiceImpl;
import com.cerner.jwala.service.initializer.JGroupsClusterInitializer;
import com.cerner.jwala.service.jvm.JvmControlService;
import com.cerner.jwala.service.jvm.JvmService;
import com.cerner.jwala.service.jvm.JvmStateService;
import com.cerner.jwala.service.jvm.impl.JvmControlServiceImpl;
import com.cerner.jwala.service.jvm.impl.JvmServiceImpl;
import com.cerner.jwala.service.jvm.state.JvmStateReceiverAdapter;
import com.cerner.jwala.service.repository.RepositoryService;
import com.cerner.jwala.service.resource.ResourceContentGeneratorService;
import com.cerner.jwala.service.resource.ResourceService;
import com.cerner.jwala.service.resource.impl.ResourceServiceImpl;
import com.cerner.jwala.service.resource.impl.handler.WebServerResourceHandler;
import com.cerner.jwala.service.state.InMemoryStateManagerService;
import com.cerner.jwala.service.state.impl.InMemoryStateManagerServiceImpl;
import com.cerner.jwala.service.webserver.WebServerCommandService;
import com.cerner.jwala.service.webserver.WebServerControlService;
import com.cerner.jwala.service.webserver.WebServerService;
import com.cerner.jwala.service.webserver.WebServerStateRetrievalScheduledTaskHandler;
import com.cerner.jwala.service.webserver.component.ClientFactoryHelper;
import com.cerner.jwala.service.webserver.component.WebServerStateSetterWorker;
import com.cerner.jwala.service.webserver.impl.WebServerCommandServiceImpl;
import com.cerner.jwala.service.webserver.impl.WebServerControlServiceImpl;
import com.cerner.jwala.service.webserver.impl.WebServerServiceImpl;
import com.jcraft.jsch.Channel;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import org.apache.commons.pool2.impl.GenericKeyedObjectPool;
import org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig;
import org.apache.tika.Tika;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.task.TaskExecutor;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.concurrent.CustomizableThreadFactory;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;

@Configuration
@EnableAsync
@EnableScheduling
@ComponentScan({"com.cerner.jwala.service.webserver.component",
        "com.cerner.jwala.service.state",
        "com.cerner.jwala.service.spring.component",
        "com.cerner.jwala.commandprocessor.jsch.impl.spring.component",
        "com.cerner.jwala.service.group.impl.spring.component",
        "com.cerner.jwala.service.jvm.impl.spring.component",
        "com.cerner.jwala.service.impl.spring.component",
        "com.cerner.jwala.service.resource.impl",
        "com.cerner.jwala.common",
        "com.cerner.jwala.service.impl",
        "com.cerner.jwala.service.media.impl",
        "com.cerner.jwala.common.jsch.impl",
        "com.cerner.jwala.service.jvm.impl",
        "com.cerner.jwala.service.jvm.operation.impl",
        "com.cerner.jwala.control.jvm.command",
        "com.cerner.jwala.control.webserver.command",
        "com.cerner.jwala.commandprocessor.impl.jsch",
        "com.cerner.jwala.control.command.common",
        "com.cerner.jwala.common.scrubber.impl"})
public class AemServiceConfiguration {

    @Autowired
    private AemPersistenceServiceConfiguration aemPersistenceServiceConfiguration;

    @Autowired
    private AemCommandExecutorConfig aemCommandExecutorConfig;

    @Autowired
    private SshConfig sshConfig;

    @Autowired
    private SimpMessagingTemplate messagingTemplate;

    @Autowired
    private GenericKeyedObjectPool<ChannelSessionKey, Channel> channelPool;

    @Autowired
    private GroupStateNotificationService groupStateNotificationService;

    @Autowired
    private BinaryDistributionService binaryDistributionService;

    @Autowired
    private ResourceService resourceService;

    @Autowired
    private BinaryDistributionLockManager binaryDistributionLockManager;

    /**
     * Make vars.properties available to spring integration configuration
     * System properties are only used if there is no setting in vars.properties.
     */
    @Bean(name = "aemServiceConfigurationPropertiesConfigurer")
    public static PropertySourcesPlaceholderConfigurer configurer() {
        PropertySourcesPlaceholderConfigurer ppc = new PropertySourcesPlaceholderConfigurer();
        ppc.setLocation(new ClassPathResource("META-INF/spring/jwala-defaults.properties"));
        ppc.setLocalOverride(true);
        ppc.setProperties(ApplicationProperties.getProperties());
        return ppc;
    }

    @Bean
    public GroupService getGroupService(final HistoryFacadeService historyFacadeService) {
        return new GroupServiceImpl(aemPersistenceServiceConfiguration.getGroupPersistenceService(), resourceService);
    }

    @Bean(name = "jvmService")
    public JvmService getJvmService(final GroupPersistenceService groupPersistenceService,
                                    final ApplicationService applicationService,
                                    final ResourceService resourceService, final ClientFactoryHelper clientFactoryHelper,
                                    @Value("${spring.messaging.topic.serverStates:/topic/server-states}") final String topicServerStates,
                                    final JvmControlService jvmControlService, final HistoryFacadeService historyFacadeService,
                                    final FileUtility fileUtility) {
        final JvmPersistenceService jvmPersistenceService = aemPersistenceServiceConfiguration.getJvmPersistenceService();
        return new JvmServiceImpl(jvmPersistenceService, groupPersistenceService, applicationService,
                messagingTemplate, groupStateNotificationService, resourceService,
                clientFactoryHelper, topicServerStates, jvmControlService, binaryDistributionService, binaryDistributionLockManager,
                historyFacadeService, fileUtility);
    }

    @Bean(name = "binaryDistributionLockManager")
    public BinaryDistributionLockManager getBinaryDistributionLockManager() {
        return new BinaryDistributionLockManagerImpl();
    }

    @Bean(name = "balancermanagerService")
    public BalancerManagerService getBalancermanagerService(final GroupService groupService,
                                                            final ApplicationService applicationService,
                                                            final WebServerService webServerService,
                                                            final JvmService jvmService,
                                                            final ClientFactoryHelper clientFactoryHelper,
                                                            final HistoryFacadeService historyFacadeService) {
        return new BalancerManagerServiceImpl(groupService, applicationService, webServerService, jvmService, clientFactoryHelper,
                new BalancerManagerHtmlParser(), new BalancerManagerXmlParser(jvmService),
                new BalancerManagerHttpClient(), historyFacadeService);
    }

    @Bean(name = "webServerService")
    public WebServerService getWebServerService(final ResourceService resourceService,
                                                @Qualifier("webServerInMemoryStateManagerService")
                                                final InMemoryStateManagerService<Identifier<WebServer>, WebServerReachableState> inMemoryStateManagerService,
                                                @Value("${paths.resource-templates:../data/templates}") final String templatePath,
                                                @Value("${spring.messaging.topic.serverStates:/topic/server-states}") final String topicServerStates) {
        return new WebServerServiceImpl(
                aemPersistenceServiceConfiguration.getWebServerPersistenceService(),
                resourceService,
                inMemoryStateManagerService,
                templatePath,
                binaryDistributionLockManager);
    }

    @Bean
    public GroupJvmRelationshipService groupJvmRelationshipService(final GroupCrudService groupCrudService,
                                                                   final JvmCrudService jvmCrudService) {
        return new GroupJvmRelationshipServiceImpl(groupCrudService, jvmCrudService);
    }

    @Bean
    public JvmPersistenceService getJvmPersistenceService(final JvmCrudService jvmCrudService,
                                                          final ApplicationCrudService applicationCrudService,
                                                          final GroupJvmRelationshipService groupJvmRelationshipService) {
        return new JpaJvmPersistenceServiceImpl(jvmCrudService, applicationCrudService, groupJvmRelationshipService);
    }

    @Bean
    public ApplicationService getApplicationService(final JvmPersistenceService jvmPersistenceService, final GroupPersistenceService groupPersistenceService,
                                                    final ResourceService resourceService, final HistoryFacadeService historyFacadeService, BinaryDistributionLockManager lockManager) {
        return new ApplicationServiceImpl(aemPersistenceServiceConfiguration.getApplicationPersistenceService(),
                jvmPersistenceService, groupPersistenceService,
                resourceService,
                binaryDistributionService, historyFacadeService, lockManager);
    }

    @Bean(name = "jvmControlService")
    public JvmControlService getJvmControlService(
            final JvmStateService jvmStateService,
            final HistoryFacadeService historyFacadeService) {
        return new JvmControlServiceImpl(
                aemPersistenceServiceConfiguration.getJvmPersistenceService(),
                jvmStateService,
                historyFacadeService);
    }

    @Bean(name = "groupControlService")
    public GroupControlService getGroupControlService(final GroupJvmControlService groupJvmControlService,
                                                      final GroupWebServerControlService groupWebServerControlService) {
        return new GroupControlServiceImpl(groupWebServerControlService, groupJvmControlService);
    }

    @Bean(name = "groupJvmControlService")
    public GroupJvmControlService getGroupJvmControlService(final GroupService groupService, final JvmControlService jvmControlService) {
        return new GroupJvmControlServiceImpl(groupService, jvmControlService);
    }

    @Bean(name = "groupWebServerControlService")
    public GroupWebServerControlService getGroupWebServerControlService(final GroupService groupService, final WebServerControlService webServerControlService) {
        return new GroupWebServerControlServiceImpl(groupService, webServerControlService);
    }

    @Bean(name = "webServerControlService")
    public WebServerControlService getWebServerControlService() {
        return new WebServerControlServiceImpl();
    }

    @Bean(name = "webServerCommandService")
    public WebServerCommandService getWebServerCommandService(final WebServerService webServerService,
                                                              final RemoteCommandExecutorService remoteCommandExecutorService) {
        final SshConfiguration sshConfig = this.sshConfig.getSshConfiguration();

        final JschBuilder jschBuilder = new JschBuilder().setPrivateKeyFileName(sshConfig.getPrivateKeyFile())
                .setKnownHostsFileName(sshConfig.getKnownHostsFile());

        return new WebServerCommandServiceImpl(
                webServerService,
                sshConfig,
                remoteCommandExecutorService);
    }

    @Bean
    public ApplicationCommandService getApplicationCommandService() {
        return new ApplicationCommandServiceImpl(sshConfig.getSshConfiguration(), sshConfig.getJschBuilder());
    }

    @Bean(name = "resourceService")
    public ResourceService getResourceService(final ApplicationPersistenceService applicationPersistenceService,
                                              final JvmPersistenceService jvmPersistenceService,
                                              final WebServerPersistenceService webServerPersistenceService,
                                              final ResourceDao resourceDao,
                                              final WebServerResourceHandler webServerResourceHandler,
                                              final ResourceContentGeneratorService resourceContentGeneratorService,
                                              @Qualifier("resourceRepositoryService")
                                              final RepositoryService repositoryService) {
        return new ResourceServiceImpl(aemPersistenceServiceConfiguration.getResourcePersistenceService(),
                aemPersistenceServiceConfiguration.getGroupPersistenceService(), applicationPersistenceService,
                jvmPersistenceService, webServerPersistenceService, resourceDao, webServerResourceHandler,
                resourceContentGeneratorService, binaryDistributionService, new Tika(), repositoryService);
    }

    @Bean
    public ResourceDao getResourceDao() {
        return new ResourceDaoImpl();
    }

    @Bean(name = "httpRequestFactory")
    public HttpComponentsClientHttpRequestFactory getHttpComponentsClientHttpRequestFactory(
            @Value("${ping.http.connectionRequestTimeout:60000}") final int connectionRequestTimeout,
            @Value("${ping.http.readTimeout:600000}") final int readTimeout) {
        final HttpComponentsClientHttpRequestFactory httpRequestFactory = new HttpComponentsClientHttpRequestFactory();
        httpRequestFactory.setConnectionRequestTimeout(connectionRequestTimeout);
        httpRequestFactory.setReadTimeout(readTimeout);
        return httpRequestFactory;
    }

    @Bean(name = "webServerStateRetrievalScheduledTaskHandler")
    public WebServerStateRetrievalScheduledTaskHandler getWebServerStateRetrievalScheduledTaskHandler(
            final WebServerService webServerService, final WebServerStateSetterWorker webServerStateSetterWorker) {
        return new WebServerStateRetrievalScheduledTaskHandler(webServerService, webServerStateSetterWorker, true);
    }

    @Bean(name = "webServerTaskExecutor")
    public TaskExecutor getWebServerTaskExecutor(@Qualifier("pollingThreadFactory") final ThreadFactory threadFactory,
                                                 @Value("${webserver.thread-task-executor.pool.size}") final int corePoolSize,
                                                 @Value("${webserver.thread-task-executor.pool.max-size}") final int maxPoolSize,
                                                 @Value("${webserver.thread-task-executor.pool.queue-capacity}") final int queueCapacity,
                                                 @Value("${webserver.thread-task-executor.pool.keep-alive-sec}") final int keepAliveSeconds) {
        final ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
        threadPoolTaskExecutor.setCorePoolSize(corePoolSize);
        threadPoolTaskExecutor.setMaxPoolSize(maxPoolSize);
        threadPoolTaskExecutor.setQueueCapacity(queueCapacity);
        threadPoolTaskExecutor.setKeepAliveSeconds(keepAliveSeconds);
        threadPoolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        threadPoolTaskExecutor.setThreadFactory(threadFactory);
        return threadPoolTaskExecutor;
    }

    @Bean(name = "jvmTaskExecutor")
    public TaskExecutor getJvmTaskExecutor(@Qualifier("pollingThreadFactory") final ThreadFactory threadFactory,
                                           @Value("${jvm.thread-task-executor.pool.size}") final int corePoolSize,
                                           @Value("${jvm.thread-task-executor.pool.max-size}") final int maxPoolSize,
                                           @Value("${jvm.thread-task-executor.pool.queue-capacity}") final int queueCapacity,
                                           @Value("${jvm.thread-task-executor.pool.keep-alive-sec}") final int keepAliveSeconds) {
        final ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
        threadPoolTaskExecutor.setCorePoolSize(corePoolSize);
        threadPoolTaskExecutor.setMaxPoolSize(maxPoolSize);
        threadPoolTaskExecutor.setQueueCapacity(queueCapacity);
        threadPoolTaskExecutor.setKeepAliveSeconds(keepAliveSeconds);
        threadPoolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        threadPoolTaskExecutor.setThreadFactory(threadFactory);
        return threadPoolTaskExecutor;
    }

    @Bean
    public HistoryService getHistoryService(final HistoryCrudService historyCrudService) {
        return new HistoryServiceImpl(historyCrudService);
    }

    @Bean
    public GenericKeyedObjectPool<ChannelSessionKey, Channel> getChannelPool(final SshConfig sshConfig) throws JSchException {
        final GenericKeyedObjectPoolConfig genericKeyedObjectPoolConfig = new GenericKeyedObjectPoolConfig();
        genericKeyedObjectPoolConfig.setMaxTotalPerKey(10);
        genericKeyedObjectPoolConfig.setBlockWhenExhausted(true);
        return new GenericKeyedObjectPool(new KeyedPooledJschChannelFactory(sshConfig.getJschBuilder().build()));
    }

    @Bean
    public JvmStateReceiverAdapter getJvmReceiverAdapter(final JvmStateService jvmStateService,
                                                         final JvmPersistenceService jvmPersistenceService) {
        return new JvmStateReceiverAdapter(jvmStateService, jvmPersistenceService);
    }

    @Bean
    public JGroupsClusterInitializer jGroupsClusterInitializer(final JvmStateReceiverAdapter jvmStateReceiverAdapter) {
        return new JGroupsClusterInitializer(jvmStateReceiverAdapter);
    }

    @Bean(name = "webServerInMemoryStateManagerService")
    InMemoryStateManagerService<Identifier<WebServer>, WebServerReachableState> getWebServerInMemoryStateManagerService() {
        return new InMemoryStateManagerServiceImpl<>();
    }

    @Bean(name = "jvmInMemoryStateManagerService")
    InMemoryStateManagerService<Identifier<Jvm>, CurrentState<Jvm, JvmState>> getJvmInMemoryStateManagerService() {
        return new InMemoryStateManagerServiceImpl<>();
    }

    @Bean
    public JSch getJSch() {
        return new JSch();
    }

    /**
     * Bean method to create a thread factory that creates daemon threads.
     * <code>
     * <bean id="pollingThreadFactory" class="org.springframework.scheduling.concurrent.CustomizableThreadFactory">
     * <constructor-arg value="polling-"/>
     * </bean></code>
     */
    @Bean(name = "pollingThreadFactory")
    public ThreadFactory getPollingThreadFactory() {
        CustomizableThreadFactory tf = new CustomizableThreadFactory("polling-");
        tf.setDaemon(true);
        return tf;
    }

    @Bean(name = "binaryDistributionControlService")
    public BinaryDistributionControlServiceImpl getBinaryDistributionControlService() {
        return new BinaryDistributionControlServiceImpl();
    }

    @Bean(name = "binaryDistributionService")
    public BinaryDistributionService getBinaryDistributionService(BinaryDistributionControlService binaryDistributionControlService, HistoryFacadeService historyFacadeService) {
        return new BinaryDistributionServiceImpl();
    }

    @Bean
    public ApplicationContextListener getApplicationContextListener() {
        return new ApplicationContextListener();
    }

    @Bean
    public DecryptPassword getDecryptPassword() {
        return new DecryptPassword();
    }
}