/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.gravitino.credential;

import java.io.IOException;
import java.lang.reflect.Constructor;
import java.util.Map;

/**
 * An abstract base class for {@link CredentialProvider} implementations that delegate the actual
 * credential generation to a {@link CredentialGenerator}. It handles the lazy and reflective
 * loading of the generator to isolate heavy dependencies.
 *
 * @param <T> The type of credential generated by this provider.
 */
public abstract class CredentialProviderDelegator<T extends Credential>
    implements CredentialProvider {

  /** The properties used by the generator to generate the credential. */
  protected Map<String, String> properties;

  private volatile CredentialGenerator<T> generator;

  /**
   * Initializes the provider by storing properties and loading the associated {@link
   * CredentialGenerator}.
   *
   * @param properties A map of configuration properties for the provider.
   */
  @Override
  public void initialize(Map<String, String> properties) {
    this.properties = properties;
    this.generator = loadGenerator();
    generator.initialize(properties);
  }

  /**
   * Delegates the credential generation to the loaded {@link CredentialGenerator}.
   *
   * @param context The context containing information required for credential retrieval.
   * @return A {@link Credential} object.
   * @throws RuntimeException if credential generation fails.
   */
  @Override
  public Credential getCredential(CredentialContext context) {
    try {
      return generator.generate(context);
    } catch (Exception e) {
      throw new RuntimeException(
          "Failed to generate credential using " + getGeneratorClassName(), e);
    }
  }

  @Override
  public void close() throws IOException {
    if (generator != null) {
      generator.close();
    }
  }

  /**
   * Returns the fully qualified class name of the {@link CredentialGenerator} implementation. This
   * generator will be loaded via reflection to perform the actual credential creation.
   *
   * @return The class name of the credential generator.
   */
  protected abstract String getGeneratorClassName();

  /**
   * Loads and instantiates the {@link CredentialGenerator} using reflection.
   *
   * <p>This implementation uses a no-argument constructor. The constructor can be non-public.
   *
   * @return An instance of the credential generator.
   * @throws RuntimeException if the generator cannot be loaded or instantiated.
   */
  @SuppressWarnings("unchecked")
  private CredentialGenerator<T> loadGenerator() {
    try {
      ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
      Class<?> generatorClass = Class.forName(getGeneratorClassName(), true, classLoader);
      Constructor<?> constructor = generatorClass.getDeclaredConstructor();
      constructor.setAccessible(true);
      return (CredentialGenerator<T>) constructor.newInstance();
    } catch (Exception e) {
      throw new RuntimeException(
          "Failed to load or instantiate CredentialGenerator: " + getGeneratorClassName(), e);
    }
  }
}
