/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache license, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the license for the specific language governing permissions and
 * limitations under the license.
 */
package org.apache.logging.log4j.catalog.config;

import java.io.File;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collections;
import java.util.List;

import com.fasterxml.jackson.databind.ObjectMapper;

import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
import com.jcraft.jsch.UserInfo;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.catalog.api.dao.CatalogDao;
import org.apache.logging.log4j.catalog.api.util.CatalogEventFilter;
import org.apache.logging.log4j.catalog.git.dao.GitCatalogDao;
import org.apache.logging.log4j.catalog.security.LocalAuthorizationInterceptor;
import org.eclipse.jgit.api.TransportConfigCallback;
import org.eclipse.jgit.transport.JschConfigSessionFactory;
import org.eclipse.jgit.transport.OpenSshConfig;
import org.eclipse.jgit.transport.SshSessionFactory;
import org.eclipse.jgit.transport.SshTransport;
import org.eclipse.jgit.transport.Transport;
import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
import org.eclipse.jgit.util.FS;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.annotation.Scope;
import org.springframework.context.support.ResourceBundleMessageSource;
import org.springframework.core.annotation.Order;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.thymeleaf.spring4.SpringTemplateEngine;
import org.thymeleaf.spring4.templateresolver.SpringResourceTemplateResolver;
import org.thymeleaf.spring4.view.ThymeleafView;
import org.thymeleaf.spring4.view.ThymeleafViewResolver;
import org.thymeleaf.templatemode.TemplateMode;

import static org.apache.commons.lang3.StringUtils.isNotBlank;

@Configuration
@ComponentScan(basePackages = {"org.apache.logging.log4j.catalog"})
//@PropertySource(value = "classpath:catalog-${env:}config.properties", ignoreResourceNotFound = true)
public class WebMvcAppContext extends WebMvcConfigurerAdapter implements ApplicationContextAware {

    private static final Logger LOGGER = LogManager.getLogger(WebMvcAppContext.class);

    @Autowired
    ConfigurationService configurationService;

    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }

    @Override
    public void addResourceHandlers(final ResourceHandlerRegistry registry) {
        super.addResourceHandlers(registry);
        registry.addResourceHandler("/images/**").addResourceLocations("classpath:/static/images/");
        registry.addResourceHandler("/css/**").addResourceLocations("classpath:/static/css/");
        registry.addResourceHandler("/js/**").addResourceLocations("classpath:/static/js/");
        registry.addResourceHandler("swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");
        registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
    }

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("products").setViewName("products");
        registry.addViewController("categories").setViewName("categories");
        registry.addViewController("events").setViewName("events");
        registry.addViewController("attributes").setViewName("attributes");
    }

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.add(jsonMessageConverter());
    }

    @Bean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator proxyCreator = new DefaultAdvisorAutoProxyCreator();
        proxyCreator.setProxyTargetClass(true);
        return proxyCreator;
    }

    @Bean
    public MessageSource messageSource() {
        ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
        messageSource.setBasename("messages");
        return messageSource;
    }

    @Bean
    public LocalAuthorizationInterceptor localAuthorizationInterceptor() {

        return new LocalAuthorizationInterceptor(configurationService.getCatalogServiceAuthToken());
    }

    public ObjectMapper objectMapper() {
        LOGGER.debug("Creating custom ObjectMapper");
        ObjectMapper mapper = JsonObjectMapperFactory.createMapper();
        SimpleFilterProvider filterProvider = new SimpleFilterProvider();
        filterProvider.addFilter("catalogEvent", new CatalogEventFilter());
        mapper.setFilterProvider(filterProvider);
        return mapper;
    }

    public MappingJackson2HttpMessageConverter jsonMessageConverter() {
        return new MappingJackson2HttpMessageConverter(objectMapper());
    }

    @Value("${gitUserName")
    private String gitUserName;

    @Value("${gitPassword:#{null}}")
    private String gitPassword;

    @Value("${gitPassPhrase:#{null}}")
    private String gitPassPhrase;

    @Value("${localRepoUrl:#{null}}")
    private String localRepoUrl;

    @Value("${branch:#{null}}")
    private String branch;

    @Value("${privateKeyPath:#{null}}")
    private String privateKeyPath;

    @Value("${remoteRepoUrl}")
    private String remoteRepoUrl;

    @Value("${remoteRepoCatalogPath:#{null}}")
    private String remoteRepoCatalogPath;

    @Bean
    public CatalogDao catalogDao() {
        GitCatalogDao dataSource = new GitCatalogDao();
        if (isNotBlank(gitUserName) && isNotBlank(gitPassword)) {
            dataSource.setCredentialsProvider(new UsernamePasswordCredentialsProvider(gitUserName, gitPassword));
        }
        if (isNotBlank(remoteRepoUrl)) {
            try {
                URI uri = new URI(remoteRepoUrl);
                if (uri.getScheme().equalsIgnoreCase("SSH")) {
                    TransportConfigCallback transportConfigCallback = new TransportConfigCallback() {
                        final SshSessionFactory sshSessionFactory = new JschConfigSessionFactory() {
                            @Override
                            protected JSch createDefaultJSch( FS fs ) throws JSchException {
                                JSch defaultJSch = super.createDefaultJSch( fs );
                                if (isNotBlank(privateKeyPath)) {
                                    defaultJSch.addIdentity(privateKeyPath);
                                }
                                return defaultJSch;
                            }

                            @Override
                            protected void configure(OpenSshConfig.Host host, Session session) {
                                session.setConfig("StrictHostKeyChecking", "no");
                                if (isNotBlank(gitPassPhrase)) {
                                    session.setUserInfo(new UserInfo() {
                                        @Override
                                        public String getPassphrase() {
                                            return gitPassPhrase;
                                        }

                                        @Override
                                        public String getPassword() {return null;}

                                        @Override
                                        public boolean promptPassword(String message) {return false;}

                                        @Override
                                        public boolean promptPassphrase(String message) {return true;}

                                        @Override
                                        public boolean promptYesNo(String message) {return false;}

                                        @Override
                                        public void showMessage(String message) {}
                                    });

                                }
                            }
                        };
                        @Override
                        public void configure(Transport transport) {
                            SshTransport sshTransport = ( SshTransport )transport;
                            sshTransport.setSshSessionFactory( sshSessionFactory );

                        }
                    };
                    dataSource.setTransportConfigCallback(transportConfigCallback);
                }
            } catch (URISyntaxException ex) {
                LOGGER.error("Invalid URI {}:", remoteRepoUrl, ex);
            }
        } else {
            LOGGER.error("No remote repo URL provided.");
        }
        if (isNotBlank(branch)) {
            dataSource.setBranch(branch);
        }

        if (isNotBlank(localRepoUrl)) {
            dataSource.setLocalRepoPath(localRepoUrl);
        } else {
            String localRepoPath = System.getProperty("java.io.tmpdir") + "/audit/catalog";
            File file = new File(localRepoPath);
            File parent = file.getParentFile();
            parent.mkdirs();
            dataSource.setLocalRepoPath(localRepoPath);
        }

        dataSource.setRemoteRepoUri(remoteRepoUrl);
        if (isNotBlank(remoteRepoCatalogPath)) {
            dataSource.setCatalogPath(remoteRepoCatalogPath);
        }
        return dataSource;
    }
}
