/*
 * Copyright Java Operator SDK 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 io.javaoperatorsdk.operator.api.monitoring;

import java.util.List;
import java.util.Map;
import java.util.Objects;

import io.fabric8.kubernetes.api.model.HasMetadata;
import io.javaoperatorsdk.operator.api.reconciler.RetryInfo;
import io.javaoperatorsdk.operator.processing.Controller;
import io.javaoperatorsdk.operator.processing.event.Event;
import io.javaoperatorsdk.operator.processing.event.ResourceID;

/**
 * An aggregated implementation of the {@link Metrics} interface that delegates method calls to a
 * collection of {@link Metrics} instances using the composite pattern.
 *
 * <p>This class allows multiple metrics providers to be combined into a single metrics instance,
 * enabling simultaneous collection of metrics data by different monitoring systems or providers.
 * All method calls are delegated to each metrics instance in the list in the order they were
 * provided to the constructor.
 *
 * <p><strong>Important:</strong> The {@link #timeControllerExecution(ControllerExecution)} method
 * is handled specially - it is only invoked on the first metrics instance in the list, since it's
 * not an idempotent operation and can only be executed once. The controller execution cannot be
 * repeated multiple times as it would produce side effects and potentially inconsistent results.
 *
 * <p>All other methods are called on every metrics instance in the list, preserving the order of
 * execution as specified in the constructor.
 *
 * @see Metrics
 */
public final class AggregatedMetrics implements Metrics {

  private final List<Metrics> metricsList;

  /**
   * Creates a new AggregatedMetrics instance that will delegate method calls to the provided list
   * of metrics instances.
   *
   * @param metricsList the list of metrics instances to delegate to; must not be null and must
   *     contain at least one metrics instance
   * @throws NullPointerException if metricsList is null
   * @throws IllegalArgumentException if metricsList is empty
   */
  public AggregatedMetrics(List<Metrics> metricsList) {
    Objects.requireNonNull(metricsList, "metricsList must not be null");
    if (metricsList.isEmpty()) {
      throw new IllegalArgumentException("metricsList must contain at least one Metrics instance");
    }
    this.metricsList = List.copyOf(metricsList);
  }

  @Override
  public void controllerRegistered(Controller<? extends HasMetadata> controller) {
    metricsList.forEach(metrics -> metrics.controllerRegistered(controller));
  }

  @Override
  public void eventReceived(Event event, Map<String, Object> metadata) {
    metricsList.forEach(metrics -> metrics.eventReceived(event, metadata));
  }

  @Override
  public void reconciliationSubmitted(
      HasMetadata resource, RetryInfo retryInfo, Map<String, Object> metadata) {
    metricsList.forEach(metrics -> metrics.reconciliationSubmitted(resource, retryInfo, metadata));
  }

  @Override
  public void reconciliationFailed(
      HasMetadata resource, RetryInfo retry, Exception exception, Map<String, Object> metadata) {
    metricsList.forEach(
        metrics -> metrics.reconciliationFailed(resource, retry, exception, metadata));
  }

  @Override
  public void reconciliationStarted(HasMetadata resource, Map<String, Object> metadata) {
    metricsList.forEach(metrics -> metrics.reconciliationStarted(resource, metadata));
  }

  @Override
  public void reconciliationFinished(
      HasMetadata resource, RetryInfo retryInfo, Map<String, Object> metadata) {
    metricsList.forEach(metrics -> metrics.reconciliationFinished(resource, retryInfo, metadata));
  }

  @Override
  public void cleanupDone(ResourceID resourceID, Map<String, Object> metadata) {
    metricsList.forEach(metrics -> metrics.cleanupDone(resourceID, metadata));
  }

  @Override
  public void reconciliationSucceeded(HasMetadata resource, Map<String, Object> metadata) {
    metricsList.forEach(metrics -> metrics.reconciliationSucceeded(resource, metadata));
  }

  @Override
  public <T> T timeControllerExecution(ControllerExecution<T> execution) throws Exception {
    return metricsList.get(0).timeControllerExecution(execution);
  }
}
