/*
 * 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.apache.ambari.server.ldap;

import static org.easymock.EasyMock.createNiceMock;

import java.util.Map;

import jakarta.persistence.EntityManager;

import org.apache.ambari.server.configuration.AmbariServerConfigurationKey;
import org.apache.ambari.server.ldap.domain.AmbariLdapConfiguration;
import org.apache.ambari.server.ldap.service.ads.LdapConnectionTemplateFactory;
import org.apache.ambari.server.ldap.service.ads.detectors.AttributeDetectorFactory;
import org.apache.ambari.server.orm.DBAccessor;
import org.apache.ambari.server.state.stack.OsFamily;
import org.apache.directory.api.ldap.model.constants.SchemaConstants;
import org.apache.directory.api.ldap.model.exception.LdapException;
import org.apache.directory.ldap.client.api.LdapConnection;
import org.apache.directory.ldap.client.template.ConnectionCallback;
import org.apache.directory.ldap.client.template.LdapConnectionTemplate;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.collect.Maps;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Module;
import com.google.inject.util.Modules;

/**
 * Test for the GUICE LdapModule setup
 *
 * - checks the module's bindings (can the GUICE context be created properely)
 * - checks for specific instances in the GUICE context (re they constructed properly, what is the instance' scope)
 *
 * It's named functional test as it creates a GUICE context. ("Real" unit tests only mock a class' collaborators, and
 * are more lightweight)
 *
 * By default the test is ignored, as it connects to external LDAP instances, thus in different environments may fail
 */
@Ignore
public class LdapModuleFunctionalTest {

  private static final Logger LOG = LoggerFactory.getLogger(LdapModuleFunctionalTest.class);
  private static Injector injector;


  @BeforeClass
  public static void beforeClass() throws Exception {

    // overriding bindings for testing purposes
    Module testModule = Modules.override(new LdapModule()).with(new AbstractModule() {
      @Override
      protected void configure() {
        // override the configuration instance binding not to access the database
        bind(EntityManager.class).toInstance(createNiceMock(EntityManager.class));
        bind(DBAccessor.class).toInstance(createNiceMock(DBAccessor.class));
        bind(OsFamily.class).toInstance(createNiceMock(OsFamily.class));
      }
    });

    injector = Guice.createInjector(testModule);
  }

  @Test
  public void shouldLdapTemplateBeInstantiated() throws Exception {
    // GIVEN
    // the injector is set up
    Assert.assertNotNull(injector);

    // WHEN
    LdapConnectionTemplateFactory ldapConnectionTemplateFactory = injector.getInstance(LdapConnectionTemplateFactory.class);
    AmbariLdapConfiguration ldapConfiguration = new AmbariLdapConfiguration(getProps());
    LdapConnectionTemplate template = ldapConnectionTemplateFactory.create(ldapConfiguration);

    // THEN
    Assert.assertNotNull(template);
    //template.authenticate(new Dn("cn=read-only-admin,dc=example,dc=com"), "password".toCharArray());

    Boolean success = template.execute(new ConnectionCallback<Boolean>() {
      @Override
      public Boolean doWithConnection(LdapConnection connection) throws LdapException {

        return connection.isConnected() && connection.isAuthenticated();
      }
    });

    Assert.assertTrue("Could not bind to the LDAP server", success);

  }


  private static Map<String, String> getProps() {
    Map<String, String> ldapPropsMap = Maps.newHashMap();

    ldapPropsMap.put(AmbariServerConfigurationKey.ANONYMOUS_BIND.key(), "true");
    ldapPropsMap.put(AmbariServerConfigurationKey.SERVER_HOST.key(), "ldap.forumsys.com");
    ldapPropsMap.put(AmbariServerConfigurationKey.SERVER_PORT.key(), "389");
    ldapPropsMap.put(AmbariServerConfigurationKey.BIND_DN.key(), "cn=read-only-admin,dc=example,dc=com");
    ldapPropsMap.put(AmbariServerConfigurationKey.BIND_PASSWORD.key(), "password");
//    ldapPropsMap.put(AmbariLdapConfigKeys.USE_SSL.key(), "true");

    ldapPropsMap.put(AmbariServerConfigurationKey.USER_OBJECT_CLASS.key(), SchemaConstants.PERSON_OC);
    ldapPropsMap.put(AmbariServerConfigurationKey.USER_NAME_ATTRIBUTE.key(), SchemaConstants.UID_AT);
    ldapPropsMap.put(AmbariServerConfigurationKey.USER_SEARCH_BASE.key(), "dc=example,dc=com");
    ldapPropsMap.put(AmbariServerConfigurationKey.DN_ATTRIBUTE.key(), SchemaConstants.UID_AT);
//    ldapPropsMap.put(AmbariLdapConfigKeys.TRUST_STORE.key(), "custom");
    ldapPropsMap.put(AmbariServerConfigurationKey.TRUST_STORE_TYPE.key(), "JKS");
//    ldapPropsMap.put(AmbariLdapConfigKeys.TRUST_STORE_PATH.key(), "/Users/lpuskas/my_truststore/KeyStore.jks");


    return ldapPropsMap;
  }

  @Test
  public void testShouldDetectorsBeBound() throws Exception {
    // GIVEN

    // WHEN
    AttributeDetectorFactory f = injector.getInstance(AttributeDetectorFactory.class);

    // THEN
    Assert.assertNotNull(f);
    LOG.info(f.groupAttributeDetector().toString());
    LOG.info(f.userAttributDetector().toString());

  }
}