/*
 * Copyright 2012 SURFnet bv, The Netherlands
 *
 * Licensed 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 eu.dnetlib.oauth.config;

import eu.dnetlib.oauth.authenticate.Authenticator;
import eu.dnetlib.oauth.filters.FormConsentFilter;
import eu.dnetlib.oauth.store.CredentialsStore;
import eu.dnetlib.oauth.store.DBStore;
import eu.dnetlib.oauth.store.LDAPStore;
import org.apache.openjpa.persistence.PersistenceProviderImpl;
import org.apache.tomcat.jdbc.pool.DataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.*;
import org.springframework.core.env.Environment;
import org.springframework.data.jpa.repository.support.JpaRepositoryFactory;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.surfnet.oaaas.auth.*;
import org.surfnet.oaaas.repository.*;
import org.surfnet.oaaas.resource.TokenResource;
import org.surfnet.oaaas.resource.VerifyResource;
import org.surfnet.oaaas.support.Cleaner;

import javax.inject.Inject;
import javax.persistence.EntityManager;
import javax.servlet.Filter;
import javax.servlet.ServletException;
import javax.validation.Validator;
import java.util.HashMap;
import java.util.Map;

/**
 * The SpringConfiguration is a {@link Configuration} that can be overridden if
 * you want to plugin your own implementations. Note that the two most likely
 * candidates to change are the {@link AbstractAuthenticator} an
 * {@link AbstractUserConsentHandler}. You can change the implementation by
 * editing the application.apis.properties file where the implementations are
 * configured.
 */
@Configuration
@PropertySource("classpath:apis.application.properties")
/*
 * The component scan can be used to add packages and exclusions to the default
 * package
 */
@ComponentScan(basePackages = {"eu.dnetlib.oauth.resource"})
@ImportResource("classpath:spring-repositories.xml")
@EnableTransactionManagement
@EnableScheduling
public class SpringConfiguration {

    private static final String PERSISTENCE_UNIT_NAME = "oaaas";
    private static final Class<PersistenceProviderImpl> PERSISTENCE_PROVIDER_CLASS = PersistenceProviderImpl.class;

    @Inject
    Environment env;
    private final Logger log = LoggerFactory.getLogger(this.getClass());

    private JpaRepositoryFactory factory;

    @Bean
    public javax.sql.DataSource authenticationRepository() {
        DataSource authenticationRepository = new DataSource();
        authenticationRepository.setDriverClassName(env.getProperty("jdbc.driverClassName"));
        authenticationRepository.setUrl(env.getProperty("jdbc.url"));
        authenticationRepository.setUsername(env.getProperty("jdbc.username"));
        authenticationRepository.setPassword(env.getProperty("jdbc.password"));
        return authenticationRepository;
    }

    @Bean
    public javax.sql.DataSource dataSource() {
        DataSource dataSource = new DataSource();
        dataSource.setDriverClassName(env.getProperty("oauth_store.driverClassName"));
        dataSource.setUrl(env.getProperty("oauth_store.url"));
        dataSource.setUsername(env.getProperty("oauth_store.username"));
        dataSource.setPassword(env.getProperty("oauth_store.password"));

        return dataSource;
    }

    @Bean
    public CredentialsStore dbStore() {
        return new DBStore();

    }

    ;

    @Bean
    public CredentialsStore LDAPStore() {
        LDAPStore LDAPStore = new eu.dnetlib.oauth.store.LDAPStore();

        LDAPStore.setPassword(env.getProperty("ldap.password"));
        LDAPStore.setUsername(env.getProperty("ldap.username"));
        LDAPStore.setLdapURL(env.getProperty("ldap.url"));
        LDAPStore.setPort(Integer.parseInt(env.getProperty("ldap.port")));
        LDAPStore.setSSL(env.getProperty("ldap.ssl"));
        return LDAPStore;
    }


    @Bean
    public JpaTransactionManager transactionManager() {
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        // transactionManager.setEntityManagerFactory((javax.persistence.EntityManagerFactory) entityManagerFactory());
        return transactionManager;

    }

    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
        final LocalContainerEntityManagerFactoryBean emfBean = new LocalContainerEntityManagerFactoryBean();
        emfBean.setDataSource(dataSource());
        emfBean.setPersistenceUnitName(PERSISTENCE_UNIT_NAME);
        emfBean.setPersistenceProviderClass(PERSISTENCE_PROVIDER_CLASS);
        return emfBean;
    }

    /*New beeans for entity managers start here*/

    @Bean
    public EntityManager entityManager() {
        LocalContainerEntityManagerFactoryBean emfBean = new LocalContainerEntityManagerFactoryBean();
        emfBean.setDataSource(dataSource());
        emfBean.setPersistenceUnitName(PERSISTENCE_UNIT_NAME);
        emfBean.setPersistenceProviderClass(PERSISTENCE_PROVIDER_CLASS);
        emfBean.afterPropertiesSet();
        Map map = new HashMap<String, String>();
        map.put("openjpa.ConnectionFactoryProperties", "PrintParameters=true");
        return emfBean.getObject().createEntityManager(map);

    }

    @Bean
    public JpaRepositoryFactory factory() {
        factory = new JpaRepositoryFactory(entityManager());
        return factory;
    }

    /*New beans for entity managers end  here*/
/*Testing repos*/

    @Bean
    public TokenResource tokenResource()

    {
        TokenResource tokenResource = new TokenResource();
        tokenResource.setAuthorizationRequestRepository((AuthorizationRequestRepository) factory().getRepository(AuthorizationRequestRepository.class));
        tokenResource.setAccessTokenRepository((AccessTokenRepository) factory().getRepository(AccessTokenRepository.class));

        return tokenResource;
    }


    @Bean
    public VerifyResource verifyResource()

    {
        VerifyResource
                vf = new VerifyResource();
        vf.setResourceServerRepository((ResourceServerRepository) factory().getRepository(ResourceServerRepository.class));

        vf.setAccessTokenRepository((AccessTokenRepository) factory().getRepository(AccessTokenRepository.class));

        return vf;
    }


    @Bean
    public Filter oauth2AuthenticationFilter() {
        final AuthenticationFilter authenticationFilter = new AuthenticationFilter();
        authenticationFilter.setAuthenticator(authenticator());
        return authenticationFilter;
    }


    @Bean
    public Filter oauth2UserConsentFilter() throws Exception {
        UserConsentFilter userConsentFilter = new FormConsentFilter();
        userConsentFilter.setUserConsentHandler(userConsentHandler());
        return userConsentFilter;
    }

    /*

    @Bean
    public Filter AuthorizationServerFilter
            () {
        final AuthorizationServerFilter filter = new AuthorizationServerFilter();

        filter.setCacheEnabled(false);
        filter.setResourceServerKey(env.getProperty("adminService.resourceServerKey"));
        filter.setAuthorizationServerUrl("http://localhost:8080/uoa-oauth-server-1.0.0-SNAPSHOT/");
        filter.setTypeInformationIsIncluded(Boolean.getBoolean(env.getProperty("adminService.jsonTypeInfoIncluded")));
        filter.setResourceServerSecret(env.getProperty("adminService.resourceServerSecret"));
        filter.setAllowCorsRequests(Boolean.getBoolean("false"));


        return filter;
    }

    ;
*/
    //TODO  RESOURCE SERVER  REPO is null

    @Bean
    public OAuth2Validator oAuth2Validator() {
        return new OAuth2ValidatorImpl();
    }

    @Bean
    public ResourceOwnerAuthenticator resourceOwnerAuthenticator() {
        return (ResourceOwnerAuthenticator) getConfiguredBean("resourceOwnerAuthenticatorClass");
    }

    /**
     * Returns the {@link AbstractAuthenticator} that is responsible for the
     * authentication of Resource Owners.
     *
     * @return an {@link AbstractAuthenticator}
     */
    @Bean
    public AbstractAuthenticator authenticator() {
        AbstractAuthenticator authenticatorClass = (AbstractAuthenticator) getConfiguredBean("authenticatorClass");
        try {
            authenticatorClass.init(null);
        } catch (ServletException e) {
            throw new RuntimeException(e);
        }
        return authenticatorClass;
    }


    @Bean
    public ResourceServerRepository resourceServerRepository() {
        return factory.getRepository(ResourceServerRepository.class);
    }

    @Bean
    public Authenticator multiAuthenticator() {
        Authenticator multiAuthenticator = new Authenticator();

        try {
            multiAuthenticator.setDBStore(this.dbStore());
            multiAuthenticator.setLDAPStore(this.LDAPStore());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return multiAuthenticator;
    }

    @Bean
    public AbstractUserConsentHandler userConsentHandler() throws Exception {

        AbstractUserConsentHandler userConsentHandler = (AbstractUserConsentHandler) getConfiguredBean("userConsentHandlerClass");

        if (userConsentHandler == null) {
            throw new RuntimeException("user consent handler bean is null");
        }


        try {
            userConsentHandler.init(null);
        } catch (ServletException e) {
            throw new RuntimeException(e);
        }
        return userConsentHandler;
    }


    @Bean
    public ExceptionTranslator exceptionTranslator() {
        return new OpenJPAExceptionTranslator();
    }

    private Object getConfiguredBean(String className) {
        try {
            return getClass().getClassLoader().loadClass(env.getProperty(className)).newInstance();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Bean
    public Validator validator() {
        // This LocalValidatorFactoryBean already uses the
        // SpringConstraintValidatorFactory by default,
        // so available validators will be wired automatically.
        return new org.springframework.validation.beanvalidation.LocalValidatorFactoryBean();
    }

    @Bean
    public Cleaner cleaner() {
        return new Cleaner();
    }
}
