/*
 * Copyright 2017-2019 the original author or authors.
 *
 * 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 org.opendevstack.provision.authentication.basic;

import com.atlassian.crowd.integration.http.HttpAuthenticator;
import com.atlassian.crowd.integration.http.HttpAuthenticatorImpl;
import com.atlassian.crowd.integration.springsecurity.RemoteCrowdAuthenticationProvider;
import com.atlassian.crowd.integration.springsecurity.user.CrowdUserDetailsService;
import com.atlassian.crowd.integration.springsecurity.user.CrowdUserDetailsServiceImpl;
import com.atlassian.crowd.service.AuthenticationManager;
import com.atlassian.crowd.service.GroupManager;
import com.atlassian.crowd.service.UserManager;
import com.atlassian.crowd.service.cache.*;
import com.atlassian.crowd.service.soap.client.SecurityServerClient;
import com.atlassian.crowd.service.soap.client.SecurityServerClientImpl;
import com.atlassian.crowd.service.soap.client.SoapClientPropertiesImpl;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import net.sf.ehcache.CacheManager;
import org.opendevstack.provision.authentication.SimpleCachingGroupMembershipManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cache.ehcache.EhCacheManagerFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;

@Configuration
@ConditionalOnProperty(
    name = "provision.auth.provider.basic-auth.activate-beside-oauth2",
    havingValue = "true")
public class BasicAuthConfig {

  private static final Logger logger = LoggerFactory.getLogger(BasicAuthConfig.class);

  @Value("${idmanager.realm}")
  private String idManagerRealm;

  @Value("${crowd.application.name}")
  String crowdApplicationName;

  @Value("${crowd.application.password}")
  String crowdApplicationPassword;

  @Value("${crowd.server.url}")
  String crowdServerUrl;

  @Value("${crowd.cookie.domain}")
  String cookieDomain;

  private SecurityServerClientImpl securityServerClient;

  @Bean
  public RemoteCrowdAuthenticationProvider crowdAuthenticationProvider() throws IOException {
    logger.info(
        "Created RemoteCrowdAuthenticationProvider to enable REST API calls with Basic Auth beside OAuth2!");
    return new RemoteCrowdAuthenticationProvider(
        simpleCrowdAuthenticationManager(), httpAuthenticator(), crowdUserDetailsService());
  }

  @Bean
  public AuthenticationManager simpleCrowdAuthenticationManager() throws IOException {
    return new SimpleAuthenticationManager(securityServerClient());
  }

  @Bean
  public SecurityServerClient securityServerClient() throws IOException {
    if (securityServerClient == null) {
      securityServerClient =
          new SecurityServerClientImpl(
              SoapClientPropertiesImpl.newInstanceFromProperties(getProps()));
    }
    return securityServerClient;
  }

  @Bean
  public HttpAuthenticator httpAuthenticator() throws IOException {
    return new HttpAuthenticatorImpl(simpleCrowdAuthenticationManager());
  }

  private Properties getProps() throws IOException {

    Properties prop = new Properties();
    try (InputStream in =
        Thread.currentThread().getContextClassLoader().getResourceAsStream("crowd.properties")) {
      prop.load(in);
    }
    prop.setProperty("application.name", crowdApplicationName);
    prop.setProperty("application.password", crowdApplicationPassword);
    prop.setProperty("crowd.server.url", crowdServerUrl);
    prop.setProperty("cookie.domain", cookieDomain);
    return prop;
  }

  @Bean
  public CrowdUserDetailsService crowdUserDetailsService() throws IOException {
    CrowdUserDetailsServiceImpl cusd = new CrowdUserDetailsServiceImpl();
    cusd.setUserManager(userManager());
    cusd.setGroupMembershipManager(
        new SimpleCachingGroupMembershipManager(
            securityServerClient(), userManager(), groupManager(), getCache()));
    cusd.setAuthorityPrefix("");
    return cusd;
  }

  @Bean
  public UserManager userManager() throws IOException {
    return new CachingUserManager(securityServerClient(), getCache());
  }

  @Bean
  public BasicCache getCache() {
    return new CacheImpl(getCacheManager());
  }

  @Bean
  public CacheManager getCacheManager() {
    return getEhCacheFactory().getObject();
  }

  @Bean
  public EhCacheManagerFactoryBean getEhCacheFactory() {
    EhCacheManagerFactoryBean factoryBean = new EhCacheManagerFactoryBean();
    factoryBean.setConfigLocation(new ClassPathResource("crowd-ehcache.xml"));
    return factoryBean;
  }

  @Bean
  public GroupManager groupManager() throws IOException {
    return new CachingGroupManager(securityServerClient(), getCache());
  }

  @Bean
  public BasicAuthenticationEntryPoint basicAuthEntryPoint() {
    BasicAuthenticationEntryPoint entryPoint = new BasicAuthenticationEntryPoint();
    entryPoint.setRealmName(idManagerRealm);
    return entryPoint;
  }
}