/*
 * 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.service.ads;

import jakarta.inject.Inject;
import jakarta.inject.Provider;
import jakarta.inject.Singleton;

import org.apache.ambari.server.events.AmbariConfigurationChangedEvent;
import org.apache.ambari.server.ldap.domain.AmbariLdapConfiguration;
import org.apache.ambari.server.ldap.service.AmbariLdapException;
import org.apache.ambari.server.ldap.service.LdapConnectionConfigService;
import org.apache.directory.ldap.client.api.DefaultLdapConnectionFactory;
import org.apache.directory.ldap.client.api.LdapConnectionConfig;
import org.apache.directory.ldap.client.api.LdapConnectionFactory;
import org.apache.directory.ldap.client.api.LdapConnectionPool;
import org.apache.directory.ldap.client.api.ValidatingPoolableLdapConnectionFactory;
import org.apache.directory.ldap.client.template.LdapConnectionTemplate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.eventbus.Subscribe;

/**
 * Factory for creating LdapConnectionTemplate instances.
 * Depending on the usage context, the instance can be constructed based on the provided configuration or based on the persisted settings.
 */
@Singleton
public class LdapConnectionTemplateFactory {

  private static final Logger LOG = LoggerFactory.getLogger(LdapConnectionTemplateFactory.class);

  // Inject the persisted configuration (when available) check the provider implementation for details.
  @Inject
  private Provider<AmbariLdapConfiguration> ambariLdapConfigurationProvider;


  @Inject
  private LdapConnectionConfigService ldapConnectionConfigService;

  // cached instance that only changes when the underlying configuration changes.
  private LdapConnectionTemplate ldapConnectionTemplateInstance;


  @Inject
  public LdapConnectionTemplateFactory() {
  }

  /**
   * Creates a new instance based on the provided configuration. Use this factory method whle operating with ambari configuration not yet persisted.
   *
   * @param ambariLdapConfiguration ambari ldap configuration instance
   * @return an instance of LdapConnectionTemplate
   */
  public LdapConnectionTemplate create(AmbariLdapConfiguration ambariLdapConfiguration) throws AmbariLdapException {
    LOG.info("Constructing new instance based on the provided ambari ldap configuration: {}", ambariLdapConfiguration);

    // create the connection config
    LdapConnectionConfig ldapConnectionConfig = ldapConnectionConfigService.createLdapConnectionConfig(ambariLdapConfiguration);

    // create the connection factory
    LdapConnectionFactory ldapConnectionFactory = new DefaultLdapConnectionFactory(ldapConnectionConfig);

    // create the connection pool
    LdapConnectionPool ldapConnectionPool = new LdapConnectionPool(new ValidatingPoolableLdapConnectionFactory(ldapConnectionFactory));

    LdapConnectionTemplate template = new LdapConnectionTemplate(ldapConnectionPool);
    LOG.info("Ldap connection template instance: {}", template);

    return template;

  }

  /**
   * Loads the persisted LDAP configuration.
   *
   * @return theh persisted
   */
  public LdapConnectionTemplate load() throws AmbariLdapException {

    if (null == ldapConnectionTemplateInstance) {
      ldapConnectionTemplateInstance = create(ambariLdapConfigurationProvider.get());
    }
    return ldapConnectionTemplateInstance;
  }

  /**
   * The returned connection template instance is recreated whenever the ambari ldap configuration changes
   *
   * @param event
   * @throws AmbariLdapException
   */
  @Subscribe
  public void onConfigChange(AmbariConfigurationChangedEvent event) throws AmbariLdapException {
    ldapConnectionTemplateInstance = create(ambariLdapConfigurationProvider.get());
  }


}
