/*
 * 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 static javax.net.ssl.TrustManagerFactory.getDefaultAlgorithm;

import java.io.FileInputStream;
import java.security.KeyStore;

import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;

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

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.api.util.Strings;
import org.apache.directory.ldap.client.api.LdapConnectionConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Singleton
public class DefaultLdapConnectionConfigService implements LdapConnectionConfigService {

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

  @Inject
  public DefaultLdapConnectionConfigService() {
  }

  @Override
  public LdapConnectionConfig createLdapConnectionConfig(AmbariLdapConfiguration ambariLdapConfiguration) throws AmbariLdapException {

    LOG.debug("Assembling ldap connection config based on: {}", ambariLdapConfiguration);

    LdapConnectionConfig config = new LdapConnectionConfig();
    config.setLdapHost(ambariLdapConfiguration.serverHost());
    config.setLdapPort(ambariLdapConfiguration.serverPort());
    config.setName(ambariLdapConfiguration.bindDn());
    config.setCredentials(ambariLdapConfiguration.bindPassword());
    config.setUseSsl(ambariLdapConfiguration.useSSL());

    if ("custom".equals(ambariLdapConfiguration.trustStore())) {
      LOG.info("Using custom trust manager configuration");
      config.setTrustManagers(trustManagers(ambariLdapConfiguration));
    }

    return config;
  }


  /**
   * Configure the trust managers to use the custom keystore.
   *
   * @param ambariLdapConfiguration congiguration instance holding current values
   * @return the array of trust managers
   * @throws AmbariLdapException if an error occurs while setting up the connection
   */
  private TrustManager[] trustManagers(AmbariLdapConfiguration ambariLdapConfiguration) throws AmbariLdapException {
    try {

      TrustManagerFactory tmFactory = TrustManagerFactory.getInstance(getDefaultAlgorithm());
      tmFactory.init(keyStore(ambariLdapConfiguration));
      return tmFactory.getTrustManagers();

    } catch (Exception e) {

      LOG.error("Failed to initialize trust managers", e);
      throw new AmbariLdapException(e);

    }

  }

  private KeyStore keyStore(AmbariLdapConfiguration ambariLdapConfiguration) throws AmbariLdapException {

    // validating configuration settings
    if (Strings.isEmpty(ambariLdapConfiguration.trustStoreType())) {
      throw new AmbariLdapException("Key Store Type must be specified");
    }

    if (Strings.isEmpty(ambariLdapConfiguration.trustStorePath())) {
      throw new AmbariLdapException("Key Store Path must be specified");
    }

    try {

      KeyStore ks = KeyStore.getInstance(ambariLdapConfiguration.trustStoreType());
      FileInputStream fis = new FileInputStream(ambariLdapConfiguration.trustStorePath());
      ks.load(fis, ambariLdapConfiguration.trustStorePassword().toCharArray());
      return ks;

    } catch (Exception e) {

      LOG.error("Failed to create keystore", e);
      throw new AmbariLdapException(e);

    }
  }
}
