/*
 * 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.hadoop.ozone.recon.api;

import java.io.IOException;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.TreeMap;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.apache.hadoop.hdds.protocol.DatanodeDetails;
import org.apache.hadoop.hdds.protocol.DatanodeID;
import org.apache.hadoop.hdds.scm.pipeline.Pipeline;
import org.apache.hadoop.hdds.scm.pipeline.PipelineID;
import org.apache.hadoop.hdds.scm.server.OzoneStorageContainerManager;
import org.apache.hadoop.ozone.recon.MetricsServiceProviderFactory;
import org.apache.hadoop.ozone.recon.api.types.PipelineMetadata;
import org.apache.hadoop.ozone.recon.api.types.PipelinesResponse;
import org.apache.hadoop.ozone.recon.metrics.Metric;
import org.apache.hadoop.ozone.recon.scm.ReconPipelineManager;
import org.apache.hadoop.ozone.recon.spi.MetricsServiceProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Endpoint to fetch details about Pipelines.
 */
@Path("/pipelines")
@Produces(MediaType.APPLICATION_JSON)
public class PipelineEndpoint {

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

  private ReconPipelineManager pipelineManager;
  private MetricsServiceProvider metricsServiceProvider;

  @Inject
  PipelineEndpoint(OzoneStorageContainerManager reconSCM,
                   MetricsServiceProviderFactory
                       metricsServiceProviderFactory) {
    this.pipelineManager = (ReconPipelineManager) reconSCM.getPipelineManager();
    this.metricsServiceProvider =
        metricsServiceProviderFactory.getMetricsServiceProvider();
  }

  /**
   * Return the list of pipelines with detailed information about each pipeline.
   * @return {@link Response}
   */
  @GET
  public Response getPipelines() {
    List<PipelineMetadata> pipelinesList = new ArrayList<>();
    List<Pipeline> pipelines = pipelineManager.getPipelines();

    pipelines.forEach(pipeline -> {
      final PipelineID pipelineId = pipeline.getId();
      List<DatanodeDetails> datanodes = new ArrayList<>();
      PipelineMetadata.Builder builder = PipelineMetadata.newBuilder();
      pipeline.getNodes().forEach(node -> datanodes.add(node));
      long duration =
          Instant.now().toEpochMilli() -
              pipeline.getCreationTimestamp().toEpochMilli();

      try {
        String leaderNode = pipeline.getLeaderNode().getHostName();
        builder.setLeaderNode(leaderNode);
      } catch (IOException ioEx) {
        LOG.warn("Cannot get leader node for pipeline {}",
            pipelineId, ioEx);
      }

      try {
        int containers =
            pipelineManager.getNumberOfContainers(pipeline.getId());
        builder.setContainers(containers);
      } catch (IOException ioEx) {
        LOG.warn("Cannot get containers for pipeline {} ", pipelineId, ioEx);
      }

      PipelineMetadata.Builder pipelineBuilder =
          builder.setPipelineId(pipelineId.getId())
              .setDatanodes(datanodes)
              .setDuration(duration)
              .setStatus(pipeline.getPipelineState())
              .setReplicationConfig(pipeline.getReplicationConfig());
      // If any metrics service providers like Prometheus
      // is configured, then query it for metrics and populate
      // leader election count and last leader election time
      if (metricsServiceProvider != null) {
        // Extract last part of pipelineId to get its group Id.
        // ex. group id of 48981bf7-8bea-4fbd-9857-79df51ee872d
        // is group-79DF51EE872D
        String[] splits = pipelineId.getId().toString().split("-");
        String groupId = "group-" + splits[splits.length - 1].toUpperCase();
        Long leaderElectionCount = getElectionCountMetricValue(groupId);
        pipelineBuilder.setLeaderElections(leaderElectionCount);
        Long lastLeaderElectionElapsedTime
                = getLastLeaderElectionElapsedTimeMetricValue(groupId,
                pipeline.getLeaderId());
        pipelineBuilder.setLastLeaderElection(lastLeaderElectionElapsedTime);
      }

      pipelinesList.add(pipelineBuilder.build());
    });

    PipelinesResponse pipelinesResponse =
        new PipelinesResponse(pipelinesList.size(), pipelinesList);
    return Response.ok(pipelinesResponse).build();
  }

  private Long getElectionCountMetricValue(String groupId) {
    Long electionCount = 0L;
    String metricsQuery = String.format(
            "query=ratis_leader_election_electionCount{group=\"%s\"}", groupId);
    try {
      List<Metric> metrics = metricsServiceProvider.getMetricsInstant(
              metricsQuery);
      if (!metrics.isEmpty()) {
        for (Metric m : metrics) {
          TreeMap<Double, Double> values = (TreeMap<Double, Double>)
                  m.getValues();
          if (!values.isEmpty()) {
            electionCount += values.firstEntry().getValue().longValue();
          }
        }
      }
    } catch (Exception ex) {
      if (LOG.isErrorEnabled()) {
        LOG.error("Unable to get metrics value for " +
                "ratis_leader_election_electionCount", ex);
      }
    }
    return electionCount;
  }

  private Long getLastLeaderElectionElapsedTimeMetricValue(String groupId,
                                                           DatanodeID dnId) {
    String metricsQuery = String.format(
            "query=ratis_leader_election_lastLeaderElectionElapsedTime{group=" +
                    "\"%s\",exported_instance=\"%s\"}", groupId, dnId);
    try {
      List<Metric> metrics = metricsServiceProvider.getMetricsInstant(
              metricsQuery);
      if (!metrics.isEmpty()) {
        TreeMap<Double, Double> values = (TreeMap<Double, Double>)
                metrics.get(0).getValues();
        if (!values.isEmpty()) {
          return values.firstEntry().getValue().longValue();
        }
      }
    } catch (Exception ex) {
      if (LOG.isErrorEnabled()) {
        LOG.error("Unable to get metrics value for " +
                "ratis_leader_election_lastLeaderElectionElapsedTime", ex);
      }
    }
    return 0L;
  }
}
